@moonwell-fi/moonwell-sdk 0.23.0 → 0.25.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 (199) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/_cjs/actions/axiosWithRetry.js +10 -11
  3. package/_cjs/actions/axiosWithRetry.js.map +1 -1
  4. package/_cjs/actions/core/getUserBalances.js +63 -32
  5. package/_cjs/actions/core/getUserBalances.js.map +1 -1
  6. package/_cjs/actions/core/markets/common.js +31 -42
  7. package/_cjs/actions/core/markets/common.js.map +1 -1
  8. package/_cjs/actions/core/markets/getMarkets.js +7 -3
  9. package/_cjs/actions/core/markets/getMarkets.js.map +1 -1
  10. package/_cjs/actions/core/user-positions/common.js +56 -60
  11. package/_cjs/actions/core/user-positions/common.js.map +1 -1
  12. package/_cjs/actions/core/user-positions/getUserPositions.js +9 -6
  13. package/_cjs/actions/core/user-positions/getUserPositions.js.map +1 -1
  14. package/_cjs/actions/governance/getStakingInfo.js +1 -7
  15. package/_cjs/actions/governance/getStakingInfo.js.map +1 -1
  16. package/_cjs/actions/governance/getUserStakingInfo.js +1 -7
  17. package/_cjs/actions/governance/getUserStakingInfo.js.map +1 -1
  18. package/_cjs/actions/governance/getWellPrice.js +51 -1
  19. package/_cjs/actions/governance/getWellPrice.js.map +1 -1
  20. package/_cjs/actions/governance/proposals/common.js +34 -15
  21. package/_cjs/actions/governance/proposals/common.js.map +1 -1
  22. package/_cjs/actions/governance/proposals/getProposals.js +5 -4
  23. package/_cjs/actions/governance/proposals/getProposals.js.map +1 -1
  24. package/_cjs/actions/index.js +3 -1
  25. package/_cjs/actions/index.js.map +1 -1
  26. package/_cjs/actions/morpho/markets/common.js +155 -26
  27. package/_cjs/actions/morpho/markets/common.js.map +1 -1
  28. package/_cjs/actions/morpho/markets/getMorphoMarket.js +1 -0
  29. package/_cjs/actions/morpho/markets/getMorphoMarket.js.map +1 -1
  30. package/_cjs/actions/morpho/markets/getMorphoMarkets.js +1 -0
  31. package/_cjs/actions/morpho/markets/getMorphoMarkets.js.map +1 -1
  32. package/_cjs/actions/morpho/markets/getMorphoMarketsSharedLiquidity.js +46 -0
  33. package/_cjs/actions/morpho/markets/getMorphoMarketsSharedLiquidity.js.map +1 -0
  34. package/_cjs/actions/morpho/user-positions/common.js +26 -24
  35. package/_cjs/actions/morpho/user-positions/common.js.map +1 -1
  36. package/_cjs/actions/morpho/user-positions/getMorphoMarketUserPositions.js +7 -8
  37. package/_cjs/actions/morpho/user-positions/getMorphoMarketUserPositions.js.map +1 -1
  38. package/_cjs/actions/morpho/user-positions/getMorphoVaultUserPositions.js +7 -8
  39. package/_cjs/actions/morpho/user-positions/getMorphoVaultUserPositions.js.map +1 -1
  40. package/_cjs/actions/morpho/user-rewards/common.js +1 -7
  41. package/_cjs/actions/morpho/user-rewards/common.js.map +1 -1
  42. package/_cjs/actions/morpho/vaults/common.js +104 -87
  43. package/_cjs/actions/morpho/vaults/common.js.map +1 -1
  44. package/_cjs/actions/morpho/vaults/lunarIndexerTransform.js +31 -3
  45. package/_cjs/actions/morpho/vaults/lunarIndexerTransform.js.map +1 -1
  46. package/_cjs/actions/retry.js +43 -13
  47. package/_cjs/actions/retry.js.map +1 -1
  48. package/_cjs/client/createActions.js +2 -0
  49. package/_cjs/client/createActions.js.map +1 -1
  50. package/_cjs/common/error.js +37 -1
  51. package/_cjs/common/error.js.map +1 -1
  52. package/_cjs/common/index.js +2 -1
  53. package/_cjs/common/index.js.map +1 -1
  54. package/_cjs/common/readAcrossEnvironments.js +17 -0
  55. package/_cjs/common/readAcrossEnvironments.js.map +1 -0
  56. package/_cjs/environments/types/config.js.map +1 -1
  57. package/_cjs/errors/version.js +1 -1
  58. package/_cjs/index.js +3 -1
  59. package/_cjs/index.js.map +1 -1
  60. package/_esm/actions/axiosWithRetry.js +11 -13
  61. package/_esm/actions/axiosWithRetry.js.map +1 -1
  62. package/_esm/actions/core/getUserBalances.js +72 -35
  63. package/_esm/actions/core/getUserBalances.js.map +1 -1
  64. package/_esm/actions/core/markets/common.js +36 -43
  65. package/_esm/actions/core/markets/common.js.map +1 -1
  66. package/_esm/actions/core/markets/getMarkets.js +7 -3
  67. package/_esm/actions/core/markets/getMarkets.js.map +1 -1
  68. package/_esm/actions/core/user-positions/common.js +65 -63
  69. package/_esm/actions/core/user-positions/common.js.map +1 -1
  70. package/_esm/actions/core/user-positions/getUserPositions.js +14 -6
  71. package/_esm/actions/core/user-positions/getUserPositions.js.map +1 -1
  72. package/_esm/actions/governance/getStakingInfo.js +2 -8
  73. package/_esm/actions/governance/getStakingInfo.js.map +1 -1
  74. package/_esm/actions/governance/getUserStakingInfo.js +2 -8
  75. package/_esm/actions/governance/getUserStakingInfo.js.map +1 -1
  76. package/_esm/actions/governance/getWellPrice.js +67 -7
  77. package/_esm/actions/governance/getWellPrice.js.map +1 -1
  78. package/_esm/actions/governance/proposals/common.js +37 -12
  79. package/_esm/actions/governance/proposals/common.js.map +1 -1
  80. package/_esm/actions/governance/proposals/getProposals.js +7 -5
  81. package/_esm/actions/governance/proposals/getProposals.js.map +1 -1
  82. package/_esm/actions/index.js +1 -0
  83. package/_esm/actions/index.js.map +1 -1
  84. package/_esm/actions/morpho/markets/common.js +168 -27
  85. package/_esm/actions/morpho/markets/common.js.map +1 -1
  86. package/_esm/actions/morpho/markets/getMorphoMarket.js +1 -0
  87. package/_esm/actions/morpho/markets/getMorphoMarket.js.map +1 -1
  88. package/_esm/actions/morpho/markets/getMorphoMarkets.js +1 -0
  89. package/_esm/actions/morpho/markets/getMorphoMarkets.js.map +1 -1
  90. package/_esm/actions/morpho/markets/getMorphoMarketsSharedLiquidity.js +48 -0
  91. package/_esm/actions/morpho/markets/getMorphoMarketsSharedLiquidity.js.map +1 -0
  92. package/_esm/actions/morpho/user-positions/common.js +31 -24
  93. package/_esm/actions/morpho/user-positions/common.js.map +1 -1
  94. package/_esm/actions/morpho/user-positions/getMorphoMarketUserPositions.js +12 -8
  95. package/_esm/actions/morpho/user-positions/getMorphoMarketUserPositions.js.map +1 -1
  96. package/_esm/actions/morpho/user-positions/getMorphoVaultUserPositions.js +12 -8
  97. package/_esm/actions/morpho/user-positions/getMorphoVaultUserPositions.js.map +1 -1
  98. package/_esm/actions/morpho/user-rewards/common.js +2 -8
  99. package/_esm/actions/morpho/user-rewards/common.js.map +1 -1
  100. package/_esm/actions/morpho/vaults/common.js +110 -91
  101. package/_esm/actions/morpho/vaults/common.js.map +1 -1
  102. package/_esm/actions/morpho/vaults/lunarIndexerTransform.js +36 -4
  103. package/_esm/actions/morpho/vaults/lunarIndexerTransform.js.map +1 -1
  104. package/_esm/actions/retry.js +45 -15
  105. package/_esm/actions/retry.js.map +1 -1
  106. package/_esm/client/createActions.js +2 -0
  107. package/_esm/client/createActions.js.map +1 -1
  108. package/_esm/common/error.js +49 -0
  109. package/_esm/common/error.js.map +1 -1
  110. package/_esm/common/index.js +1 -1
  111. package/_esm/common/index.js.map +1 -1
  112. package/_esm/common/readAcrossEnvironments.js +30 -0
  113. package/_esm/common/readAcrossEnvironments.js.map +1 -0
  114. package/_esm/environments/types/config.js.map +1 -1
  115. package/_esm/errors/version.js +1 -1
  116. package/_esm/index.js +1 -0
  117. package/_esm/index.js.map +1 -1
  118. package/_types/actions/axiosWithRetry.d.ts +1 -0
  119. package/_types/actions/axiosWithRetry.d.ts.map +1 -1
  120. package/_types/actions/core/getUserBalances.d.ts.map +1 -1
  121. package/_types/actions/core/markets/common.d.ts.map +1 -1
  122. package/_types/actions/core/markets/getMarkets.d.ts.map +1 -1
  123. package/_types/actions/core/user-positions/common.d.ts.map +1 -1
  124. package/_types/actions/core/user-positions/getUserPositions.d.ts +5 -0
  125. package/_types/actions/core/user-positions/getUserPositions.d.ts.map +1 -1
  126. package/_types/actions/governance/getStakingInfo.d.ts.map +1 -1
  127. package/_types/actions/governance/getUserStakingInfo.d.ts.map +1 -1
  128. package/_types/actions/governance/getWellPrice.d.ts +9 -1
  129. package/_types/actions/governance/getWellPrice.d.ts.map +1 -1
  130. package/_types/actions/governance/proposals/common.d.ts.map +1 -1
  131. package/_types/actions/governance/proposals/getProposals.d.ts +10 -1
  132. package/_types/actions/governance/proposals/getProposals.d.ts.map +1 -1
  133. package/_types/actions/index.d.ts +1 -0
  134. package/_types/actions/index.d.ts.map +1 -1
  135. package/_types/actions/morpho/markets/common.d.ts +7 -10
  136. package/_types/actions/morpho/markets/common.d.ts.map +1 -1
  137. package/_types/actions/morpho/markets/getMorphoMarket.d.ts +2 -0
  138. package/_types/actions/morpho/markets/getMorphoMarket.d.ts.map +1 -1
  139. package/_types/actions/morpho/markets/getMorphoMarkets.d.ts +2 -0
  140. package/_types/actions/morpho/markets/getMorphoMarkets.d.ts.map +1 -1
  141. package/_types/actions/morpho/markets/getMorphoMarketsSharedLiquidity.d.ts +17 -0
  142. package/_types/actions/morpho/markets/getMorphoMarketsSharedLiquidity.d.ts.map +1 -0
  143. package/_types/actions/morpho/user-positions/common.d.ts.map +1 -1
  144. package/_types/actions/morpho/user-positions/getMorphoMarketUserPositions.d.ts +5 -0
  145. package/_types/actions/morpho/user-positions/getMorphoMarketUserPositions.d.ts.map +1 -1
  146. package/_types/actions/morpho/user-positions/getMorphoVaultUserPositions.d.ts +5 -0
  147. package/_types/actions/morpho/user-positions/getMorphoVaultUserPositions.d.ts.map +1 -1
  148. package/_types/actions/morpho/user-rewards/common.d.ts.map +1 -1
  149. package/_types/actions/morpho/vaults/common.d.ts.map +1 -1
  150. package/_types/actions/morpho/vaults/lunarIndexerTransform.d.ts.map +1 -1
  151. package/_types/actions/retry.d.ts +5 -4
  152. package/_types/actions/retry.d.ts.map +1 -1
  153. package/_types/client/createActions.d.ts +2 -0
  154. package/_types/client/createActions.d.ts.map +1 -1
  155. package/_types/common/error.d.ts +31 -0
  156. package/_types/common/error.d.ts.map +1 -1
  157. package/_types/common/index.d.ts +2 -2
  158. package/_types/common/index.d.ts.map +1 -1
  159. package/_types/common/readAcrossEnvironments.d.ts +23 -0
  160. package/_types/common/readAcrossEnvironments.d.ts.map +1 -0
  161. package/_types/environments/types/config.d.ts +11 -1
  162. package/_types/environments/types/config.d.ts.map +1 -1
  163. package/_types/errors/version.d.ts +1 -1
  164. package/_types/index.d.ts +3 -1
  165. package/_types/index.d.ts.map +1 -1
  166. package/_types/types/morphoMarket.d.ts +9 -0
  167. package/_types/types/morphoMarket.d.ts.map +1 -1
  168. package/actions/axiosWithRetry.ts +19 -13
  169. package/actions/core/getUserBalances.ts +98 -51
  170. package/actions/core/markets/common.ts +49 -51
  171. package/actions/core/markets/getMarkets.ts +9 -5
  172. package/actions/core/user-positions/common.ts +107 -111
  173. package/actions/core/user-positions/getUserPositions.ts +11 -10
  174. package/actions/governance/getStakingInfo.ts +2 -9
  175. package/actions/governance/getUserStakingInfo.ts +2 -8
  176. package/actions/governance/getWellPrice.ts +99 -7
  177. package/actions/governance/proposals/common.ts +44 -19
  178. package/actions/governance/proposals/getProposals.ts +22 -5
  179. package/actions/index.ts +5 -0
  180. package/actions/morpho/markets/common.ts +324 -125
  181. package/actions/morpho/markets/getMorphoMarket.ts +3 -0
  182. package/actions/morpho/markets/getMorphoMarkets.ts +3 -0
  183. package/actions/morpho/markets/getMorphoMarketsSharedLiquidity.ts +93 -0
  184. package/actions/morpho/user-positions/common.ts +48 -42
  185. package/actions/morpho/user-positions/getMorphoMarketUserPositions.ts +14 -15
  186. package/actions/morpho/user-positions/getMorphoVaultUserPositions.ts +14 -15
  187. package/actions/morpho/user-rewards/common.ts +2 -8
  188. package/actions/morpho/vaults/common.ts +161 -127
  189. package/actions/morpho/vaults/lunarIndexerTransform.ts +41 -6
  190. package/actions/retry.ts +53 -18
  191. package/client/createActions.ts +13 -0
  192. package/common/error.ts +45 -0
  193. package/common/index.ts +2 -2
  194. package/common/readAcrossEnvironments.ts +45 -0
  195. package/environments/types/config.ts +11 -1
  196. package/errors/version.ts +1 -1
  197. package/index.ts +3 -0
  198. package/package.json +1 -1
  199. package/types/morphoMarket.ts +10 -0
@@ -0,0 +1,93 @@
1
+ import type { MoonwellClient } from "../../../client/createMoonwellClient.js";
2
+ import { getEnvironmentFromArgs } from "../../../common/index.js";
3
+ import type { NetworkParameterType } from "../../../common/types.js";
4
+ import type { Chain } from "../../../environments/index.js";
5
+ import type {
6
+ MorphoMarket,
7
+ MorphoMarketSharedLiquidity,
8
+ } from "../../../types/morphoMarket.js";
9
+ import {
10
+ computeSharedLiquidityFromLunar,
11
+ fetchSharedLiquidityFromLunar,
12
+ } from "./common.js";
13
+
14
+ export type GetMorphoMarketsSharedLiquidityParameters<
15
+ environments,
16
+ network extends Chain | undefined,
17
+ > = NetworkParameterType<environments, network> & {
18
+ /** Pass the full chain's base market result to retain allocation parameters. */
19
+ markets: readonly MorphoMarket[];
20
+ signal?: AbortSignal;
21
+ /** Overall deadline, including retries and backoff. Defaults to 15 seconds. */
22
+ timeoutMs?: number;
23
+ };
24
+
25
+ export type GetMorphoMarketsSharedLiquidityReturnType = Promise<
26
+ MorphoMarketSharedLiquidity[]
27
+ >;
28
+
29
+ /** Fetch allocator liquidity independently, without refetching base market data.
30
+ * Rejects on failure so callers can retain the previous successful snapshot and
31
+ * distinguish unavailable liquidity from a successful empty allocation list. */
32
+ export async function getMorphoMarketsSharedLiquidity<
33
+ environments,
34
+ Network extends Chain | undefined,
35
+ >(
36
+ client: MoonwellClient,
37
+ args: GetMorphoMarketsSharedLiquidityParameters<environments, Network>,
38
+ ): GetMorphoMarketsSharedLiquidityReturnType {
39
+ const environment = getEnvironmentFromArgs(client, args);
40
+ if (!environment?.lunarIndexerUrl) {
41
+ throw new Error(
42
+ "Independent shared liquidity requires a configured Lunar Indexer environment",
43
+ );
44
+ }
45
+ if (args.markets.some((market) => market.chainId !== environment.chainId)) {
46
+ throw new Error(
47
+ "Shared-liquidity markets must belong to the requested chain",
48
+ );
49
+ }
50
+ if (args.markets.length === 0) return [];
51
+
52
+ const marketParamsMap = new Map(
53
+ args.markets.map((market) => [
54
+ market.marketId.toLowerCase(),
55
+ {
56
+ oracle: market.marketParams.oracle,
57
+ irm: market.marketParams.irm,
58
+ lltv: market.marketParams.lltv.toString(),
59
+ // Display token metadata can use the native-token zero address for ETH.
60
+ // Allocator transactions need the actual ERC20 addresses from Morpho.
61
+ loanToken: {
62
+ address: market.marketParams.loanToken,
63
+ decimals: market.loanToken.decimals,
64
+ },
65
+ collateralToken: {
66
+ address: market.marketParams.collateralToken,
67
+ decimals: market.collateralToken.decimals,
68
+ },
69
+ },
70
+ ]),
71
+ );
72
+ try {
73
+ const data = await fetchSharedLiquidityFromLunar(
74
+ environment.lunarIndexerUrl,
75
+ environment.chainId,
76
+ args,
77
+ );
78
+ return computeSharedLiquidityFromLunar(
79
+ data,
80
+ args.markets.map((market) => market.marketId),
81
+ marketParamsMap,
82
+ environment.chainId,
83
+ );
84
+ } catch (error) {
85
+ if (!args.signal?.aborted) {
86
+ environment.onError?.(error, {
87
+ source: "morpho-shared-liquidity",
88
+ chainId: environment.chainId,
89
+ });
90
+ }
91
+ throw error;
92
+ }
93
+ }
@@ -59,50 +59,56 @@ export async function getMorphoMarketUserPositionsData(params: {
59
59
  account: Address;
60
60
  markets?: string[] | undefined;
61
61
  }): Promise<MorphoMarketUserPosition[]> {
62
+ // A chain without a Morpho deployment has no positions to read. Both the
63
+ // plural action and the singular getMorphoMarketUserPosition come through
64
+ // here, so the guard lives at the source rather than in each caller.
65
+ const morphoViews = params.environment.contracts.morphoViews;
66
+ if (!morphoViews) {
67
+ return [];
68
+ }
69
+
62
70
  const markets = Object.values(params.environment.config.morphoMarkets).filter(
63
71
  (market) => (params.markets ? params.markets.includes(market.id) : true),
64
72
  );
65
73
 
66
- try {
67
- const userMarketPositions =
68
- await params.environment.contracts.morphoViews!.read.getMorphoBlueUserBalances(
69
- [markets.map((market) => market.id), params.account],
70
- );
71
-
72
- return markets.map((market, index) => {
73
- const position = userMarketPositions[index];
74
-
75
- const loanToken = params.environment.config.tokens[market.loanToken];
76
-
77
- const collateralToken =
78
- params.environment.config.tokens[market.collateralToken];
79
-
80
- const supplied = new Amount(
81
- position.collateralAssets,
82
- collateralToken.decimals,
83
- );
84
-
85
- const borrowed = new Amount(position.loanAssets, loanToken.decimals);
86
-
87
- const borrowedShares = new Amount(
88
- position.loanShares,
89
- loanToken.decimals + 6,
90
- );
91
-
92
- const result: MorphoMarketUserPosition = {
93
- chainId: params.environment.chainId,
94
- account: params.account,
95
- marketId: market.id,
96
- loanToken,
97
- collateralToken,
98
- supplied,
99
- borrowed,
100
- borrowedShares,
101
- };
102
-
103
- return result;
104
- });
105
- } catch (error) {
106
- return [];
107
- }
74
+ // A failed read must propagate: swallowing it here made a Base RPC outage
75
+ // look like "user has no isolated positions" (MOO-884).
76
+ const userMarketPositions = await morphoViews.read.getMorphoBlueUserBalances([
77
+ markets.map((market) => market.id),
78
+ params.account,
79
+ ]);
80
+
81
+ return markets.map((market, index) => {
82
+ const position = userMarketPositions[index];
83
+
84
+ const loanToken = params.environment.config.tokens[market.loanToken];
85
+
86
+ const collateralToken =
87
+ params.environment.config.tokens[market.collateralToken];
88
+
89
+ const supplied = new Amount(
90
+ position.collateralAssets,
91
+ collateralToken.decimals,
92
+ );
93
+
94
+ const borrowed = new Amount(position.loanAssets, loanToken.decimals);
95
+
96
+ const borrowedShares = new Amount(
97
+ position.loanShares,
98
+ loanToken.decimals + 6,
99
+ );
100
+
101
+ const result: MorphoMarketUserPosition = {
102
+ chainId: params.environment.chainId,
103
+ account: params.account,
104
+ marketId: market.id,
105
+ loanToken,
106
+ collateralToken,
107
+ supplied,
108
+ borrowed,
109
+ borrowedShares,
110
+ };
111
+
112
+ return result;
113
+ });
108
114
  }
@@ -1,6 +1,7 @@
1
1
  import type { Address } from "viem";
2
2
  import type { MoonwellClient } from "../../../client/createMoonwellClient.js";
3
3
  import { getEnvironmentsFromArgs } from "../../../common/index.js";
4
+ import { readAcrossEnvironments } from "../../../common/readAcrossEnvironments.js";
4
5
  import type { OptionalNetworkParameterType } from "../../../common/types.js";
5
6
  import type { Chain } from "../../../environments/index.js";
6
7
  import type { MorphoMarketUserPosition } from "../../../types/morphoUserPosition.js";
@@ -17,6 +18,11 @@ export type GetMorphoMarketUserPositionsReturnType = Promise<
17
18
  MorphoMarketUserPosition[]
18
19
  >;
19
20
 
21
+ /**
22
+ * Rejects with a `ChainReadError` when the read fails on any requested chain,
23
+ * so a failed RPC never looks like an empty position list. The error lists the
24
+ * failed chains and carries the positions from the chains that succeeded.
25
+ */
20
26
  export async function getMorphoMarketUserPositions<
21
27
  environments,
22
28
  Network extends Chain | undefined,
@@ -26,20 +32,13 @@ export async function getMorphoMarketUserPositions<
26
32
  ): GetMorphoMarketUserPositionsReturnType {
27
33
  const environments = getEnvironmentsFromArgs(client, args);
28
34
 
29
- const settled = await Promise.allSettled(
30
- environments
31
- .filter((environment) => environment.contracts.morphoViews !== undefined)
32
- .map((environment) => {
33
- return getMorphoMarketUserPositionsData({
34
- environment,
35
- account: args.userAddress,
36
- });
35
+ return readAcrossEnvironments({
36
+ environments,
37
+ source: "getMorphoMarketUserPositions",
38
+ read: (environment) =>
39
+ getMorphoMarketUserPositionsData({
40
+ environment,
41
+ account: args.userAddress,
37
42
  }),
38
- );
39
-
40
- const result = settled.flatMap((s) =>
41
- s.status === "fulfilled" ? s.value : [],
42
- );
43
-
44
- return result;
43
+ });
45
44
  }
@@ -1,6 +1,7 @@
1
1
  import type { Address, Chain } from "viem";
2
2
  import type { MoonwellClient } from "../../../client/createMoonwellClient.js";
3
3
  import { getEnvironmentsFromArgs } from "../../../common/index.js";
4
+ import { readAcrossEnvironments } from "../../../common/readAcrossEnvironments.js";
4
5
  import type { OptionalNetworkParameterType } from "../../../common/types.js";
5
6
  import type { MorphoVaultUserPosition } from "../../../types/morphoUserPosition.js";
6
7
  import { getMorphoVaultUserPositionsData } from "./common.js";
@@ -16,6 +17,11 @@ export type GetMorphoVaultUserPositionsReturnType = Promise<
16
17
  MorphoVaultUserPosition[]
17
18
  >;
18
19
 
20
+ /**
21
+ * Rejects with a `ChainReadError` when the read fails on any requested chain,
22
+ * so a failed RPC never looks like an empty position list. The error lists the
23
+ * failed chains and carries the positions from the chains that succeeded.
24
+ */
19
25
  export async function getMorphoVaultUserPositions<
20
26
  environments,
21
27
  Network extends Chain | undefined,
@@ -25,20 +31,13 @@ export async function getMorphoVaultUserPositions<
25
31
  ): GetMorphoVaultUserPositionsReturnType {
26
32
  const environments = getEnvironmentsFromArgs(client, args);
27
33
 
28
- const settled = await Promise.allSettled(
29
- environments
30
- .filter((environment) => environment.contracts.morphoViews !== undefined)
31
- .map((environment) => {
32
- return getMorphoVaultUserPositionsData({
33
- environment,
34
- account: args.userAddress,
35
- });
34
+ return readAcrossEnvironments({
35
+ environments,
36
+ source: "getMorphoVaultUserPositions",
37
+ read: (environment) =>
38
+ getMorphoVaultUserPositionsData({
39
+ environment,
40
+ account: args.userAddress,
36
41
  }),
37
- );
38
-
39
- const result = settled.flatMap((s) =>
40
- s.status === "fulfilled" ? s.value : [],
41
- );
42
-
43
- return result;
42
+ });
44
43
  }
@@ -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 { getGovernanceTokenPriceFor } from "../../governance/getWellPrice.js";
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
- getGovernanceTokenPriceFor(params.environment).catch((err) => {
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);
@@ -19,7 +19,7 @@ import type {
19
19
  MorphoVault,
20
20
  MorphoVaultMarket,
21
21
  } from "../../../types/morphoVault.js";
22
- import { getGovernanceTokenPriceFor } from "../../governance/getWellPrice.js";
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 vaultConfig = environment.config.vaults[vault.vaultKey];
310
- if (!vaultConfig?.multiReward) continue;
313
+ const multiReward =
314
+ environment.config.vaults[vault.vaultKey]?.multiReward;
315
+ if (!multiReward) continue;
311
316
 
312
- try {
313
- // Fetch market prices from the views contract to calculate rewards
314
- const homeEnvironment =
315
- (Object.values(publicEnvironments) as Environment[]).find((e) =>
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
- const [allMarkets, nativeTokenPriceRaw, governanceTokenPriceRaw] = data;
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
- const governanceTokenPrice = new Amount(governanceTokenPriceRaw, 18);
337
- const nativeTokenPrice = new Amount(nativeTokenPriceRaw ?? 0n, 18);
331
+ return [...allVaults, ...fallbackVaults];
332
+ }
338
333
 
339
- let tokenPrices =
340
- allMarkets
341
- ?.map((marketInfo) => {
342
- const marketFound = findMarketByAddress(
343
- environment,
344
- marketInfo.market,
345
- );
346
- if (marketFound) {
347
- return {
348
- token: marketFound.underlyingToken,
349
- tokenPrice: new Amount(
350
- marketInfo.underlyingPrice,
351
- 36 - marketFound.underlyingToken.decimals,
352
- ),
353
- };
354
- } else {
355
- return;
356
- }
357
- })
358
- .filter((token) => !!token) || [];
359
-
360
- // Add governance token to token prices
361
- if (environment.custom?.governance?.token) {
362
- tokenPrices = [
363
- ...tokenPrices,
364
- {
365
- token:
366
- environment.config.tokens[environment.custom.governance.token]!,
367
- tokenPrice: governanceTokenPrice,
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
- // Add native token to token prices
373
- tokenPrices = [
374
- ...tokenPrices,
375
- {
376
- token: findTokenByAddress(environment, zeroAddress)!,
377
- tokenPrice: nativeTokenPrice,
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
- const rewards = await getRewardsData(
382
- environment,
383
- vaultConfig.multiReward,
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
- const distributorTotalSupply = await getTotalSupplyData(
387
- environment,
388
- vaultConfig.multiReward,
389
- );
384
+ return tokenPrices;
385
+ }
390
386
 
391
- rewards
392
- .filter(
393
- (reward) =>
394
- reward?.periodFinish &&
395
- dayjs.utc().isBefore(dayjs.unix(Number(reward.periodFinish))),
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
- const market = tokenPrices.find(
404
- (m) => m?.token.address === reward.token,
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
- const rewardPriceUsd = market?.tokenPrice.value ?? 0;
408
-
409
- const rewardsPerYear =
410
- new Amount(reward.rewardRate, market?.token.decimals ?? 18)
411
- .value *
412
- SECONDS_PER_YEAR *
413
- rewardPriceUsd;
414
-
415
- vault.stakingRewards.push({
416
- apr:
417
- (rewardsPerYear /
418
- (new Amount(distributorTotalSupply, vault.vaultToken.decimals)
419
- .value *
420
- vault.underlyingPrice)) *
421
- 100,
422
- token: token,
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
- vault.stakingRewardsApr = vault.stakingRewards.reduce(
427
- (acc, curr) => acc + curr.apr,
428
- 0,
429
- );
430
- vault.totalStakingApr = vault.stakingRewardsApr + vault.baseApy;
431
- } catch (error) {
432
- console.warn(
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
- return [...allVaults, ...fallbackVaults];
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
- getGovernanceTokenPriceFor(environment).catch((err) => {
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;