@moonwell-fi/moonwell-sdk 0.23.0 → 0.24.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 (144) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/_cjs/actions/axiosWithRetry.js +10 -11
  3. package/_cjs/actions/axiosWithRetry.js.map +1 -1
  4. package/_cjs/actions/core/markets/common.js +30 -35
  5. package/_cjs/actions/core/markets/common.js.map +1 -1
  6. package/_cjs/actions/core/markets/getMarkets.js +7 -3
  7. package/_cjs/actions/core/markets/getMarkets.js.map +1 -1
  8. package/_cjs/actions/core/user-positions/common.js +56 -60
  9. package/_cjs/actions/core/user-positions/common.js.map +1 -1
  10. package/_cjs/actions/core/user-positions/getUserPositions.js +9 -6
  11. package/_cjs/actions/core/user-positions/getUserPositions.js.map +1 -1
  12. package/_cjs/actions/governance/proposals/common.js +0 -5
  13. package/_cjs/actions/governance/proposals/common.js.map +1 -1
  14. package/_cjs/actions/index.js +3 -1
  15. package/_cjs/actions/index.js.map +1 -1
  16. package/_cjs/actions/morpho/markets/common.js +128 -26
  17. package/_cjs/actions/morpho/markets/common.js.map +1 -1
  18. package/_cjs/actions/morpho/markets/getMorphoMarket.js +1 -0
  19. package/_cjs/actions/morpho/markets/getMorphoMarket.js.map +1 -1
  20. package/_cjs/actions/morpho/markets/getMorphoMarkets.js +1 -0
  21. package/_cjs/actions/morpho/markets/getMorphoMarkets.js.map +1 -1
  22. package/_cjs/actions/morpho/markets/getMorphoMarketsSharedLiquidity.js +46 -0
  23. package/_cjs/actions/morpho/markets/getMorphoMarketsSharedLiquidity.js.map +1 -0
  24. package/_cjs/actions/morpho/user-positions/common.js +26 -24
  25. package/_cjs/actions/morpho/user-positions/common.js.map +1 -1
  26. package/_cjs/actions/morpho/user-positions/getMorphoMarketUserPositions.js +7 -8
  27. package/_cjs/actions/morpho/user-positions/getMorphoMarketUserPositions.js.map +1 -1
  28. package/_cjs/actions/morpho/user-positions/getMorphoVaultUserPositions.js +7 -8
  29. package/_cjs/actions/morpho/user-positions/getMorphoVaultUserPositions.js.map +1 -1
  30. package/_cjs/actions/retry.js +43 -13
  31. package/_cjs/actions/retry.js.map +1 -1
  32. package/_cjs/client/createActions.js +2 -0
  33. package/_cjs/client/createActions.js.map +1 -1
  34. package/_cjs/common/error.js +37 -1
  35. package/_cjs/common/error.js.map +1 -1
  36. package/_cjs/common/index.js +2 -1
  37. package/_cjs/common/index.js.map +1 -1
  38. package/_cjs/common/readAcrossEnvironments.js +17 -0
  39. package/_cjs/common/readAcrossEnvironments.js.map +1 -0
  40. package/_cjs/errors/version.js +1 -1
  41. package/_cjs/index.js +3 -1
  42. package/_cjs/index.js.map +1 -1
  43. package/_esm/actions/axiosWithRetry.js +11 -13
  44. package/_esm/actions/axiosWithRetry.js.map +1 -1
  45. package/_esm/actions/core/markets/common.js +34 -35
  46. package/_esm/actions/core/markets/common.js.map +1 -1
  47. package/_esm/actions/core/markets/getMarkets.js +7 -3
  48. package/_esm/actions/core/markets/getMarkets.js.map +1 -1
  49. package/_esm/actions/core/user-positions/common.js +65 -63
  50. package/_esm/actions/core/user-positions/common.js.map +1 -1
  51. package/_esm/actions/core/user-positions/getUserPositions.js +14 -6
  52. package/_esm/actions/core/user-positions/getUserPositions.js.map +1 -1
  53. package/_esm/actions/governance/proposals/common.js +0 -2
  54. package/_esm/actions/governance/proposals/common.js.map +1 -1
  55. package/_esm/actions/index.js +1 -0
  56. package/_esm/actions/index.js.map +1 -1
  57. package/_esm/actions/morpho/markets/common.js +136 -27
  58. package/_esm/actions/morpho/markets/common.js.map +1 -1
  59. package/_esm/actions/morpho/markets/getMorphoMarket.js +1 -0
  60. package/_esm/actions/morpho/markets/getMorphoMarket.js.map +1 -1
  61. package/_esm/actions/morpho/markets/getMorphoMarkets.js +1 -0
  62. package/_esm/actions/morpho/markets/getMorphoMarkets.js.map +1 -1
  63. package/_esm/actions/morpho/markets/getMorphoMarketsSharedLiquidity.js +48 -0
  64. package/_esm/actions/morpho/markets/getMorphoMarketsSharedLiquidity.js.map +1 -0
  65. package/_esm/actions/morpho/user-positions/common.js +31 -24
  66. package/_esm/actions/morpho/user-positions/common.js.map +1 -1
  67. package/_esm/actions/morpho/user-positions/getMorphoMarketUserPositions.js +12 -8
  68. package/_esm/actions/morpho/user-positions/getMorphoMarketUserPositions.js.map +1 -1
  69. package/_esm/actions/morpho/user-positions/getMorphoVaultUserPositions.js +12 -8
  70. package/_esm/actions/morpho/user-positions/getMorphoVaultUserPositions.js.map +1 -1
  71. package/_esm/actions/retry.js +45 -15
  72. package/_esm/actions/retry.js.map +1 -1
  73. package/_esm/client/createActions.js +2 -0
  74. package/_esm/client/createActions.js.map +1 -1
  75. package/_esm/common/error.js +49 -0
  76. package/_esm/common/error.js.map +1 -1
  77. package/_esm/common/index.js +1 -1
  78. package/_esm/common/index.js.map +1 -1
  79. package/_esm/common/readAcrossEnvironments.js +30 -0
  80. package/_esm/common/readAcrossEnvironments.js.map +1 -0
  81. package/_esm/errors/version.js +1 -1
  82. package/_esm/index.js +1 -0
  83. package/_esm/index.js.map +1 -1
  84. package/_types/actions/axiosWithRetry.d.ts +1 -0
  85. package/_types/actions/axiosWithRetry.d.ts.map +1 -1
  86. package/_types/actions/core/markets/common.d.ts.map +1 -1
  87. package/_types/actions/core/markets/getMarkets.d.ts.map +1 -1
  88. package/_types/actions/core/user-positions/common.d.ts.map +1 -1
  89. package/_types/actions/core/user-positions/getUserPositions.d.ts +5 -0
  90. package/_types/actions/core/user-positions/getUserPositions.d.ts.map +1 -1
  91. package/_types/actions/governance/proposals/common.d.ts.map +1 -1
  92. package/_types/actions/index.d.ts +1 -0
  93. package/_types/actions/index.d.ts.map +1 -1
  94. package/_types/actions/morpho/markets/common.d.ts +7 -10
  95. package/_types/actions/morpho/markets/common.d.ts.map +1 -1
  96. package/_types/actions/morpho/markets/getMorphoMarket.d.ts +2 -0
  97. package/_types/actions/morpho/markets/getMorphoMarket.d.ts.map +1 -1
  98. package/_types/actions/morpho/markets/getMorphoMarkets.d.ts +2 -0
  99. package/_types/actions/morpho/markets/getMorphoMarkets.d.ts.map +1 -1
  100. package/_types/actions/morpho/markets/getMorphoMarketsSharedLiquidity.d.ts +17 -0
  101. package/_types/actions/morpho/markets/getMorphoMarketsSharedLiquidity.d.ts.map +1 -0
  102. package/_types/actions/morpho/user-positions/common.d.ts.map +1 -1
  103. package/_types/actions/morpho/user-positions/getMorphoMarketUserPositions.d.ts +5 -0
  104. package/_types/actions/morpho/user-positions/getMorphoMarketUserPositions.d.ts.map +1 -1
  105. package/_types/actions/morpho/user-positions/getMorphoVaultUserPositions.d.ts +5 -0
  106. package/_types/actions/morpho/user-positions/getMorphoVaultUserPositions.d.ts.map +1 -1
  107. package/_types/actions/retry.d.ts +5 -4
  108. package/_types/actions/retry.d.ts.map +1 -1
  109. package/_types/client/createActions.d.ts +2 -0
  110. package/_types/client/createActions.d.ts.map +1 -1
  111. package/_types/common/error.d.ts +31 -0
  112. package/_types/common/error.d.ts.map +1 -1
  113. package/_types/common/index.d.ts +2 -2
  114. package/_types/common/index.d.ts.map +1 -1
  115. package/_types/common/readAcrossEnvironments.d.ts +23 -0
  116. package/_types/common/readAcrossEnvironments.d.ts.map +1 -0
  117. package/_types/errors/version.d.ts +1 -1
  118. package/_types/index.d.ts +3 -1
  119. package/_types/index.d.ts.map +1 -1
  120. package/_types/types/morphoMarket.d.ts +9 -0
  121. package/_types/types/morphoMarket.d.ts.map +1 -1
  122. package/actions/axiosWithRetry.ts +19 -13
  123. package/actions/core/markets/common.ts +44 -43
  124. package/actions/core/markets/getMarkets.ts +9 -5
  125. package/actions/core/user-positions/common.ts +107 -111
  126. package/actions/core/user-positions/getUserPositions.ts +11 -10
  127. package/actions/governance/proposals/common.ts +0 -3
  128. package/actions/index.ts +5 -0
  129. package/actions/morpho/markets/common.ts +289 -125
  130. package/actions/morpho/markets/getMorphoMarket.ts +3 -0
  131. package/actions/morpho/markets/getMorphoMarkets.ts +3 -0
  132. package/actions/morpho/markets/getMorphoMarketsSharedLiquidity.ts +93 -0
  133. package/actions/morpho/user-positions/common.ts +48 -42
  134. package/actions/morpho/user-positions/getMorphoMarketUserPositions.ts +14 -15
  135. package/actions/morpho/user-positions/getMorphoVaultUserPositions.ts +14 -15
  136. package/actions/retry.ts +53 -18
  137. package/client/createActions.ts +13 -0
  138. package/common/error.ts +45 -0
  139. package/common/index.ts +2 -2
  140. package/common/readAcrossEnvironments.ts +45 -0
  141. package/errors/version.ts +1 -1
  142. package/index.ts +3 -0
  143. package/package.json +1 -1
  144. 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
  }
package/actions/retry.ts CHANGED
@@ -16,8 +16,8 @@
16
16
  * where the existing `environment.onError(...)` wiring picks it up
17
17
  */
18
18
  import axios, {
19
- type AxiosError,
20
19
  type AxiosInstance,
20
+ type GenericAbortSignal,
21
21
  type InternalAxiosRequestConfig,
22
22
  } from "axios";
23
23
 
@@ -25,14 +25,30 @@ export interface RetryOptions {
25
25
  maxAttempts?: number;
26
26
  initialDelay?: number;
27
27
  maxDelay?: number;
28
+ signal?: GenericAbortSignal | undefined;
28
29
  }
29
30
 
30
31
  const DEFAULT_MAX_ATTEMPTS = 3;
31
32
  const DEFAULT_INITIAL_DELAY_MS = 250;
32
33
  const DEFAULT_MAX_DELAY_MS = 5_000;
33
34
 
34
- async function sleep(ms: number): Promise<void> {
35
- return new Promise((resolve) => setTimeout(resolve, ms));
35
+ async function sleep(ms: number, signal?: GenericAbortSignal): Promise<void> {
36
+ return new Promise((resolve, reject) => {
37
+ if (signal?.aborted) {
38
+ reject(new axios.CanceledError());
39
+ return;
40
+ }
41
+ const onAbort = (): void => {
42
+ clearTimeout(timer);
43
+ signal?.removeEventListener?.("abort", onAbort);
44
+ reject(new axios.CanceledError());
45
+ };
46
+ const timer = setTimeout(() => {
47
+ signal?.removeEventListener?.("abort", onAbort);
48
+ resolve();
49
+ }, ms);
50
+ signal?.addEventListener?.("abort", onAbort, { once: true });
51
+ });
36
52
  }
37
53
 
38
54
  function backoffDelay(
@@ -51,6 +67,7 @@ function backoffDelay(
51
67
  * - Non-axios errors (parse errors, code bugs) → do NOT retry
52
68
  */
53
69
  export function isRetriableError(error: unknown): boolean {
70
+ if (axios.isCancel(error)) return false;
54
71
  if (!axios.isAxiosError(error)) return false;
55
72
  if (!error.response) return true;
56
73
  return error.response.status >= 500;
@@ -59,33 +76,49 @@ export function isRetriableError(error: unknown): boolean {
59
76
  /**
60
77
  * Attach a retry-on-failure interceptor to an axios instance. Every request
61
78
  * made through this instance is retried up to `maxAttempts` times when the
62
- * failure is retriable. Per-config state is tracked on a WeakMap so we don't
63
- * pollute the public axios config shape.
79
+ * failure is retriable. Axios merges into a new config on every request, so
80
+ * the attempt counter must travel with that config rather than its identity.
64
81
  */
65
82
  export function attachRetryInterceptor(
66
83
  instance: AxiosInstance,
67
- options: RetryOptions = {},
84
+ options: Omit<RetryOptions, "signal"> = {},
68
85
  ): void {
69
86
  const {
70
87
  maxAttempts = DEFAULT_MAX_ATTEMPTS,
71
88
  initialDelay = DEFAULT_INITIAL_DELAY_MS,
72
89
  maxDelay = DEFAULT_MAX_DELAY_MS,
73
90
  } = options;
74
- const attemptsByConfig = new WeakMap<InternalAxiosRequestConfig, number>();
91
+ interface RetryConfig extends InternalAxiosRequestConfig {
92
+ moonwellRetryAttempts?: number | undefined;
93
+ }
75
94
 
76
95
  instance.interceptors.response.use(
77
- (response) => response,
78
- async (error: AxiosError) => {
79
- const config = error.config;
96
+ (response) => {
97
+ const config: RetryConfig = response.config;
98
+ config.moonwellRetryAttempts = undefined;
99
+ return response;
100
+ },
101
+ async (error: unknown) => {
102
+ if (!axios.isAxiosError(error)) throw error;
103
+ const config: RetryConfig | undefined = error.config;
80
104
  if (!config) throw error;
81
- if (!isRetriableError(error)) throw error;
82
-
83
- const previousAttempts = attemptsByConfig.get(config) ?? 0;
84
- if (previousAttempts >= maxAttempts - 1) throw error;
105
+ const previousAttempts = config.moonwellRetryAttempts ?? 0;
106
+ if (!isRetriableError(error) || previousAttempts >= maxAttempts - 1) {
107
+ config.moonwellRetryAttempts = undefined;
108
+ throw error;
109
+ }
85
110
 
86
- attemptsByConfig.set(config, previousAttempts + 1);
87
- await sleep(backoffDelay(previousAttempts, initialDelay, maxDelay));
88
- return instance.request(config);
111
+ config.moonwellRetryAttempts = previousAttempts + 1;
112
+ try {
113
+ await sleep(
114
+ backoffDelay(previousAttempts, initialDelay, maxDelay),
115
+ config.signal,
116
+ );
117
+ return await instance.request(config);
118
+ } catch (retryError) {
119
+ config.moonwellRetryAttempts = undefined;
120
+ throw retryError;
121
+ }
89
122
  },
90
123
  );
91
124
  }
@@ -103,10 +136,12 @@ export async function retry<T>(
103
136
  maxAttempts = DEFAULT_MAX_ATTEMPTS,
104
137
  initialDelay = DEFAULT_INITIAL_DELAY_MS,
105
138
  maxDelay = DEFAULT_MAX_DELAY_MS,
139
+ signal,
106
140
  } = options;
107
141
  let attempt = 0;
108
142
  let lastError: unknown;
109
143
  while (attempt < maxAttempts) {
144
+ if (signal?.aborted) throw new axios.CanceledError();
110
145
  try {
111
146
  return await fn();
112
147
  } catch (error) {
@@ -114,7 +149,7 @@ export async function retry<T>(
114
149
  attempt++;
115
150
  if (!isRetriableError(error)) throw error;
116
151
  if (attempt >= maxAttempts) break;
117
- await sleep(backoffDelay(attempt - 1, initialDelay, maxDelay));
152
+ await sleep(backoffDelay(attempt - 1, initialDelay, maxDelay), signal);
118
153
  }
119
154
  }
120
155
  throw lastError;
@@ -143,6 +143,11 @@ import {
143
143
  type GetMorphoMarketsReturnType,
144
144
  getMorphoMarkets,
145
145
  } from "../actions/morpho/markets/getMorphoMarkets.js";
146
+ import {
147
+ type GetMorphoMarketsSharedLiquidityParameters,
148
+ type GetMorphoMarketsSharedLiquidityReturnType,
149
+ getMorphoMarketsSharedLiquidity,
150
+ } from "../actions/morpho/markets/getMorphoMarketsSharedLiquidity.js";
146
151
  import {
147
152
  type GetMorphoMarketUserPositionParameters,
148
153
  type GetMorphoMarketUserPositionReturnType,
@@ -288,6 +293,12 @@ export type Actions<
288
293
  args?: GetMorphoMarketsParameters<environments, chain>,
289
294
  ) => GetMorphoMarketsReturnType;
290
295
 
296
+ getMorphoMarketsSharedLiquidity: <
297
+ chain extends Chain | undefined = Chain | undefined,
298
+ >(
299
+ args: GetMorphoMarketsSharedLiquidityParameters<environments, chain>,
300
+ ) => GetMorphoMarketsSharedLiquidityReturnType;
301
+
291
302
  getMorphoMarketUserPosition: <
292
303
  chain extends Chain | undefined = Chain | undefined,
293
304
  >(
@@ -399,6 +410,8 @@ export const actions = <
399
410
  getUserVotingPowers: (args) => getUserVotingPowers(client, args),
400
411
  getMorphoMarket: (args) => getMorphoMarket(client, args),
401
412
  getMorphoMarkets: (args) => getMorphoMarkets(client, args),
413
+ getMorphoMarketsSharedLiquidity: (args) =>
414
+ getMorphoMarketsSharedLiquidity(client, args),
402
415
  getMorphoMarketUserPosition: (args) =>
403
416
  getMorphoMarketUserPosition(client, args),
404
417
  getMorphoMarketUserPositions: (args) =>
package/common/error.ts CHANGED
@@ -28,3 +28,48 @@ export class HttpRequestError extends BaseError {
28
28
  Object.setPrototypeOf(this, HttpRequestError.prototype);
29
29
  }
30
30
  }
31
+
32
+ /** One failed per-chain read inside a multi-chain action. */
33
+ export type ChainReadFailure = {
34
+ chainId: number;
35
+ /** The original error from the RPC / contract read, unchanged. */
36
+ reason: unknown;
37
+ };
38
+
39
+ /**
40
+ * Thrown by multi-chain read actions (`getUserPositions`,
41
+ * `getMorphoMarketUserPositions`, `getMorphoVaultUserPositions`) when the read
42
+ * fails on one or more of the requested chains.
43
+ *
44
+ * `failures` lists every failed chain with its original error and `data`
45
+ * carries the results from the chains that succeeded, so a consumer can keep
46
+ * rendering the healthy chains while flagging the broken one. `errors` (from
47
+ * `AggregateError`) holds the same original errors in `failures` order.
48
+ *
49
+ * These failures are surfaced only through this rejection, not additionally
50
+ * through `onError`: a rejection is the consumer's to report, and reporting it
51
+ * on both paths doubled telemetry under query-library retries.
52
+ */
53
+ export class ChainReadError<T = unknown> extends AggregateError {
54
+ override readonly name = "ChainReadError";
55
+ readonly source: string;
56
+ readonly failures: ChainReadFailure[];
57
+ readonly data: T[];
58
+
59
+ constructor(params: {
60
+ source: string;
61
+ failures: ChainReadFailure[];
62
+ data: T[];
63
+ }) {
64
+ const chainIds = params.failures
65
+ .map((failure) => failure.chainId)
66
+ .join(", ");
67
+ super(
68
+ params.failures.map((failure) => failure.reason),
69
+ `[moonwell-sdk] ${params.source}: read failed on chainIds ${chainIds}`,
70
+ );
71
+ this.source = params.source;
72
+ this.failures = params.failures;
73
+ this.data = params.data;
74
+ }
75
+ }
package/common/index.ts CHANGED
@@ -5,8 +5,8 @@ import type { Environment } from "../environments/index.js";
5
5
 
6
6
  dayjs.extend(utc);
7
7
  export { Amount } from "./amount.js";
8
- export { BaseError, HttpRequestError } from "./error.js";
9
- export type { HttpRequestErrorType } from "./error.js";
8
+ export { BaseError, ChainReadError, HttpRequestError } from "./error.js";
9
+ export type { ChainReadFailure, HttpRequestErrorType } from "./error.js";
10
10
  export { getBlockNumberAtTimestamp } from "./getBlockNumberAtTimestamp.js";
11
11
  export type { MultichainReturnType } from "./types.js";
12
12
 
@@ -0,0 +1,45 @@
1
+ import type { Environment } from "../environments/index.js";
2
+ import { ChainReadError } from "./error.js";
3
+
4
+ /**
5
+ * Run a per-environment read on every environment and flatten the results.
6
+ *
7
+ * Unlike a bare `Promise.allSettled` that keeps only fulfilled entries, a
8
+ * rejected environment is never dropped silently: if any environment fails,
9
+ * the call rejects with a `ChainReadError` that lists every failed chain with
10
+ * its original error and carries the results of the chains that succeeded.
11
+ * For user positions a silently missing chain is indistinguishable from "no
12
+ * position", which drives wrong balances and borrow limits downstream; the
13
+ * partial `data` on the error lets a consumer keep rendering the healthy
14
+ * chains while flagging the broken one.
15
+ *
16
+ * Failures are surfaced only through the rejection, not additionally through
17
+ * `environment.onError`, so a consumer that retries the call is not told about
18
+ * the same outage once per attempt per chain.
19
+ */
20
+ export async function readAcrossEnvironments<T>(params: {
21
+ environments: Environment[];
22
+ source: string;
23
+ read: (environment: Environment) => Promise<T[]>;
24
+ }): Promise<T[]> {
25
+ const { environments, source, read } = params;
26
+
27
+ const settled = await Promise.allSettled(
28
+ environments.map((environment) => read(environment)),
29
+ );
30
+
31
+ const data = settled.flatMap((result) =>
32
+ result.status === "fulfilled" ? result.value : [],
33
+ );
34
+ const failures = settled.flatMap((result, index) =>
35
+ result.status === "rejected"
36
+ ? [{ chainId: environments[index].chainId, reason: result.reason }]
37
+ : [],
38
+ );
39
+
40
+ if (failures.length > 0) {
41
+ throw new ChainReadError({ source, failures, data });
42
+ }
43
+
44
+ return data;
45
+ }
package/errors/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '0.23.0'
1
+ export const version = '0.24.0'
package/index.ts CHANGED
@@ -5,6 +5,8 @@ export {
5
5
 
6
6
  export { MerklApiError } from "./actions/morpho/user-rewards/common.js";
7
7
  export { MorphoUserRewardsAggregateError } from "./actions/morpho/user-rewards/getMorphoUserRewards.js";
8
+ export { ChainReadError } from "./common/error.js";
9
+ export type { ChainReadFailure } from "./common/error.js";
8
10
 
9
11
  export type {
10
12
  MarketConfig,
@@ -18,6 +20,7 @@ export type { Discussion } from "./types/discussion.js";
18
20
  export type { Market, MarketReward, MarketSnapshot } from "./types/market.js";
19
21
  export type {
20
22
  MorphoMarket,
23
+ MorphoMarketSharedLiquidity,
21
24
  MorphoMarketParamsType,
22
25
  PublicAllocatorSharedLiquidityType,
23
26
  } from "./types/morphoMarket.js";
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.23.0",
4
+ "version": "0.24.0",
5
5
  "main": "./_cjs/index.js",
6
6
  "module": "./_esm/index.js",
7
7
  "types": "./_types/index.d.ts",
@@ -41,6 +41,16 @@ export type MorphoMarket = {
41
41
  totalBorrowApr: number;
42
42
  rewards: Required<MorphoReward>[];
43
43
  publicAllocatorSharedLiquidity: PublicAllocatorSharedLiquidityType[];
44
+ /** Whether allocator liquidity was fetched. An empty list is not proof of zero
45
+ * reallocatable liquidity when this is "not-requested" or "unavailable". */
46
+ sharedLiquidityStatus?: "available" | "unavailable" | "not-requested";
47
+ };
48
+
49
+ export type MorphoMarketSharedLiquidity = {
50
+ chainId: number;
51
+ marketId: string;
52
+ reallocatableLiquidityAssets: Amount;
53
+ publicAllocatorSharedLiquidity: PublicAllocatorSharedLiquidityType[];
44
54
  };
45
55
 
46
56
  export type MorphoMarketParamsType = {