@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
|
@@ -6,7 +6,7 @@ import type { OptionalNetworkParameterType } from "../../common/types.js";
|
|
|
6
6
|
import type { Environment } from "../../environments/index.js";
|
|
7
7
|
import type { UserStakingInfo } from "../../types/staking.js";
|
|
8
8
|
import { getMerklCampaignIds, getMerklRewardsData } from "./common.js";
|
|
9
|
-
import {
|
|
9
|
+
import { getGovernanceTokenPriceOrZero } from "./getWellPrice.js";
|
|
10
10
|
|
|
11
11
|
export type GetUserStakingInfoParameters<
|
|
12
12
|
environments,
|
|
@@ -150,7 +150,7 @@ export async function getUserStakingInfo<
|
|
|
150
150
|
environment.contracts.views?.read.getUserStakingInfo([userAddress]),
|
|
151
151
|
environment.contracts.governanceToken?.read.balanceOf([userAddress]),
|
|
152
152
|
environment.contracts.views?.read.getStakingInfo(),
|
|
153
|
-
|
|
153
|
+
getGovernanceTokenPriceOrZero(environment, baseEnvironment),
|
|
154
154
|
]);
|
|
155
155
|
|
|
156
156
|
const [userStakingR, balanceR, stakingScheduleR, priceR] = settled;
|
|
@@ -175,12 +175,6 @@ export async function getUserStakingInfo<
|
|
|
175
175
|
: 0n;
|
|
176
176
|
|
|
177
177
|
const price = priceR.status === "fulfilled" ? priceR.value : 0n;
|
|
178
|
-
if (priceR.status === "rejected") {
|
|
179
|
-
environment.onError?.(priceR.reason, {
|
|
180
|
-
source: "governance-token-price",
|
|
181
|
-
chainId: environment.chainId,
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
178
|
|
|
185
179
|
return { userStaking, schedule, tokenBalance, price };
|
|
186
180
|
}),
|
|
@@ -41,6 +41,71 @@ export async function getWellPriceFromBaseOracle(
|
|
|
41
41
|
return await oracle.read.getUnderlyingPrice([mWELL]);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
async function readGovernanceTokenPrice(
|
|
45
|
+
environment: Environment,
|
|
46
|
+
baseEnvironment?: Environment,
|
|
47
|
+
): Promise<bigint> {
|
|
48
|
+
if (environment.custom?.governance?.token === "WELL") {
|
|
49
|
+
return getWellPriceFromBaseOracle(baseEnvironment);
|
|
50
|
+
}
|
|
51
|
+
// Non-WELL (e.g. Moonriver / MFAM): the env itself is the governance "home".
|
|
52
|
+
const views = environment.contracts.views;
|
|
53
|
+
if (!views) return 0n;
|
|
54
|
+
return (await views.read.getGovernanceTokenPrice()) ?? 0n;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const GOVERNANCE_TOKEN_PRICE_TTL_MS = 10_000;
|
|
58
|
+
|
|
59
|
+
type OnErrorCallback = NonNullable<Environment["onError"]>;
|
|
60
|
+
|
|
61
|
+
type GovernanceTokenPriceEntry = {
|
|
62
|
+
promise: Promise<bigint>;
|
|
63
|
+
expiresAt: number;
|
|
64
|
+
requestingChainIds: Set<number>;
|
|
65
|
+
/** Callbacks already told about this read's failure. */
|
|
66
|
+
reportedTo: Set<OnErrorCallback>;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Keyed by the environment the price is read through (the Base env for every
|
|
70
|
+
// WELL chain), so markets, vaults, rewards and staking reads issued together
|
|
71
|
+
// share one request, while clients with their own RPCs never share results.
|
|
72
|
+
const governanceTokenPriceCache = new WeakMap<
|
|
73
|
+
Environment,
|
|
74
|
+
GovernanceTokenPriceEntry
|
|
75
|
+
>();
|
|
76
|
+
|
|
77
|
+
function getSharedGovernanceTokenPrice(
|
|
78
|
+
environment: Environment,
|
|
79
|
+
baseEnvironment?: Environment,
|
|
80
|
+
): { sourceChainId: number; entry: GovernanceTokenPriceEntry } {
|
|
81
|
+
const sourceEnvironment: Environment =
|
|
82
|
+
environment.custom?.governance?.token === "WELL"
|
|
83
|
+
? (baseEnvironment ?? publicEnvironments.base)
|
|
84
|
+
: environment;
|
|
85
|
+
const sourceChainId = sourceEnvironment.chainId;
|
|
86
|
+
|
|
87
|
+
const cached = governanceTokenPriceCache.get(sourceEnvironment);
|
|
88
|
+
if (cached && Date.now() < cached.expiresAt) {
|
|
89
|
+
cached.requestingChainIds.add(environment.chainId);
|
|
90
|
+
return { sourceChainId, entry: cached };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const entry: GovernanceTokenPriceEntry = {
|
|
94
|
+
promise: readGovernanceTokenPrice(environment, baseEnvironment),
|
|
95
|
+
expiresAt: Date.now() + GOVERNANCE_TOKEN_PRICE_TTL_MS,
|
|
96
|
+
requestingChainIds: new Set([environment.chainId]),
|
|
97
|
+
reportedTo: new Set(),
|
|
98
|
+
};
|
|
99
|
+
governanceTokenPriceCache.set(sourceEnvironment, entry);
|
|
100
|
+
// Failures are never cached: evict so the next call reads again.
|
|
101
|
+
entry.promise.catch(() => {
|
|
102
|
+
if (governanceTokenPriceCache.get(sourceEnvironment) === entry) {
|
|
103
|
+
governanceTokenPriceCache.delete(sourceEnvironment);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
return { sourceChainId, entry };
|
|
107
|
+
}
|
|
108
|
+
|
|
44
109
|
/**
|
|
45
110
|
* Returns the governance-token-in-USD price for an environment.
|
|
46
111
|
*
|
|
@@ -50,17 +115,44 @@ export async function getWellPriceFromBaseOracle(
|
|
|
50
115
|
* env's own views.getGovernanceTokenPrice() — Moonriver has its own MFAM
|
|
51
116
|
* oracle and isn't priced from Base.
|
|
52
117
|
*
|
|
53
|
-
*
|
|
118
|
+
* Concurrent calls for the same price source share one in-flight read, and a
|
|
119
|
+
* successful price is reused for a few seconds. Rejects if the read fails.
|
|
54
120
|
*/
|
|
55
121
|
export async function getGovernanceTokenPriceFor(
|
|
56
122
|
environment: Environment,
|
|
57
123
|
baseEnvironment?: Environment,
|
|
58
124
|
): Promise<bigint> {
|
|
59
|
-
|
|
60
|
-
|
|
125
|
+
return getSharedGovernanceTokenPrice(environment, baseEnvironment).entry
|
|
126
|
+
.promise;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Same shared read as `getGovernanceTokenPriceFor`, but resolves to 0n on
|
|
131
|
+
* failure. A failed shared read is reported once per `onError` callback,
|
|
132
|
+
* however many callers using that callback were waiting on it; `items` lists
|
|
133
|
+
* the requesting chainIds.
|
|
134
|
+
*/
|
|
135
|
+
export async function getGovernanceTokenPriceOrZero(
|
|
136
|
+
environment: Environment,
|
|
137
|
+
baseEnvironment?: Environment,
|
|
138
|
+
): Promise<bigint> {
|
|
139
|
+
const { sourceChainId, entry } = getSharedGovernanceTokenPrice(
|
|
140
|
+
environment,
|
|
141
|
+
baseEnvironment,
|
|
142
|
+
);
|
|
143
|
+
try {
|
|
144
|
+
return await entry.promise;
|
|
145
|
+
} catch (error) {
|
|
146
|
+
const onError = environment.onError;
|
|
147
|
+
if (onError && !entry.reportedTo.has(onError)) {
|
|
148
|
+
entry.reportedTo.add(onError);
|
|
149
|
+
environment.onError?.(error, {
|
|
150
|
+
source: "governance-token-price",
|
|
151
|
+
chainId: sourceChainId,
|
|
152
|
+
operation: "governance-token-price-read",
|
|
153
|
+
items: [...entry.requestingChainIds].map(String),
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
return 0n;
|
|
61
157
|
}
|
|
62
|
-
// Non-WELL (e.g. Moonriver / MFAM): the env itself is the governance "home".
|
|
63
|
-
const views = environment.contracts.views;
|
|
64
|
-
if (!views) return 0n;
|
|
65
|
-
return (await views.read.getGovernanceTokenPrice()) ?? 0n;
|
|
66
158
|
}
|
|
@@ -447,6 +447,29 @@ export const getProposalsOnChainData = async (
|
|
|
447
447
|
? governanceEnvironment
|
|
448
448
|
: getEnvironmentByChainId(chainId);
|
|
449
449
|
|
|
450
|
+
// viem batches these reads into one multicall, so a single failed request
|
|
451
|
+
// rejects every read in it. Collect failures per chain and report them once
|
|
452
|
+
// after the batch instead of once per read (Sentry MOONWELL-FRONTEND-1AX).
|
|
453
|
+
const readFailures = new Map<
|
|
454
|
+
number,
|
|
455
|
+
{ error: unknown; failed: number; total: number; proposalIds: Set<string> }
|
|
456
|
+
>();
|
|
457
|
+
const recordRead = (chainId: number, proposalId: number, error?: unknown) => {
|
|
458
|
+
const entry = readFailures.get(chainId) ?? {
|
|
459
|
+
error: undefined,
|
|
460
|
+
failed: 0,
|
|
461
|
+
total: 0,
|
|
462
|
+
proposalIds: new Set<string>(),
|
|
463
|
+
};
|
|
464
|
+
entry.total++;
|
|
465
|
+
if (error !== undefined) {
|
|
466
|
+
entry.error ??= error;
|
|
467
|
+
entry.failed++;
|
|
468
|
+
entry.proposalIds.add(String(proposalId));
|
|
469
|
+
}
|
|
470
|
+
readFailures.set(chainId, entry);
|
|
471
|
+
};
|
|
472
|
+
|
|
450
473
|
const onChainDataList = await Promise.all(
|
|
451
474
|
apiProposals.map(async (p) => {
|
|
452
475
|
const isLocal = p.chainId === governanceEnvironment.chainId;
|
|
@@ -501,29 +524,17 @@ export const getProposalsOnChainData = async (
|
|
|
501
524
|
]);
|
|
502
525
|
if (stateResult.status === "fulfilled") {
|
|
503
526
|
state = Number(stateResult.value);
|
|
527
|
+
recordRead(p.chainId, p.proposalId);
|
|
504
528
|
} else {
|
|
505
529
|
stateReadFailed = true;
|
|
506
|
-
|
|
507
|
-
`Failed to fetch state for proposal ${p.proposalId} (chainId=${p.chainId}):`,
|
|
508
|
-
stateResult.reason,
|
|
509
|
-
);
|
|
510
|
-
governanceEnvironment.onError?.(stateResult.reason, {
|
|
511
|
-
source: "governance-proposals",
|
|
512
|
-
chainId: p.chainId,
|
|
513
|
-
});
|
|
530
|
+
recordRead(p.chainId, p.proposalId, stateResult.reason);
|
|
514
531
|
}
|
|
515
532
|
if (proposalsResult.status === "fulfilled") {
|
|
516
533
|
proposalData = proposalsResult.value ?? null;
|
|
534
|
+
recordRead(p.chainId, p.proposalId);
|
|
517
535
|
} else {
|
|
518
536
|
proposalsReadFailed = true;
|
|
519
|
-
|
|
520
|
-
`Failed to fetch proposalData for proposal ${p.proposalId} (chainId=${p.chainId}):`,
|
|
521
|
-
proposalsResult.reason,
|
|
522
|
-
);
|
|
523
|
-
governanceEnvironment.onError?.(proposalsResult.reason, {
|
|
524
|
-
source: "governance-proposals",
|
|
525
|
-
chainId: p.chainId,
|
|
526
|
-
});
|
|
537
|
+
recordRead(p.chainId, p.proposalId, proposalsResult.reason);
|
|
527
538
|
}
|
|
528
539
|
}
|
|
529
540
|
|
|
@@ -609,6 +620,23 @@ export const getProposalsOnChainData = async (
|
|
|
609
620
|
}),
|
|
610
621
|
);
|
|
611
622
|
|
|
623
|
+
for (const [chainId, entry] of readFailures) {
|
|
624
|
+
if (entry.failed === 0) continue;
|
|
625
|
+
const proposalIds = [...entry.proposalIds];
|
|
626
|
+
console.warn(
|
|
627
|
+
`Failed ${entry.failed}/${entry.total} governor reads (chainId=${chainId}, proposals ${proposalIds.join(", ")}):`,
|
|
628
|
+
entry.error,
|
|
629
|
+
);
|
|
630
|
+
governanceEnvironment.onError?.(entry.error, {
|
|
631
|
+
source: "governance-proposals",
|
|
632
|
+
chainId,
|
|
633
|
+
operation: "proposal-onchain-read",
|
|
634
|
+
failedCount: entry.failed,
|
|
635
|
+
totalCount: entry.total,
|
|
636
|
+
items: proposalIds,
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
|
|
612
640
|
return onChainDataList;
|
|
613
641
|
};
|
|
614
642
|
|
|
@@ -20,7 +20,16 @@ import {
|
|
|
20
20
|
export type GetProposalsParameters<
|
|
21
21
|
environments,
|
|
22
22
|
network extends Chain | undefined,
|
|
23
|
-
> = OptionalNetworkParameterType<environments, network
|
|
23
|
+
> = OptionalNetworkParameterType<environments, network> & {
|
|
24
|
+
/**
|
|
25
|
+
* Governance chains to fetch proposals from: 1 (Ethereum, active),
|
|
26
|
+
* 1284 (Moonbeam archive) and/or 1285 (Moonriver archive). Defaults to all
|
|
27
|
+
* of them. The archives hold almost all of the payload (~6.6MB of ~7MB), so
|
|
28
|
+
* views that only show recent proposals can pass `[1]` and load the archives
|
|
29
|
+
* separately when needed. Unsupported chainIds are ignored.
|
|
30
|
+
*/
|
|
31
|
+
proposalChainIds?: readonly number[];
|
|
32
|
+
};
|
|
24
33
|
|
|
25
34
|
export type GetProposalsReturnType = Promise<Proposal[]>;
|
|
26
35
|
|
|
@@ -42,7 +51,10 @@ export async function getProposals<
|
|
|
42
51
|
return [];
|
|
43
52
|
}
|
|
44
53
|
|
|
45
|
-
const proposals = await fetchGovernorProposals(
|
|
54
|
+
const proposals = await fetchGovernorProposals(
|
|
55
|
+
governanceEnvironment,
|
|
56
|
+
args?.proposalChainIds,
|
|
57
|
+
);
|
|
46
58
|
// Newer multigov-ethereum proposals (chainId=1) and historical Moonbeam
|
|
47
59
|
// proposals (chainId=1284) restart their proposalId counters from 1, so IDs
|
|
48
60
|
// may collide across chains. Sort by proposalId desc with chainId as a
|
|
@@ -58,7 +70,8 @@ export async function getProposals<
|
|
|
58
70
|
}
|
|
59
71
|
|
|
60
72
|
/**
|
|
61
|
-
* Fetch every proposal the Governor API serves, across all governance chains
|
|
73
|
+
* Fetch every proposal the Governor API serves, across all governance chains
|
|
74
|
+
* (or only `proposalChainIds`, when given).
|
|
62
75
|
*
|
|
63
76
|
* One indexer DO serves them all:
|
|
64
77
|
* - chainId=1 (Ethereum) — the active multigov contract
|
|
@@ -76,16 +89,20 @@ export async function getProposals<
|
|
|
76
89
|
*/
|
|
77
90
|
async function fetchGovernorProposals(
|
|
78
91
|
governanceEnvironment: Environment,
|
|
92
|
+
proposalChainIds?: readonly number[],
|
|
79
93
|
): Promise<Proposal[]> {
|
|
94
|
+
const chainIds = SUPPORTED_GOVERNOR_CHAIN_IDS.filter(
|
|
95
|
+
(chainId) => !proposalChainIds || proposalChainIds.includes(chainId),
|
|
96
|
+
);
|
|
80
97
|
const results = await Promise.allSettled(
|
|
81
|
-
|
|
98
|
+
chainIds.map((chainId) =>
|
|
82
99
|
fetchAllProposals(governanceEnvironment, { chainId }),
|
|
83
100
|
),
|
|
84
101
|
);
|
|
85
102
|
|
|
86
103
|
const apiProposals: ApiProposal[] = [];
|
|
87
104
|
results.forEach((result, index) => {
|
|
88
|
-
const chainId =
|
|
105
|
+
const chainId = chainIds[index];
|
|
89
106
|
if (result.status === "fulfilled") {
|
|
90
107
|
apiProposals.push(...result.value);
|
|
91
108
|
} else if (chainId !== undefined) {
|
|
@@ -76,6 +76,21 @@ function validateLoanDecimals(value: unknown): number {
|
|
|
76
76
|
return value;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
/** Summarizes an unexpected top-level body so the error says what arrived. */
|
|
80
|
+
function describeSharedLiquidityBody(data: unknown): string {
|
|
81
|
+
if (data === null || data === undefined) return `received ${data}`;
|
|
82
|
+
if (typeof data === "string") {
|
|
83
|
+
return `received string of ${data.length} chars: ${JSON.stringify(data.slice(0, 100))}`;
|
|
84
|
+
}
|
|
85
|
+
if (typeof data !== "object") return `received ${typeof data}`;
|
|
86
|
+
if (Array.isArray(data)) return `received array of ${data.length}`;
|
|
87
|
+
const body = data as Record<string, unknown>;
|
|
88
|
+
const kind = (value: unknown): string =>
|
|
89
|
+
Array.isArray(value) ? "array" : value === null ? "null" : typeof value;
|
|
90
|
+
const keys = Object.keys(body).slice(0, 10).join(", ");
|
|
91
|
+
return `received object with keys [${keys}]; vaults: ${kind(body.vaults)}, markets: ${kind(body.markets)}`;
|
|
92
|
+
}
|
|
93
|
+
|
|
79
94
|
function validatedMarketLiquidity(
|
|
80
95
|
data: LunarSharedLiquidityResponse,
|
|
81
96
|
): Map<string, number> {
|
|
@@ -86,9 +101,19 @@ function validatedMarketLiquidity(
|
|
|
86
101
|
typeof data.markets !== "object" ||
|
|
87
102
|
Array.isArray(data.markets)
|
|
88
103
|
) {
|
|
89
|
-
throw new TypeError(
|
|
104
|
+
throw new TypeError(
|
|
105
|
+
`Invalid shared-liquidity response (${describeSharedLiquidityBody(data)})`,
|
|
106
|
+
);
|
|
90
107
|
}
|
|
91
108
|
for (const vault of data.vaults) {
|
|
109
|
+
// A null entry or a vault record without `markets` (Sentry
|
|
110
|
+
// MOONWELL-FRONTEND-116, MOO-535) must fail validation here, not as a
|
|
111
|
+
// TypeError from `vault.fee` or the loop below.
|
|
112
|
+
if (!vault || !Array.isArray(vault.markets)) {
|
|
113
|
+
throw new TypeError(
|
|
114
|
+
`Invalid shared-liquidity response (vault ${vault?.address ?? "<null>"} has no markets array)`,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
92
117
|
parseLiquidityQuantity(vault.fee, "vault.fee", true);
|
|
93
118
|
for (const market of vault.markets) {
|
|
94
119
|
for (const field of [
|
|
@@ -158,13 +183,31 @@ export async function fetchSharedLiquidityFromLunar(
|
|
|
158
183
|
const abort = (): void => controller.abort();
|
|
159
184
|
if (options.signal?.aborted) abort();
|
|
160
185
|
options.signal?.addEventListener("abort", abort, { once: true });
|
|
161
|
-
|
|
186
|
+
let timedOut = false;
|
|
187
|
+
const timer = setTimeout(() => {
|
|
188
|
+
timedOut = true;
|
|
189
|
+
abort();
|
|
190
|
+
}, timeoutMs);
|
|
162
191
|
try {
|
|
163
192
|
const response = await getWithRetry<LunarSharedLiquidityResponse>(
|
|
164
193
|
`${lunarIndexerUrl}/api/v1/isolated/shared-liquidity/${chainId}`,
|
|
165
194
|
{ timeout: timeoutMs, signal: controller.signal },
|
|
166
195
|
);
|
|
167
196
|
return response.data;
|
|
197
|
+
} catch (error) {
|
|
198
|
+
// Our own deadline aborts the request, which axios reports as a bare
|
|
199
|
+
// `CanceledError: canceled` (Sentry MOONWELL-FRONTEND-1C7 & co). That is a
|
|
200
|
+
// timeout, not a cancellation: surface it as one. A caller's own abort
|
|
201
|
+
// still propagates as the CanceledError it is.
|
|
202
|
+
if (timedOut && !options.signal?.aborted) {
|
|
203
|
+
const timeoutError = new Error(
|
|
204
|
+
`Shared-liquidity request timed out after ${timeoutMs}ms (chainId=${chainId})`,
|
|
205
|
+
{ cause: error },
|
|
206
|
+
);
|
|
207
|
+
timeoutError.name = "TimeoutError";
|
|
208
|
+
throw timeoutError;
|
|
209
|
+
}
|
|
210
|
+
throw error;
|
|
168
211
|
} finally {
|
|
169
212
|
clearTimeout(timer);
|
|
170
213
|
options.signal?.removeEventListener("abort", abort);
|
|
@@ -186,6 +186,13 @@ export async function fetchMarketsFromIndexer(
|
|
|
186
186
|
const queryString = params.toString();
|
|
187
187
|
const url = `${lunarIndexerUrl}/api/v1/isolated/markets/${chainId}${queryString ? `?${queryString}` : ""}`;
|
|
188
188
|
const response = await getWithRetry<LunarIndexerMarketsResponse>(url);
|
|
189
|
+
// A 2xx body without `results` otherwise reaches `.filter` / iteration in
|
|
190
|
+
// getMorphoMarketsDataFromIndexer as a TypeError (PR #339 review).
|
|
191
|
+
if (!Array.isArray(response.data?.results)) {
|
|
192
|
+
throw new Error(
|
|
193
|
+
`Lunar Indexer morpho markets response for chain ${chainId} is missing the results array`,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
189
196
|
return response.data;
|
|
190
197
|
}
|
|
191
198
|
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from "../../../environments/utils/index.js";
|
|
14
14
|
import type { MorphoUserReward } from "../../../types/morphoUserReward.js";
|
|
15
15
|
import type { MorphoUserStakingReward } from "../../../types/morphoUserStakingReward.js";
|
|
16
|
-
import {
|
|
16
|
+
import { getGovernanceTokenPriceOrZero } from "../../governance/getWellPrice.js";
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Error thrown for any failure communicating with the Merkl API: non-ok HTTP
|
|
@@ -166,13 +166,7 @@ export async function getUserMorphoStakingRewardsData(params: {
|
|
|
166
166
|
await Promise.all([
|
|
167
167
|
viewsContract?.read.getAllMarketsInfo(),
|
|
168
168
|
homeViewsContract?.read.getNativeTokenPrice(),
|
|
169
|
-
|
|
170
|
-
params.environment.onError?.(err, {
|
|
171
|
-
source: "governance-token-price",
|
|
172
|
-
chainId: params.environment.chainId,
|
|
173
|
-
});
|
|
174
|
-
return 0n;
|
|
175
|
-
}),
|
|
169
|
+
getGovernanceTokenPriceOrZero(params.environment),
|
|
176
170
|
]);
|
|
177
171
|
|
|
178
172
|
const governanceTokenPrice = new Amount(governanceTokenPriceRaw, 18);
|