@moonwell-fi/moonwell-sdk 0.12.0 → 0.12.2

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 +14 -0
  2. package/_cjs/actions/axiosWithRetry.js +25 -0
  3. package/_cjs/actions/axiosWithRetry.js.map +1 -0
  4. package/_cjs/actions/beam/getBeamTokenLimits.js +2 -5
  5. package/_cjs/actions/beam/getBeamTokenLimits.js.map +1 -1
  6. package/_cjs/actions/beam/getBeamTokenRoutes.js +3 -6
  7. package/_cjs/actions/beam/getBeamTokenRoutes.js.map +1 -1
  8. package/_cjs/actions/core/markets/getMarketSnapshots.js +2 -5
  9. package/_cjs/actions/core/markets/getMarketSnapshots.js.map +1 -1
  10. package/_cjs/actions/core/user-positions/getUserPositionSnapshots.js +2 -5
  11. package/_cjs/actions/core/user-positions/getUserPositionSnapshots.js.map +1 -1
  12. package/_cjs/actions/governance/getCirculatingSupplySnapshots.js +3 -6
  13. package/_cjs/actions/governance/getCirculatingSupplySnapshots.js.map +1 -1
  14. package/_cjs/actions/governance/getDelegates.js +2 -5
  15. package/_cjs/actions/governance/getDelegates.js.map +1 -1
  16. package/_cjs/actions/governance/getDiscussions.js +3 -3
  17. package/_cjs/actions/governance/getDiscussions.js.map +1 -1
  18. package/_cjs/actions/governance/getStakingSnapshots.js +2 -5
  19. package/_cjs/actions/governance/getStakingSnapshots.js.map +1 -1
  20. package/_cjs/actions/governance/getUserVotingPowers.js +12 -4
  21. package/_cjs/actions/governance/getUserVotingPowers.js.map +1 -1
  22. package/_cjs/actions/governance/governor-api-client.js +10 -13
  23. package/_cjs/actions/governance/governor-api-client.js.map +1 -1
  24. package/_cjs/actions/governance/proposals/common.js +32 -4
  25. package/_cjs/actions/governance/proposals/common.js.map +1 -1
  26. package/_cjs/actions/governance/snapshot/common.js +2 -5
  27. package/_cjs/actions/governance/snapshot/common.js.map +1 -1
  28. package/_cjs/actions/lunar-indexer-client.js +4 -0
  29. package/_cjs/actions/lunar-indexer-client.js.map +1 -1
  30. package/_cjs/actions/morpho/markets/common.js +2 -5
  31. package/_cjs/actions/morpho/markets/common.js.map +1 -1
  32. package/_cjs/actions/morpho/markets/lunarIndexerTransform.js +5 -8
  33. package/_cjs/actions/morpho/markets/lunarIndexerTransform.js.map +1 -1
  34. package/_cjs/actions/morpho/user-rewards/common.js +21 -193
  35. package/_cjs/actions/morpho/user-rewards/common.js.map +1 -1
  36. package/_cjs/actions/retry.js +63 -0
  37. package/_cjs/actions/retry.js.map +1 -0
  38. package/_cjs/common/getBlockNumberAtTimestamp.js +42 -0
  39. package/_cjs/common/getBlockNumberAtTimestamp.js.map +1 -0
  40. package/_cjs/common/index.js +3 -1
  41. package/_cjs/common/index.js.map +1 -1
  42. package/_cjs/environments/types/config.js.map +1 -1
  43. package/_cjs/errors/version.js +1 -1
  44. package/_esm/actions/axiosWithRetry.js +32 -0
  45. package/_esm/actions/axiosWithRetry.js.map +1 -0
  46. package/_esm/actions/beam/getBeamTokenLimits.js +2 -2
  47. package/_esm/actions/beam/getBeamTokenLimits.js.map +1 -1
  48. package/_esm/actions/beam/getBeamTokenRoutes.js +3 -3
  49. package/_esm/actions/beam/getBeamTokenRoutes.js.map +1 -1
  50. package/_esm/actions/core/markets/getMarketSnapshots.js +2 -2
  51. package/_esm/actions/core/markets/getMarketSnapshots.js.map +1 -1
  52. package/_esm/actions/core/user-positions/getUserPositionSnapshots.js +2 -2
  53. package/_esm/actions/core/user-positions/getUserPositionSnapshots.js.map +1 -1
  54. package/_esm/actions/governance/getCirculatingSupplySnapshots.js +3 -3
  55. package/_esm/actions/governance/getCirculatingSupplySnapshots.js.map +1 -1
  56. package/_esm/actions/governance/getDelegates.js +2 -2
  57. package/_esm/actions/governance/getDelegates.js.map +1 -1
  58. package/_esm/actions/governance/getDiscussions.js +3 -3
  59. package/_esm/actions/governance/getDiscussions.js.map +1 -1
  60. package/_esm/actions/governance/getStakingSnapshots.js +2 -2
  61. package/_esm/actions/governance/getStakingSnapshots.js.map +1 -1
  62. package/_esm/actions/governance/getUserVotingPowers.js +13 -5
  63. package/_esm/actions/governance/getUserVotingPowers.js.map +1 -1
  64. package/_esm/actions/governance/governor-api-client.js +10 -10
  65. package/_esm/actions/governance/governor-api-client.js.map +1 -1
  66. package/_esm/actions/governance/proposals/common.js +45 -3
  67. package/_esm/actions/governance/proposals/common.js.map +1 -1
  68. package/_esm/actions/governance/snapshot/common.js +2 -2
  69. package/_esm/actions/governance/snapshot/common.js.map +1 -1
  70. package/_esm/actions/lunar-indexer-client.js +6 -0
  71. package/_esm/actions/lunar-indexer-client.js.map +1 -1
  72. package/_esm/actions/morpho/markets/common.js +2 -2
  73. package/_esm/actions/morpho/markets/common.js.map +1 -1
  74. package/_esm/actions/morpho/markets/lunarIndexerTransform.js +5 -5
  75. package/_esm/actions/morpho/markets/lunarIndexerTransform.js.map +1 -1
  76. package/_esm/actions/morpho/user-rewards/common.js +28 -195
  77. package/_esm/actions/morpho/user-rewards/common.js.map +1 -1
  78. package/_esm/actions/retry.js +90 -0
  79. package/_esm/actions/retry.js.map +1 -0
  80. package/_esm/common/getBlockNumberAtTimestamp.js +56 -0
  81. package/_esm/common/getBlockNumberAtTimestamp.js.map +1 -0
  82. package/_esm/common/index.js +1 -0
  83. package/_esm/common/index.js.map +1 -1
  84. package/_esm/environments/types/config.js.map +1 -1
  85. package/_esm/errors/version.js +1 -1
  86. package/_types/actions/axiosWithRetry.d.ts +16 -0
  87. package/_types/actions/axiosWithRetry.d.ts.map +1 -0
  88. package/_types/actions/beam/getBeamTokenLimits.d.ts.map +1 -1
  89. package/_types/actions/beam/getBeamTokenRoutes.d.ts.map +1 -1
  90. package/_types/actions/core/markets/getMarketSnapshots.d.ts.map +1 -1
  91. package/_types/actions/core/user-positions/getUserPositionSnapshots.d.ts.map +1 -1
  92. package/_types/actions/governance/getCirculatingSupplySnapshots.d.ts.map +1 -1
  93. package/_types/actions/governance/getDelegates.d.ts.map +1 -1
  94. package/_types/actions/governance/getDiscussions.d.ts.map +1 -1
  95. package/_types/actions/governance/getStakingSnapshots.d.ts.map +1 -1
  96. package/_types/actions/governance/getUserVotingPowers.d.ts +13 -1
  97. package/_types/actions/governance/getUserVotingPowers.d.ts.map +1 -1
  98. package/_types/actions/governance/governor-api-client.d.ts.map +1 -1
  99. package/_types/actions/governance/proposals/common.d.ts +15 -0
  100. package/_types/actions/governance/proposals/common.d.ts.map +1 -1
  101. package/_types/actions/governance/snapshot/common.d.ts.map +1 -1
  102. package/_types/actions/lunar-indexer-client.d.ts.map +1 -1
  103. package/_types/actions/morpho/markets/common.d.ts.map +1 -1
  104. package/_types/actions/morpho/markets/lunarIndexerTransform.d.ts.map +1 -1
  105. package/_types/actions/morpho/user-rewards/common.d.ts.map +1 -1
  106. package/_types/actions/retry.d.ts +45 -0
  107. package/_types/actions/retry.d.ts.map +1 -0
  108. package/_types/client/createMoonwellClient.d.ts +2 -2
  109. package/_types/common/getBlockNumberAtTimestamp.d.ts +15 -0
  110. package/_types/common/getBlockNumberAtTimestamp.d.ts.map +1 -0
  111. package/_types/common/index.d.ts +1 -0
  112. package/_types/common/index.d.ts.map +1 -1
  113. package/_types/environments/definitions/base/environment.d.ts +21 -3
  114. package/_types/environments/definitions/base/environment.d.ts.map +1 -1
  115. package/_types/environments/definitions/moonbeam/custom.d.ts +1 -1
  116. package/_types/environments/definitions/moonbeam/environment.d.ts +1 -1
  117. package/_types/environments/index.d.ts +119 -20
  118. package/_types/environments/index.d.ts.map +1 -1
  119. package/_types/environments/types/config.d.ts +27 -41
  120. package/_types/environments/types/config.d.ts.map +1 -1
  121. package/_types/errors/version.d.ts +1 -1
  122. package/actions/axiosWithRetry.ts +42 -0
  123. package/actions/beam/getBeamTokenLimits.ts +2 -2
  124. package/actions/beam/getBeamTokenRoutes.ts +3 -3
  125. package/actions/core/markets/getMarketSnapshots.ts +2 -2
  126. package/actions/core/user-positions/getUserPositionSnapshots.ts +2 -2
  127. package/actions/governance/getCirculatingSupplySnapshots.ts +3 -3
  128. package/actions/governance/getDelegates.ts +2 -2
  129. package/actions/governance/getDiscussions.ts +6 -5
  130. package/actions/governance/getStakingSnapshots.ts +2 -2
  131. package/actions/governance/getUserVotingPowers.ts +43 -8
  132. package/actions/governance/governor-api-client.ts +12 -10
  133. package/actions/governance/proposals/common.ts +58 -3
  134. package/actions/governance/snapshot/common.ts +2 -2
  135. package/actions/lunar-indexer-client.ts +8 -0
  136. package/actions/morpho/markets/common.ts +2 -2
  137. package/actions/morpho/markets/lunarIndexerTransform.ts +6 -5
  138. package/actions/morpho/user-rewards/common.ts +52 -344
  139. package/actions/retry.ts +121 -0
  140. package/common/getBlockNumberAtTimestamp.ts +59 -0
  141. package/common/index.ts +1 -0
  142. package/environments/types/config.ts +34 -207
  143. package/errors/version.ts +1 -1
  144. package/package.json +2 -2
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Drop-in axios method wrappers with retry built in.
3
+ *
4
+ * Use these instead of `axios.get` / `axios.post` for outbound HTTP calls in
5
+ * action files. The retry policy lives in `./retry.ts`.
6
+ *
7
+ * Why a wrapper instead of `retry(() => axios.get(...))` at each callsite?
8
+ * TypeScript's control-flow narrowing (e.g. after `if (!env.url) return [];`)
9
+ * doesn't propagate into nested closures, so inline `retry(() => axios.get(env.url))`
10
+ * makes `env.url` `string | undefined` again. Passing the URL as a function
11
+ * argument keeps the narrowing intact.
12
+ */
13
+ import axios, { type AxiosRequestConfig, type AxiosResponse } from "axios";
14
+
15
+ import { retry } from "./retry.js";
16
+
17
+ // Only forward args the caller actually passed — calling axios.get(url, undefined)
18
+ // is observably different from axios.get(url) for tests that assert call arity.
19
+
20
+ export function getWithRetry<T = unknown>(
21
+ url: string,
22
+ config?: AxiosRequestConfig,
23
+ ): Promise<AxiosResponse<T>> {
24
+ if (config !== undefined) {
25
+ return retry(() => axios.get<T>(url, config));
26
+ }
27
+ return retry(() => axios.get<T>(url));
28
+ }
29
+
30
+ export function postWithRetry<T = unknown, D = unknown>(
31
+ url: string,
32
+ data?: D,
33
+ config?: AxiosRequestConfig<D>,
34
+ ): Promise<AxiosResponse<T>> {
35
+ if (config !== undefined) {
36
+ return retry(() => axios.post<T, AxiosResponse<T>, D>(url, data, config));
37
+ }
38
+ if (data !== undefined) {
39
+ return retry(() => axios.post<T, AxiosResponse<T>, D>(url, data));
40
+ }
41
+ return retry(() => axios.post<T, AxiosResponse<T>, D>(url));
42
+ }
@@ -1,7 +1,7 @@
1
- import axios from "axios";
2
1
  import type { HttpRequestError } from "../../common/index.js";
3
2
  import { Amount } from "../../common/index.js";
4
3
  import type { BeamTokenLimits, BeamTokenRoutes } from "../../types/beam.js";
4
+ import { getWithRetry } from "../axiosWithRetry.js";
5
5
 
6
6
  export type GetBeamTokenLimitsErrorType = HttpRequestError;
7
7
 
@@ -36,7 +36,7 @@ export async function getBeamTokenLimits(
36
36
  direction === "withdraw" ? path.chainId : route.chainId;
37
37
 
38
38
  try {
39
- const acrossLimitsResponse = await axios.get<{
39
+ const acrossLimitsResponse = await getWithRetry<{
40
40
  minDeposit: string;
41
41
  maxDeposit: string;
42
42
  maxDepositInstant: string;
@@ -1,10 +1,10 @@
1
- import axios from "axios";
2
1
  import { zeroAddress } from "viem";
3
2
  import type { MoonwellClient } from "../../client/createMoonwellClient.js";
4
3
  import type { HttpRequestError } from "../../common/index.js";
5
4
  import { getEnvironmentsFromArgs } from "../../common/index.js";
6
5
  import type { TokenConfig } from "../../environments/index.js";
7
6
  import type { BeamTokenInfo, BeamTokenRoutes } from "../../types/beam.js";
7
+ import { getWithRetry } from "../axiosWithRetry.js";
8
8
 
9
9
  export type GetBeamTokenRoutesErrorType = HttpRequestError;
10
10
 
@@ -18,7 +18,7 @@ export async function getBeamTokenRoutes(
18
18
  ): GetBeamTokenRoutesReturnType {
19
19
  const environments = getEnvironmentsFromArgs(client, undefined, false);
20
20
 
21
- const acrossRoutesResponse = await axios.get<
21
+ const acrossRoutesResponse = await getWithRetry<
22
22
  {
23
23
  originChainId: number;
24
24
  originToken: string;
@@ -30,7 +30,7 @@ export async function getBeamTokenRoutes(
30
30
  }[]
31
31
  >("https://across.to/api/available-routes");
32
32
 
33
- const biconomyInfoResponse = await axios.get<{
33
+ const biconomyInfoResponse = await getWithRetry<{
34
34
  version: string;
35
35
  node: string;
36
36
  supportedChains: {
@@ -1,4 +1,3 @@
1
- import axios from "axios";
2
1
  import type { MoonwellClient } from "../../../client/createMoonwellClient.js";
3
2
  import {
4
3
  type SnapshotPeriod,
@@ -12,6 +11,7 @@ import { buildMarketId } from "../../../common/lunar-indexer-helpers.js";
12
11
  import type { NetworkParameterType } from "../../../common/types.js";
13
12
  import type { Chain, Environment } from "../../../environments/index.js";
14
13
  import type { MarketSnapshot } from "../../../types/market.js";
14
+ import { postWithRetry } from "../../axiosWithRetry.js";
15
15
  import {
16
16
  DEFAULT_LUNAR_TIMEOUT_MS,
17
17
  createLunarIndexerClient,
@@ -203,7 +203,7 @@ async function fetchCoreMarketSnapshotsFromPonder(
203
203
  let endCursor: string | undefined;
204
204
 
205
205
  while (hasNextPage) {
206
- const result = await axios.post<{
206
+ const result = await postWithRetry<{
207
207
  data: {
208
208
  marketDailySnapshots: {
209
209
  items: MarketDailyData[];
@@ -1,4 +1,3 @@
1
- import axios from "axios";
2
1
  import type { Address } from "viem";
3
2
  import type { MoonwellClient } from "../../../client/createMoonwellClient.js";
4
3
  import {
@@ -12,6 +11,7 @@ import {
12
11
  import type { NetworkParameterType } from "../../../common/types.js";
13
12
  import type { Chain, Environment } from "../../../environments/index.js";
14
13
  import type { UserPositionSnapshot } from "../../../types/userPosition.js";
14
+ import { postWithRetry } from "../../axiosWithRetry.js";
15
15
  import {
16
16
  DEFAULT_LUNAR_TIMEOUT_MS,
17
17
  createLunarIndexerClient,
@@ -205,7 +205,7 @@ async function fetchUserPositionSnapshotsFromPonder(
205
205
  let endCursor: string | undefined;
206
206
 
207
207
  while (hasNextPage) {
208
- const result = await axios.post<{
208
+ const result = await postWithRetry<{
209
209
  data: {
210
210
  accountDailySnapshots: {
211
211
  items: UserDailyData[];
@@ -1,9 +1,9 @@
1
- import axios from "axios";
2
1
  import type { MoonwellClient } from "../../client/createMoonwellClient.js";
3
2
  import { getEnvironmentFromArgs } from "../../common/index.js";
4
3
  import type { OptionalNetworkParameterType } from "../../common/types.js";
5
4
  import type { Chain, Environment } from "../../environments/index.js";
6
5
  import type { CirculatingSupplySnapshot } from "../../types/circulatingSupply.js";
6
+ import { getWithRetry, postWithRetry } from "../axiosWithRetry.js";
7
7
 
8
8
  export type GetCirculatingSupplySnapshotsParameters<
9
9
  environments,
@@ -46,7 +46,7 @@ async function fetchCirculatingSupplyFromLunar(
46
46
  params.cursor = cursor;
47
47
  }
48
48
 
49
- const response = await axios.get<
49
+ const response = await getWithRetry<
50
50
  LunarPaginatedResponse<LunarCirculatingSupplySnapshot>
51
51
  >(`${lunarIndexerUrl}/api/v1/staking/circulating-supply/${chainId}`, {
52
52
  params,
@@ -118,7 +118,7 @@ async function fetchCirculatingSupplyFromPonder(
118
118
  ): Promise<CirculatingSupplySnapshot[]> {
119
119
  if (!environment.indexerUrl) return [];
120
120
  try {
121
- const response = await axios.post<{
121
+ const response = await postWithRetry<{
122
122
  data: {
123
123
  circulatingSupplyDailySnapshots: {
124
124
  items: {
@@ -1,4 +1,3 @@
1
- import axios from "axios";
2
1
  import { isAddress } from "viem";
3
2
  import { base, moonbeam, optimism } from "viem/chains";
4
3
  import type { MoonwellClient } from "../../client/createMoonwellClient.js";
@@ -8,6 +7,7 @@ import {
8
7
  } from "../../environments/index.js";
9
8
  import * as logger from "../../logger/console.js";
10
9
  import type { Delegate } from "../../types/delegate.js";
10
+ import { getWithRetry } from "../axiosWithRetry.js";
11
11
  import { fetchAllVoters } from "./governor-api-client.js";
12
12
 
13
13
  export type GetDelegatesReturnType = Promise<Delegate[]>;
@@ -119,7 +119,7 @@ const getForumProfiles = async () => {
119
119
 
120
120
  const getUsersPaginated = async (page = 0) => {
121
121
  try {
122
- const response = await axios.get<{
122
+ const response = await getWithRetry<{
123
123
  directory_items: {
124
124
  user: {
125
125
  id: number;
@@ -1,9 +1,9 @@
1
- import axios from "axios";
2
1
  import lodash from "lodash";
3
2
  import type { MoonwellClient } from "../../client/createMoonwellClient.js";
4
3
  import { HttpRequestError } from "../../common/index.js";
5
4
  import * as logger from "../../logger/console.js";
6
5
  import type { Discussion } from "../../types/discussion.js";
6
+ import { getWithRetry } from "../axiosWithRetry.js";
7
7
 
8
8
  const { isEqual, uniqWith } = lodash;
9
9
 
@@ -43,7 +43,7 @@ export async function getDiscussions(
43
43
  "Starting to get discussions...",
44
44
  );
45
45
 
46
- const moonwellProposalsResult = await axios.get<ForumTopicRequestResponse>(
46
+ const moonwellProposalsResult = await getWithRetry<ForumTopicRequestResponse>(
47
47
  "https://forum.moonwell.fi/c/proposals/moonwell-improvement-proposals/9/l/latest.json",
48
48
  );
49
49
 
@@ -51,9 +51,10 @@ export async function getDiscussions(
51
51
  throw new HttpRequestError(moonwellProposalsResult.statusText);
52
52
  }
53
53
 
54
- const communityProposalsResult = await axios.get<ForumTopicRequestResponse>(
55
- "https://forum.moonwell.fi/c/proposals/community-proposal/19/l/latest.json",
56
- );
54
+ const communityProposalsResult =
55
+ await getWithRetry<ForumTopicRequestResponse>(
56
+ "https://forum.moonwell.fi/c/proposals/community-proposal/19/l/latest.json",
57
+ );
57
58
 
58
59
  if (
59
60
  communityProposalsResult.status !== 200 ||
@@ -1,4 +1,3 @@
1
- import axios from "axios";
2
1
  import type { MoonwellClient } from "../../client/createMoonwellClient.js";
3
2
  import {
4
3
  type SnapshotPeriod,
@@ -10,6 +9,7 @@ import {
10
9
  import type { NetworkParameterType } from "../../common/types.js";
11
10
  import type { Chain } from "../../environments/index.js";
12
11
  import type { StakingSnapshot } from "../../types/staking.js";
12
+ import { postWithRetry } from "../axiosWithRetry.js";
13
13
  import {
14
14
  DEFAULT_LUNAR_TIMEOUT_MS,
15
15
  createLunarIndexerClient,
@@ -129,7 +129,7 @@ async function fetchStakingSnapshotsFromPonder(
129
129
  startTime?: number,
130
130
  ): Promise<StakingSnapshot[]> {
131
131
  try {
132
- const response = await axios.post<{
132
+ const response = await postWithRetry<{
133
133
  data: {
134
134
  stakingDailySnapshots: {
135
135
  items: StakingSnapshot[];
@@ -1,6 +1,10 @@
1
1
  import { type Address, zeroAddress } from "viem";
2
2
  import type { MoonwellClient } from "../../client/createMoonwellClient.js";
3
- import { Amount, getEnvironmentsFromArgs } from "../../common/index.js";
3
+ import {
4
+ Amount,
5
+ getBlockNumberAtTimestamp,
6
+ getEnvironmentsFromArgs,
7
+ } from "../../common/index.js";
4
8
  import type { OptionalNetworkParameterType } from "../../common/types.js";
5
9
  import type { Chain, GovernanceToken } from "../../environments/index.js";
6
10
  import type { UserVotingPowers } from "../../types/userVotingPowers.js";
@@ -15,8 +19,21 @@ export type GetUserVotingPowersParameters<
15
19
  /** User address*/
16
20
  userAddress: Address;
17
21
 
18
- /** Block number **/
22
+ /**
23
+ * Block number to read voting power at. Applied to every queried chain — only safe
24
+ * for single-chain governance tokens. For cross-chain tokens (e.g. WELL) prefer
25
+ * `snapshotTimestamp`, which resolves a correct per-chain block.
26
+ */
19
27
  blockNumber?: bigint;
28
+
29
+ /**
30
+ * Unix timestamp (seconds) at which to read voting power. When set, the SDK looks up
31
+ * the block on each queried chain whose timestamp is the latest one ≤ this value, and
32
+ * uses that per-chain block. Use this for cross-chain governance tokens; supplying a
33
+ * single block number across heterogeneous chains is unsafe because chain heights
34
+ * diverge. Takes priority over `blockNumber` when both are provided.
35
+ */
36
+ snapshotTimestamp?: number;
20
37
  };
21
38
 
22
39
  export type GetUserVotingPowersReturnType = Promise<UserVotingPowers[]>;
@@ -28,7 +45,7 @@ export async function getUserVotingPowers<
28
45
  client: MoonwellClient,
29
46
  args: GetUserVotingPowersParameters<environments, Network>,
30
47
  ): GetUserVotingPowersReturnType {
31
- const { governanceToken, userAddress, blockNumber } = args;
48
+ const { governanceToken, userAddress, blockNumber, snapshotTimestamp } = args;
32
49
 
33
50
  const environments = getEnvironmentsFromArgs(client, args);
34
51
 
@@ -36,12 +53,30 @@ export async function getUserVotingPowers<
36
53
  (env) => env.custom?.governance?.token === governanceToken,
37
54
  );
38
55
 
56
+ const perChainBlockNumbers =
57
+ snapshotTimestamp !== undefined
58
+ ? await Promise.all(
59
+ tokenEnvironments.map((env) =>
60
+ getBlockNumberAtTimestamp(
61
+ env.publicClient,
62
+ BigInt(snapshotTimestamp),
63
+ ),
64
+ ),
65
+ )
66
+ : undefined;
67
+
39
68
  const environmentsUserVotingPowers = await Promise.all(
40
- tokenEnvironments.map((environment) =>
41
- environment.contracts.views?.read.getUserVotingPower([userAddress], {
42
- blockNumber,
43
- }),
44
- ),
69
+ tokenEnvironments.map((environment, index) => {
70
+ const blockForChain = perChainBlockNumbers
71
+ ? perChainBlockNumbers[index]
72
+ : blockNumber;
73
+ return environment.contracts.views?.read.getUserVotingPower(
74
+ [userAddress],
75
+ {
76
+ blockNumber: blockForChain,
77
+ },
78
+ );
79
+ }),
45
80
  );
46
81
 
47
82
  return tokenEnvironments.map((environment, index) => {
@@ -1,5 +1,5 @@
1
- import axios from "axios";
2
1
  import type { Environment } from "../../environments/index.js";
2
+ import { getWithRetry } from "../axiosWithRetry.js";
3
3
 
4
4
  /**
5
5
  * Base configuration for Governor API requests
@@ -108,7 +108,7 @@ export async function fetchProposals(
108
108
  if (options?.cursor) params.append("cursor", options.cursor);
109
109
  if (options?.chainId) params.append("chainId", options.chainId.toString());
110
110
 
111
- const response = await axios.get<PaginatedResponse<ApiProposal>>(
111
+ const response = await getWithRetry<PaginatedResponse<ApiProposal>>(
112
112
  `${baseUrl}/api/v1/governor/proposals?${params.toString()}`,
113
113
  );
114
114
 
@@ -152,7 +152,7 @@ export async function fetchProposal(
152
152
  ): Promise<ApiProposal> {
153
153
  const baseUrl = getGovernorApiUrl(environment);
154
154
 
155
- const response = await axios.get<ApiProposal>(
155
+ const response = await getWithRetry<ApiProposal>(
156
156
  `${baseUrl}/api/v1/governor/proposals/${proposalId}`,
157
157
  );
158
158
 
@@ -177,7 +177,7 @@ export async function fetchProposalVotes(
177
177
  if (options?.limit) params.append("limit", options.limit.toString());
178
178
  if (options?.cursor) params.append("cursor", options.cursor);
179
179
 
180
- const response = await axios.get<PaginatedResponse<ApiVote>>(
180
+ const response = await getWithRetry<PaginatedResponse<ApiVote>>(
181
181
  `${baseUrl}/api/v1/governor/proposals/${proposalId}/votes?${params.toString()}`,
182
182
  );
183
183
 
@@ -225,7 +225,9 @@ export async function fetchProposalStateChanges(
225
225
  if (options?.limit) params.append("limit", options.limit.toString());
226
226
  if (options?.cursor) params.append("cursor", options.cursor);
227
227
 
228
- const response = await axios.get<PaginatedResponse<ApiProposalStateChange>>(
228
+ const response = await getWithRetry<
229
+ PaginatedResponse<ApiProposalStateChange>
230
+ >(
229
231
  `${baseUrl}/api/v1/governor/proposals/${proposalId}/state-changes?${params.toString()}`,
230
232
  );
231
233
 
@@ -277,7 +279,7 @@ export async function fetchVoters(
277
279
  const endpoint = `${baseUrl}/api/v1/governor/voters?${params.toString()}`;
278
280
 
279
281
  try {
280
- const response = await axios.get<PaginatedResponse<ApiVoter>>(endpoint);
282
+ const response = await getWithRetry<PaginatedResponse<ApiVoter>>(endpoint);
281
283
 
282
284
  if (response.status !== 200 || !response.data) {
283
285
  throw new Error(`Failed to fetch voters: ${response.statusText}`);
@@ -328,7 +330,7 @@ export async function fetchVoter(
328
330
  ): Promise<ApiVoter> {
329
331
  const baseUrl = getGovernorApiUrl(environment);
330
332
 
331
- const response = await axios.get<ApiVoter>(
333
+ const response = await getWithRetry<ApiVoter>(
332
334
  `${baseUrl}/api/v1/governor/voters/${address}`,
333
335
  );
334
336
 
@@ -359,7 +361,7 @@ export async function fetchVoterProposals(
359
361
 
360
362
  const endpoint = `${baseUrl}/api/v1/governor/voters/${address}/proposals?${params.toString()}`;
361
363
 
362
- const response = await axios.get<PaginatedResponse<ApiProposal>>(endpoint);
364
+ const response = await getWithRetry<PaginatedResponse<ApiProposal>>(endpoint);
363
365
 
364
366
  if (response.status !== 200 || !response.data) {
365
367
  throw new Error(`Failed to fetch voter proposals: ${response.statusText}`);
@@ -407,7 +409,7 @@ export async function fetchVoterVotes(
407
409
 
408
410
  const endpoint = `${baseUrl}/api/v1/governor/voters/${address}/votes?${params.toString()}`;
409
411
 
410
- const response = await axios.get<PaginatedResponse<ApiVote>>(endpoint);
412
+ const response = await getWithRetry<PaginatedResponse<ApiVote>>(endpoint);
411
413
 
412
414
  if (response.status !== 200 || !response.data) {
413
415
  throw new Error(`Failed to fetch voter votes: ${response.statusText}`);
@@ -450,7 +452,7 @@ export async function fetchUserVoteReceipt(
450
452
  ): Promise<ApiVoteReceipt[]> {
451
453
  const baseUrl = getGovernorApiUrl(environment);
452
454
 
453
- const response = await axios.get<ApiVoteReceipt[]>(
455
+ const response = await getWithRetry<ApiVoteReceipt[]>(
454
456
  `${baseUrl}/api/v1/governor/proposals/${proposalId}/vote/${voterAddress}`,
455
457
  );
456
458
 
@@ -9,6 +9,7 @@ import {
9
9
  type Proposal,
10
10
  type ProposalState,
11
11
  } from "../../../types/proposal.js";
12
+ import { postWithRetry } from "../../axiosWithRetry.js";
12
13
  import type { ApiProposal } from "../governor-api-client.js";
13
14
 
14
15
  export const WORMHOLE_CONTRACT = "0xc8e2b0cd52cf01b0ce87d389daa3d414d4ce29f3";
@@ -98,6 +99,24 @@ export const isMultichainProposal = (targets?: string[]): boolean => {
98
99
  );
99
100
  };
100
101
 
102
+ /**
103
+ * Routes a proposal to the multichain governor when:
104
+ * - its targets include the Wormhole bridge (legacy detection), OR
105
+ * - its proposalId is past the legacy Artemis governor's `proposalCount`,
106
+ * which means it could only have been created on the multichain governor
107
+ * (proposals migrated to the multichain governor after the cutoff but
108
+ * can have local-only targets, e.g. Moonbeam-internal contract calls).
109
+ *
110
+ * `legacyArtemisMaxId === 0` indicates the count read failed; in that case we
111
+ * fall back to the targets-only heuristic.
112
+ */
113
+ export const isMultichainAware = (
114
+ proposal: { targets?: string[]; proposalId: number },
115
+ legacyArtemisMaxId: number,
116
+ ): boolean =>
117
+ isMultichainProposal(proposal.targets) ||
118
+ (legacyArtemisMaxId > 0 && proposal.proposalId > legacyArtemisMaxId);
119
+
101
120
  export type ApiProposalFormatted = {
102
121
  forVotes: Amount;
103
122
  againstVotes: Amount;
@@ -180,6 +199,40 @@ export type ProposalOnChainData = {
180
199
  quorum: bigint;
181
200
  };
182
201
 
202
+ // Cached per chain: highest proposalId held by the legacy Artemis governor.
203
+ // Anything with a higher proposalId belongs to the multichain governor, even
204
+ // if its targets don't include the Wormhole bridge. The legacy governor only
205
+ // receives new proposals during chain migrations, so a 5-minute TTL is plenty.
206
+ const LEGACY_ARTEMIS_MAX_ID_TTL_MS = 5 * 60 * 1000;
207
+ const legacyArtemisMaxIdCache = new Map<
208
+ number,
209
+ { value: number; fetchedAt: number }
210
+ >();
211
+
212
+ const getLegacyArtemisMaxId = async (
213
+ governanceEnvironment: Environment,
214
+ ): Promise<number> => {
215
+ const governor = governanceEnvironment.contracts.governor;
216
+ if (!governor) return 0;
217
+
218
+ const cached = legacyArtemisMaxIdCache.get(governanceEnvironment.chainId);
219
+ if (cached && Date.now() - cached.fetchedAt < LEGACY_ARTEMIS_MAX_ID_TTL_MS) {
220
+ return cached.value;
221
+ }
222
+
223
+ try {
224
+ const value = Number(await governor.read.proposalCount());
225
+ legacyArtemisMaxIdCache.set(governanceEnvironment.chainId, {
226
+ value,
227
+ fetchedAt: Date.now(),
228
+ });
229
+ return value;
230
+ } catch (error) {
231
+ console.warn("Failed to fetch legacy governor proposalCount:", error);
232
+ return cached?.value ?? 0;
233
+ }
234
+ };
235
+
183
236
  /**
184
237
  * Fetches on-chain data for multiple proposals
185
238
  */
@@ -200,9 +253,11 @@ export const getProposalsOnChainData = async (
200
253
  }
201
254
  }
202
255
 
256
+ const legacyArtemisMaxId = await getLegacyArtemisMaxId(governanceEnvironment);
257
+
203
258
  const onChainDataList = await Promise.all(
204
259
  apiProposals.map(async (p) => {
205
- const isMultichain = isMultichainProposal(p.targets);
260
+ const isMultichain = isMultichainAware(p, legacyArtemisMaxId);
206
261
 
207
262
  const governorContract = isMultichain
208
263
  ? governanceEnvironment.contracts.multichainGovernor
@@ -241,7 +296,7 @@ export const getProposalsOnChainData = async (
241
296
 
242
297
  const votesCollectedList = await Promise.all(
243
298
  apiProposals.map(async (apiProposal) => {
244
- const isMultichain = isMultichainProposal(apiProposal.targets);
299
+ const isMultichain = isMultichainAware(apiProposal, legacyArtemisMaxId);
245
300
 
246
301
  if (
247
302
  !isMultichain ||
@@ -656,7 +711,7 @@ export const getExtendedProposalData = async (params: {
656
711
  try {
657
712
  while (shouldContinue && page < MAX_PAGES) {
658
713
  page++;
659
- const response = await axios.post<{
714
+ const response = await postWithRetry<{
660
715
  data: {
661
716
  proposals: {
662
717
  items: {
@@ -1,4 +1,3 @@
1
- import axios from "axios";
2
1
  import {
3
2
  type Environment,
4
3
  base,
@@ -6,6 +5,7 @@ import {
6
5
  supportedChains,
7
6
  } from "../../../environments/index.js";
8
7
  import type { SnapshotProposal } from "../../../types/snapshotProposal.js";
8
+ import { postWithRetry } from "../../axiosWithRetry.js";
9
9
  import type { GetSnapshotProposalsReturnType } from "./getSnapshotProposals.js";
10
10
 
11
11
  export const getSnapshotProposalData = async (params: {
@@ -32,7 +32,7 @@ export const getSnapshotProposalData = async (params: {
32
32
 
33
33
  const pageSize = params.pagination?.size ? params.pagination.size : 10;
34
34
 
35
- const response = await axios.post<{
35
+ const response = await postWithRetry<{
36
36
  data: {
37
37
  spaces: {
38
38
  proposalsCount: number;
@@ -7,6 +7,8 @@
7
7
 
8
8
  import axios, { type AxiosInstance, type AxiosError } from "axios";
9
9
 
10
+ import { attachRetryInterceptor } from "./retry.js";
11
+
10
12
  // ============================================================================
11
13
  // Type Definitions
12
14
  // ============================================================================
@@ -301,6 +303,12 @@ export class LunarIndexerClient {
301
303
  "Content-Type": "application/json",
302
304
  },
303
305
  });
306
+
307
+ // Retry transient failures (5xx, network errors, timeouts) silently before
308
+ // surfacing to callers. 4xx (incl. 404) bypasses retries — see ./retry.ts.
309
+ attachRetryInterceptor(this.client);
310
+ attachRetryInterceptor(this.stakingClient);
311
+ attachRetryInterceptor(this.vaultsClient);
304
312
  }
305
313
 
306
314
  /**
@@ -1,4 +1,3 @@
1
- import axios from "axios";
2
1
  import type { Address } from "viem";
3
2
  import { Amount } from "../../../common/amount.js";
4
3
  import type { MultichainReturnType } from "../../../common/types.js";
@@ -8,6 +7,7 @@ import type {
8
7
  PublicAllocatorSharedLiquidityType,
9
8
  } from "../../../types/morphoMarket.js";
10
9
  import type { MorphoReward } from "../../../types/morphoReward.js";
10
+ import { getWithRetry } from "../../axiosWithRetry.js";
11
11
  import { getGraphQL } from "../utils/graphql.js";
12
12
  import {
13
13
  type GetMorphoMarketsRewardsReturnType as LunarIndexerRewardsType,
@@ -49,7 +49,7 @@ async function fetchSharedLiquidityFromLunar(
49
49
  lunarIndexerUrl: string,
50
50
  chainId: number,
51
51
  ): Promise<LunarSharedLiquidityResponse> {
52
- const response = await axios.get<LunarSharedLiquidityResponse>(
52
+ const response = await getWithRetry<LunarSharedLiquidityResponse>(
53
53
  `${lunarIndexerUrl}/api/v1/isolated/shared-liquidity/${chainId}`,
54
54
  );
55
55
  return response.data;
@@ -8,7 +8,6 @@
8
8
  * @module morpho/markets/lunarIndexerTransform
9
9
  */
10
10
 
11
- import axios from "axios";
12
11
  import type { Address } from "viem";
13
12
  import { parseUnits } from "viem";
14
13
  import { Amount } from "../../../common/amount.js";
@@ -20,6 +19,7 @@ import type {
20
19
  PublicAllocatorSharedLiquidityType,
21
20
  } from "../../../types/morphoMarket.js";
22
21
  import type { MorphoReward } from "../../../types/morphoReward.js";
22
+ import { getWithRetry } from "../../axiosWithRetry.js";
23
23
 
24
24
  /**
25
25
  * Lunar Indexer API Response Types
@@ -185,7 +185,7 @@ export async function fetchMarketsFromIndexer(
185
185
  }
186
186
  const queryString = params.toString();
187
187
  const url = `${lunarIndexerUrl}/api/v1/isolated/markets/${chainId}${queryString ? `?${queryString}` : ""}`;
188
- const response = await axios.get<LunarIndexerMarketsResponse>(url);
188
+ const response = await getWithRetry<LunarIndexerMarketsResponse>(url);
189
189
  return response.data;
190
190
  }
191
191
 
@@ -198,7 +198,7 @@ export async function fetchMarketFromIndexer(
198
198
  marketId: string,
199
199
  ): Promise<LunarIndexerMarket> {
200
200
  const url = `${lunarIndexerUrl}/api/v1/isolated/market/${chainId}/${marketId.toLowerCase()}`;
201
- const response = await axios.get<LunarIndexerMarket>(url);
201
+ const response = await getWithRetry<LunarIndexerMarket>(url);
202
202
  return response.data;
203
203
  }
204
204
 
@@ -240,7 +240,7 @@ export async function fetchMarketSnapshotsFromIndexer(
240
240
  queryString ? `?${queryString}` : ""
241
241
  }`;
242
242
 
243
- const response = await axios.get<LunarIndexerMarketSnapshotsResponse>(url);
243
+ const response = await getWithRetry<LunarIndexerMarketSnapshotsResponse>(url);
244
244
  return response.data;
245
245
  }
246
246
 
@@ -281,7 +281,8 @@ export async function fetchAccountMarketPortfolioFromIndexer(
281
281
  queryString ? `?${queryString}` : ""
282
282
  }`;
283
283
 
284
- const response = await axios.get<LunarIndexerAccountPortfolioResponse>(url);
284
+ const response =
285
+ await getWithRetry<LunarIndexerAccountPortfolioResponse>(url);
285
286
  return response.data;
286
287
  }
287
288