@moonwell-fi/moonwell-sdk 0.20.3 → 0.20.4

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 (43) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/_cjs/actions/governance/governor-api-client.js +3 -2
  3. package/_cjs/actions/governance/governor-api-client.js.map +1 -1
  4. package/_cjs/actions/governance/proposals/common.js +11 -296
  5. package/_cjs/actions/governance/proposals/common.js.map +1 -1
  6. package/_cjs/actions/governance/proposals/getProposal.js +9 -25
  7. package/_cjs/actions/governance/proposals/getProposal.js.map +1 -1
  8. package/_cjs/actions/governance/proposals/getProposals.js +23 -14
  9. package/_cjs/actions/governance/proposals/getProposals.js.map +1 -1
  10. package/_cjs/errors/version.js +1 -1
  11. package/_cjs/types/proposal.js.map +1 -1
  12. package/_esm/actions/governance/governor-api-client.js +21 -4
  13. package/_esm/actions/governance/governor-api-client.js.map +1 -1
  14. package/_esm/actions/governance/ipfs.js +2 -2
  15. package/_esm/actions/governance/proposals/common.js +28 -302
  16. package/_esm/actions/governance/proposals/common.js.map +1 -1
  17. package/_esm/actions/governance/proposals/getProposal.js +23 -35
  18. package/_esm/actions/governance/proposals/getProposal.js.map +1 -1
  19. package/_esm/actions/governance/proposals/getProposals.js +48 -20
  20. package/_esm/actions/governance/proposals/getProposals.js.map +1 -1
  21. package/_esm/errors/version.js +1 -1
  22. package/_esm/types/proposal.js.map +1 -1
  23. package/_types/actions/governance/getUserVoteReceipt.d.ts +4 -4
  24. package/_types/actions/governance/governor-api-client.d.ts +34 -4
  25. package/_types/actions/governance/governor-api-client.d.ts.map +1 -1
  26. package/_types/actions/governance/ipfs.d.ts +2 -2
  27. package/_types/actions/governance/proposals/common.d.ts +12 -39
  28. package/_types/actions/governance/proposals/common.d.ts.map +1 -1
  29. package/_types/actions/governance/proposals/getProposal.d.ts +2 -1
  30. package/_types/actions/governance/proposals/getProposal.d.ts.map +1 -1
  31. package/_types/actions/governance/proposals/getProposals.d.ts.map +1 -1
  32. package/_types/errors/version.d.ts +1 -1
  33. package/_types/types/proposal.d.ts +11 -0
  34. package/_types/types/proposal.d.ts.map +1 -1
  35. package/actions/governance/getUserVoteReceipt.ts +4 -4
  36. package/actions/governance/governor-api-client.ts +35 -4
  37. package/actions/governance/ipfs.ts +2 -2
  38. package/actions/governance/proposals/common.ts +28 -466
  39. package/actions/governance/proposals/getProposal.ts +25 -49
  40. package/actions/governance/proposals/getProposals.ts +57 -30
  41. package/errors/version.ts +1 -1
  42. package/package.json +1 -1
  43. package/types/proposal.ts +11 -0
@@ -22,6 +22,17 @@ export type Proposal = {
22
22
  id: number;
23
23
  votesCollected: boolean;
24
24
  };
25
+ /**
26
+ * Authoritative per-chain voting-power snapshot blocks resolved by the
27
+ * lunar-indexer, keyed by chain name (`mainnet` → 1, `base` → 8453,
28
+ * `optimism` → 10, `moonbeam` → 1284). Block numbers are decimal strings.
29
+ *
30
+ * Present on indexer-sourced multichain proposals; absent for older
31
+ * proposals indexed before the field existed and for on-chain (Moonriver)
32
+ * proposals. Consumers reading voting power should prefer these blocks over
33
+ * resolving the snapshot timestamp to a block client-side.
34
+ */
35
+ snapshotBlocks?: Record<string, string>;
25
36
  title?: string;
26
37
  subtitle?: string;
27
38
  description?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"proposal.d.ts","sourceRoot":"","sources":["../../types/proposal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAE5D,MAAM,MAAM,QAAQ,GAAG;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,aAAa,CAAC;IACrB,UAAU,CAAC,EAAE;QACX,EAAE,EAAE,MAAM,CAAC;QACX,cAAc,EAAE,OAAO,CAAC;KACzB,CAAC;IAEF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,YAAY,CAAC,EAAE;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,eAAe,EAAE,MAAM,CAAC;QACxB,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,EAAE,CAAC;IACJ,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B,CAAC;AAEF,oBAAY,aAAa;IACvB,OAAO,IAAI;IACX,MAAM,IAAI;IACV,QAAQ,IAAI;IACZ,QAAQ,IAAI;IACZ,SAAS,IAAI;IACb,MAAM,IAAI;IACV,OAAO,IAAI;IACX,QAAQ,IAAI;IACZ,gBAAgB,IAAI;IACpB,kBAAkB,IAAI;CACvB;AAGD,oBAAY,uBAAuB;IACjC,MAAM,IAAI;IACV,wBAAwB,IAAI;IAC5B,QAAQ,IAAI;IACZ,QAAQ,IAAI;IACZ,SAAS,IAAI;IACb,QAAQ,IAAI;CACb;AAGD,eAAO,MAAM,8BAA8B;;;;;;;CAO1C,CAAC;AAGF,MAAM,MAAM,oBAAoB,GAAG;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,YAAY,EAAE;QACZ,WAAW,EAAE,MAAM,CAAC;QACpB,eAAe,EAAE,MAAM,CAAC;QACxB,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,EAAE,CAAC;CACL,CAAC"}
1
+ {"version":3,"file":"proposal.d.ts","sourceRoot":"","sources":["../../types/proposal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAE5D,MAAM,MAAM,QAAQ,GAAG;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,aAAa,CAAC;IACrB,UAAU,CAAC,EAAE;QACX,EAAE,EAAE,MAAM,CAAC;QACX,cAAc,EAAE,OAAO,CAAC;KACzB,CAAC;IACF;;;;;;;;;OASG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAExC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,YAAY,CAAC,EAAE;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,eAAe,EAAE,MAAM,CAAC;QACxB,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,EAAE,CAAC;IACJ,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B,CAAC;AAEF,oBAAY,aAAa;IACvB,OAAO,IAAI;IACX,MAAM,IAAI;IACV,QAAQ,IAAI;IACZ,QAAQ,IAAI;IACZ,SAAS,IAAI;IACb,MAAM,IAAI;IACV,OAAO,IAAI;IACX,QAAQ,IAAI;IACZ,gBAAgB,IAAI;IACpB,kBAAkB,IAAI;CACvB;AAGD,oBAAY,uBAAuB;IACjC,MAAM,IAAI;IACV,wBAAwB,IAAI;IAC5B,QAAQ,IAAI;IACZ,QAAQ,IAAI;IACZ,SAAS,IAAI;IACb,QAAQ,IAAI;CACb;AAGD,eAAO,MAAM,8BAA8B;;;;;;;CAO1C,CAAC;AAGF,MAAM,MAAM,oBAAoB,GAAG;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,YAAY,EAAE;QACZ,WAAW,EAAE,MAAM,CAAC;QACpB,eAAe,EAAE,MAAM,CAAC;QACxB,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,EAAE,CAAC;CACL,CAAC"}
@@ -23,10 +23,10 @@ export type GetUserVoteReceiptParameters<
23
23
 
24
24
  /**
25
25
  * The chain the proposal lives on (1 = Ethereum multigov,
26
- * 1284 = Moonbeam historical). When omitted, every supported chain is queried
27
- * and non-empty receipts are concatenated — proposalIds may collide across
28
- * chains (they represent different proposals), so a single bare proposalId
29
- * can have votes on both Ethereum and Moonbeam.
26
+ * 1284 = Moonbeam historical, 1285 = Moonriver legacy). When omitted, every
27
+ * supported chain is queried and non-empty receipts are concatenated —
28
+ * proposalIds may collide across chains (they represent different proposals),
29
+ * so a single bare proposalId can have votes on more than one chain.
30
30
  */
31
31
  chainId?: number;
32
32
  };
@@ -12,9 +12,26 @@ const getGovernorApiUrl = (environment: Environment): string => {
12
12
 
13
13
  /**
14
14
  * Chains the governor indexer serves. Ethereum first because that's where the
15
- * active multigov contract lives; Moonbeam follows for the historical archive.
15
+ * active multigov contract lives; Moonbeam follows for the historical archive,
16
+ * then Moonriver (legacy standalone governor).
17
+ *
18
+ * This is the fan-out set for `getUserVoteReceipt` — a bare proposalId is
19
+ * queried on every chain and non-empty receipts are concatenated. Do NOT use it
20
+ * as the single-proposal fallback in `getProposal`: Moonriver has its own
21
+ * explicit route there, and reaching a 1285 proposal through a Moonbeam env
22
+ * would surface a degraded result (see `MULTIGOV_PROPOSAL_FALLBACK_CHAIN_IDS`).
23
+ */
24
+ export const SUPPORTED_GOVERNOR_CHAIN_IDS = [1, 1284, 1285] as const;
25
+
26
+ /**
27
+ * Fallback chains for a single multigov proposal lookup (`getProposal` with no
28
+ * explicit `chainId`), tried first-hit-wins. Deliberately excludes Moonriver
29
+ * (1285): Moonriver proposals are fetched through their own env via an explicit
30
+ * chainId, so a bare Moonbeam/Ethereum lookup must never resolve to a 1285
31
+ * proposal — that would return `quorum: 0n` (cross-chain quorum skips
32
+ * governor-less chains) and the wrong `environment`.
16
33
  */
17
- export const SUPPORTED_GOVERNOR_CHAIN_IDS = [1, 1284] as const;
34
+ export const MULTIGOV_PROPOSAL_FALLBACK_CHAIN_IDS = [1, 1284] as const;
18
35
 
19
36
  /**
20
37
  * Build the chain-prefixed proposal key the indexer requires
@@ -103,6 +120,13 @@ export type ApiProposal = {
103
120
  targets: string[];
104
121
  values: string[];
105
122
  calldatas: string[];
123
+ /**
124
+ * Legacy/Artemis-governor function signatures (e.g. "setDirectPrice(address,uint256)"),
125
+ * parallel to `targets`/`calldatas`. Present for legacy-governor proposals
126
+ * (Moonriver, early Moonbeam) whose calldata carries no 4-byte selector;
127
+ * empty/absent for multichain-governor proposals (selector is in the calldata).
128
+ */
129
+ signatures?: string[];
106
130
  votingStartTime: number;
107
131
  votingEndTime: number;
108
132
  description: string;
@@ -113,6 +137,12 @@ export type ApiProposal = {
113
137
  timestamp: number;
114
138
  transactionHash: string;
115
139
  stateChanges?: ApiProposalStateChange[];
140
+ /**
141
+ * Per-chain voting-power snapshot blocks resolved by the indexer, keyed by
142
+ * chain name (`mainnet`, `base`, `optimism`, `moonbeam`). Decimal strings.
143
+ * Omitted for proposals indexed before this field existed.
144
+ */
145
+ snapshotBlocks?: Record<string, string>;
116
146
  };
117
147
 
118
148
  export type ApiVote = {
@@ -168,8 +198,9 @@ export type FetchProposalsOptions = PaginationOptions & {
168
198
  /**
169
199
  * Fetch proposals from Governor API
170
200
  *
171
- * `chainId` is required by the indexer — 1 (Ethereum multigov) or
172
- * 1284 (Moonbeam historical). Missing/unsupported chainId returns 400.
201
+ * `chainId` is required by the indexer — 1 (Ethereum multigov),
202
+ * 1284 (Moonbeam historical), or 1285 (Moonriver legacy). Missing/unsupported
203
+ * chainId returns 400.
173
204
  */
174
205
  export async function fetchProposals(
175
206
  environment: Environment,
@@ -50,8 +50,8 @@ export const fetchIpfsContent = async (hash: string): Promise<string> => {
50
50
  * fetches run in parallel.
51
51
  *
52
52
  * Failures are logged via console.warn AND surfaced through
53
- * `env.onError` (matching the convention used by `getProposalData` /
54
- * `getExtendedProposalData`), then swallowed per-proposal: the `ipfs://` URI
53
+ * `env.onError` (matching the convention used by `getProposalsOnChainData` /
54
+ * `readCrossChainQuorums`), then swallowed per-proposal: the `ipfs://` URI
55
55
  * is left untouched so consumers can detect (e.g. via
56
56
  * `description.startsWith("ipfs://")`) and render a fallback. The bulk call
57
57
  * never rejects, so a single bad pin doesn't kill `getProposals()` for
@@ -1,16 +1,12 @@
1
1
  import axios from "axios";
2
- import last from "lodash/last.js";
3
- import { moonriver } from "viem/chains";
4
2
  import { Amount } from "../../../common/index.js";
5
3
  import type { Environment } from "../../../environments/index.js";
6
4
  import { publicEnvironments } from "../../../environments/index.js";
7
5
  import {
8
6
  MultichainProposalState,
9
7
  MultichainProposalStateMapping,
10
- type Proposal,
11
8
  ProposalState,
12
9
  } from "../../../types/proposal.js";
13
- import { postWithRetry } from "../../axiosWithRetry.js";
14
10
  import type { ApiProposal } from "../governor-api-client.js";
15
11
 
16
12
  export const WORMHOLE_CONTRACT = "0xc8e2b0cd52cf01b0ce87d389daa3d414d4ce29f3"; // Moonbeam
@@ -25,21 +21,6 @@ const MULTICHAIN_WORMHOLE_BRIDGES: ReadonlySet<string> = new Set([
25
21
  "0x98f3c9e6e3face36baad05fe09d375ef1464288b", // Ethereum
26
22
  ]);
27
23
 
28
- type PonderExtendedProposalData = {
29
- id: number;
30
- title: string;
31
- subtitle: string;
32
- description: string;
33
- targets: string[];
34
- calldatas: string[];
35
- signatures: string[];
36
- stateChanges: {
37
- blockNumber: number;
38
- transactionHash: string;
39
- state: string;
40
- }[];
41
- };
42
-
43
24
  axios.defaults.timeout = 5_000;
44
25
 
45
26
  /**
@@ -143,15 +124,26 @@ export const isMultichainHomeChain = (chainId: number): boolean => {
143
124
  * falls back to the home/bridge checks. Omit it or pass `undefined` when the
144
125
  * count read failed so the unknown-cutoff bias above applies. (No `= 0` default:
145
126
  * that would coerce an explicit `undefined` back to 0 and defeat the bias.)
127
+ *
128
+ * `hasMultichainGovernor` gates rule 4: the unknown-cutoff bias only makes sense
129
+ * on a chain that actually has a multichain governor to route to. On a
130
+ * legacy-only chain (Moonriver — legacy `governor`, no `multichainGovernor`) a
131
+ * failed Artemis count must NOT flip the proposal to multichain, which would
132
+ * route reads to a null contract and surface a spurious `multichain` field.
133
+ * Defaults to `true` so callers that don't supply it keep the dual-governor
134
+ * (Moonbeam) bias behavior.
146
135
  */
147
136
  export const classifyProposalMultichain = (
148
137
  proposal: { targets?: string[]; proposalId: number; chainId: number },
149
138
  legacyArtemisMaxId?: number,
139
+ hasMultichainGovernor = true,
150
140
  ): boolean =>
151
141
  isMultichainHomeChain(proposal.chainId) ||
152
142
  isMultichainProposal(proposal.targets) ||
153
- legacyArtemisMaxId === undefined ||
154
- (legacyArtemisMaxId > 0 && proposal.proposalId > legacyArtemisMaxId);
143
+ (legacyArtemisMaxId === undefined && hasMultichainGovernor) ||
144
+ (legacyArtemisMaxId !== undefined &&
145
+ legacyArtemisMaxId > 0 &&
146
+ proposal.proposalId > legacyArtemisMaxId);
155
147
 
156
148
  /**
157
149
  * @deprecated Use `classifyProposalMultichain` — this variant misses hub-homed
@@ -330,9 +322,8 @@ const getLegacyArtemisMaxId = async (
330
322
  * the `options.crossChainQuorums?.get(...) ?? 0n` pattern.
331
323
  *
332
324
  * Read failures are routed through `onError` so Sentry-wired consumers see
333
- * them — matching `getProposalData` / `getCrossChainProposalData` /
334
- * `getExtendedProposalData`. The caller's `governanceEnvironment` carries
335
- * `onError` since we don't have one per foreign chain in scope.
325
+ * them — matching `getProposalsOnChainData`. The caller's `governanceEnvironment`
326
+ * carries `onError` since we don't have one per foreign chain in scope.
336
327
  */
337
328
  export const getEnvironmentByChainId = (
338
329
  chainId: number,
@@ -393,6 +384,15 @@ export const getProposalsOnChainData = async (
393
384
  : await governanceEnvironment.contracts.governor.read.getQuorum();
394
385
  } catch (error) {
395
386
  console.warn("Failed to fetch quorum:", error);
387
+ // Report so a swallowed quorum failure (which leaves quorum at 0n) is
388
+ // still observable to onError-wired consumers — the legacy Moonriver path
389
+ // surfaced this too. Uses a distinct `source` from the state/proposals
390
+ // reads (and from readCrossChainQuorums' "governance-cross-chain-quorum")
391
+ // so a local quorum-read failure is attributable on its own.
392
+ governanceEnvironment.onError?.(error, {
393
+ source: "governance-quorum",
394
+ chainId: governanceEnvironment.chainId,
395
+ });
396
396
  }
397
397
  }
398
398
 
@@ -420,10 +420,13 @@ export const getProposalsOnChainData = async (
420
420
  // (Ethereum multigov, no Artemis predecessor) classify as multichain
421
421
  // regardless of targets via classifyProposalMultichain. Computed once
422
422
  // here and returned on ProposalOnChainData so callers don't re-classify
423
- // and drift (see getProposal/getProposals).
423
+ // and drift (see getProposal/getProposals). `hasMultichainGovernor` keeps
424
+ // the unknown-cutoff bias from misrouting legacy-only chains (Moonriver)
425
+ // to a nonexistent multichain governor when the Artemis count read fails.
424
426
  const isMultichain = classifyProposalMultichain(
425
427
  p,
426
428
  isLocal ? legacyArtemisMaxId : 0,
429
+ Boolean(homeEnv?.contracts.multichainGovernor),
427
430
  );
428
431
 
429
432
  if (!homeEnv) {
@@ -557,444 +560,3 @@ export const getProposalsOnChainData = async (
557
560
 
558
561
  return onChainDataList;
559
562
  };
560
-
561
- /**
562
- * Get proposal data from on-chain (Ponder-based, for Moonriver)
563
- */
564
- export const getProposalData = async (params: {
565
- environment: Environment;
566
- id?: number;
567
- }) => {
568
- try {
569
- if (params.environment.contracts.governor) {
570
- let count = 0n;
571
- let quorum = 0n;
572
-
573
- if (params.environment.chainId === moonriver.id) {
574
- [count, quorum] = await Promise.all([
575
- params.environment.contracts.governor.read.proposalCount(),
576
- params.environment.contracts.governor.read.getQuorum(),
577
- ]);
578
- } else {
579
- [count, quorum] = await Promise.all([
580
- params.environment.contracts.governor.read.proposalCount(),
581
- params.environment.contracts.governor.read.quorumVotes(),
582
- ]);
583
- }
584
-
585
- if (params.id) {
586
- if (BigInt(params.id) > count) {
587
- return [];
588
- }
589
- }
590
-
591
- const ids = params.id
592
- ? [BigInt(params.id)]
593
- : Array.from({ length: Number(count) }, (_, i) => count - BigInt(i));
594
-
595
- const proposalDataCall = Promise.all(
596
- ids.map((id) =>
597
- params.environment.contracts.governor?.read.proposals([id]),
598
- ),
599
- );
600
-
601
- const proposalStateCall = Promise.all(
602
- ids.map((id) =>
603
- params.environment.contracts.governor?.read.state([id]),
604
- ),
605
- );
606
-
607
- const [proposalsData, proposalsState] = await Promise.all([
608
- proposalDataCall,
609
- proposalStateCall,
610
- ]);
611
-
612
- const proposals = proposalsData?.map((item, index: number) => {
613
- const state = proposalsState?.[index]!;
614
-
615
- const [
616
- id,
617
- proposer,
618
- eta,
619
- startTimestamp,
620
- endTimestamp,
621
- startBlock,
622
- forVotes,
623
- againstVotes,
624
- abstainVotes,
625
- totalVotes,
626
- canceled,
627
- executed,
628
- ] = item!;
629
-
630
- const proposal: Proposal = {
631
- chainId: params.environment.chainId,
632
- id: Number(id),
633
- proposalId: Number(id),
634
- proposer,
635
- eta: Number(eta),
636
- startTimestamp: Number(startTimestamp),
637
- endTimestamp: Number(endTimestamp),
638
- startBlock: Number(startBlock),
639
- forVotes: new Amount(forVotes, 18),
640
- againstVotes: new Amount(againstVotes, 18),
641
- abstainVotes: new Amount(abstainVotes, 18),
642
- totalVotes: new Amount(totalVotes, 18),
643
- canceled,
644
- executed,
645
- quorum: new Amount(quorum, 18),
646
- state,
647
- };
648
-
649
- return proposal;
650
- });
651
-
652
- return proposals;
653
- } else {
654
- return [];
655
- }
656
- } catch (error) {
657
- console.warn(
658
- `[getProposalData] RPC failed for chain ${params.environment.chainId}:`,
659
- error,
660
- );
661
- params.environment.onError?.(error, {
662
- source: "governance-proposals",
663
- chainId: params.environment.chainId,
664
- });
665
- return [];
666
- }
667
- };
668
-
669
- /**
670
- * Get cross-chain proposal data (Ponder-based, for Moonriver)
671
- */
672
- export const getCrossChainProposalData = async (params: {
673
- environment: Environment;
674
- id?: number;
675
- }) => {
676
- try {
677
- if (params.environment.contracts.governor) {
678
- const xcGovernanceSettings = params.environment.custom.governance;
679
- if (
680
- params.environment.contracts.multichainGovernor &&
681
- xcGovernanceSettings &&
682
- xcGovernanceSettings.chainIds.length > 0
683
- ) {
684
- const xcEnvironments = xcGovernanceSettings.chainIds
685
- .map((chainId) =>
686
- (Object.values(publicEnvironments) as Environment[]).find(
687
- (env) => env.chainId === chainId,
688
- ),
689
- )
690
- .filter((xcEnvironment) => !!xcEnvironment)
691
- .filter(
692
- (xcEnvironment) =>
693
- xcEnvironment!.custom?.wormhole?.chainId &&
694
- xcEnvironment!.contracts.voteCollector,
695
- );
696
-
697
- const [xcCount, xcQuorum] = await Promise.all([
698
- params.environment.contracts.multichainGovernor.read.proposalCount(),
699
- params.environment.contracts.multichainGovernor.read.quorum(),
700
- ]);
701
-
702
- if (params.id) {
703
- params.id =
704
- Number(params.id) -
705
- (params.environment.custom?.governance?.proposalIdOffset || 0);
706
-
707
- if (params.id < 0) return [];
708
- if (BigInt(params.id) > xcCount) return [];
709
- }
710
-
711
- const ids = params.id
712
- ? [BigInt(params.id)]
713
- : Array.from(
714
- { length: Number(xcCount) },
715
- (_, i) => xcCount - BigInt(i),
716
- );
717
-
718
- const xcProposalsDataCall = Promise.all(
719
- ids.map((id) =>
720
- params.environment.contracts.multichainGovernor?.read.proposals([
721
- id,
722
- ]),
723
- ),
724
- );
725
-
726
- const xcProposalsStateCall = Promise.all(
727
- ids.map((id) =>
728
- params.environment.contracts.multichainGovernor?.read.state([id]),
729
- ),
730
- );
731
-
732
- const xcVotesCall = Promise.all(
733
- xcEnvironments.map((xcEnvironment) =>
734
- Promise.all(
735
- ids.map((id) =>
736
- params.environment.contracts.multichainGovernor?.read.chainVoteCollectorVotes(
737
- [(xcEnvironment!.custom as any).wormhole.chainId, id],
738
- ),
739
- ),
740
- ),
741
- ),
742
- );
743
-
744
- const [xcProposalsData, xcProposalsState, xcVotes] = await Promise.all([
745
- xcProposalsDataCall,
746
- xcProposalsStateCall,
747
- xcVotesCall,
748
- ]);
749
-
750
- const proposals = ids.map((xcId, proposalIndex: number) => {
751
- const state = xcProposalsState?.[proposalIndex]!;
752
- const id =
753
- Number(xcId) +
754
- (params.environment.custom?.governance?.proposalIdOffset || 0);
755
-
756
- const votesCollected = false;
757
-
758
- const votes = xcVotes.reduce(
759
- (prevVotes, currVotes) => {
760
- const voteData = currVotes[proposalIndex];
761
- if (!voteData) {
762
- return prevVotes;
763
- }
764
-
765
- // chainVoteCollectorVotes returns [forVotes, againstVotes, abstainVotes]
766
- const forVotes = voteData[0] || 0n;
767
- const againstVotes = voteData[1] || 0n;
768
- const abstainVotes = voteData[2] || 0n;
769
- const totalVotes = forVotes + againstVotes + abstainVotes;
770
-
771
- return {
772
- totalVotes: prevVotes.totalVotes + totalVotes,
773
- forVotes: prevVotes.forVotes + forVotes,
774
- againstVotes: prevVotes.againstVotes + againstVotes,
775
- abstainVotes: prevVotes.abstainVotes + abstainVotes,
776
- };
777
- },
778
- {
779
- totalVotes: 0n,
780
- forVotes: 0n,
781
- againstVotes: 0n,
782
- abstainVotes: 0n,
783
- },
784
- );
785
-
786
- const proposalData = xcProposalsData?.[proposalIndex];
787
- if (!proposalData) {
788
- throw new Error(
789
- `Proposal data not found for index ${proposalIndex}`,
790
- );
791
- }
792
-
793
- const [
794
- proposer,
795
- _voteSnapshotTimestamp,
796
- votingStartTime,
797
- votingEndTime,
798
- crossChainVoteCollectionEndTimestamp,
799
- voteSnapshotBlock,
800
- proposalForVotes,
801
- proposalAgainstVotes,
802
- proposalAbstainVotes,
803
- proposalTotalVotes,
804
- canceled,
805
- executed,
806
- ] = proposalData;
807
-
808
- const multichainState = (
809
- MultichainProposalStateMapping as { [key: number]: ProposalState }
810
- )[state]!;
811
-
812
- const proposal: Proposal = {
813
- chainId: params.environment.chainId,
814
- id,
815
- proposalId: Number(xcId),
816
- proposer,
817
- eta: Number(crossChainVoteCollectionEndTimestamp),
818
- startTimestamp: Number(votingStartTime),
819
- endTimestamp: Number(votingEndTime),
820
- startBlock: Number(voteSnapshotBlock),
821
- forVotes: new Amount(proposalForVotes + votes.forVotes, 18),
822
- againstVotes: new Amount(
823
- proposalAgainstVotes + votes.againstVotes,
824
- 18,
825
- ),
826
- abstainVotes: new Amount(
827
- proposalAbstainVotes + votes.abstainVotes,
828
- 18,
829
- ),
830
- totalVotes: new Amount(proposalTotalVotes + votes.totalVotes, 18),
831
- canceled,
832
- executed,
833
- quorum: new Amount(xcQuorum, 18),
834
- state: multichainState,
835
- multichain: {
836
- id: Number(xcId),
837
- votesCollected,
838
- },
839
- };
840
-
841
- return proposal;
842
- });
843
-
844
- return proposals;
845
- } else {
846
- return [];
847
- }
848
- } else {
849
- return [];
850
- }
851
- } catch (error) {
852
- console.warn(
853
- `[getCrossChainProposalData] RPC failed for chain ${params.environment.chainId}:`,
854
- error,
855
- );
856
- params.environment.onError?.(error, {
857
- source: "governance-proposals",
858
- chainId: params.environment.chainId,
859
- });
860
- return [];
861
- }
862
- };
863
-
864
- export const appendProposalExtendedData = (
865
- proposals: Proposal[],
866
- extendedDatas: PonderExtendedProposalData[],
867
- ) => {
868
- proposals.forEach((proposal) => {
869
- const extendedData = extendedDatas.find(
870
- (item) => item.id === proposal.proposalId,
871
- );
872
-
873
- if (extendedData) {
874
- proposal.title = extendedData.title;
875
- proposal.calldatas = extendedData.calldatas;
876
- proposal.description = extendedData.description;
877
- proposal.signatures = extendedData.signatures;
878
- proposal.stateChanges = extendedData.stateChanges.map((change) => ({
879
- blockNumber: change.blockNumber,
880
- transactionHash: change.transactionHash,
881
- state: change.state,
882
- chainId: proposal.chainId,
883
- }));
884
- proposal.subtitle = extendedData.subtitle;
885
- proposal.targets = extendedData.targets;
886
- }
887
- });
888
- };
889
-
890
- export const getExtendedProposalData = async (params: {
891
- environment: Environment;
892
- id?: number;
893
- }): Promise<PonderExtendedProposalData[]> => {
894
- const result: PonderExtendedProposalData[] = [];
895
- let lastId = -1;
896
- let shouldContinue = true;
897
- const MAX_PAGES = 100;
898
- let page = 0;
899
-
900
- try {
901
- while (shouldContinue && page < MAX_PAGES) {
902
- page++;
903
- const response = await postWithRetry<{
904
- data: {
905
- proposals: {
906
- items: {
907
- proposalId: number;
908
- description: string;
909
- targets: string[];
910
- calldatas: string[];
911
- signatures: string[];
912
- stateChanges: {
913
- items: {
914
- txnHash: string;
915
- blockNumber: number;
916
- newState: string;
917
- }[];
918
- };
919
- }[];
920
- };
921
- };
922
- }>("https://ponder-eu2.moonwell.fi", {
923
- query: `
924
- query {
925
- proposals(
926
- limit: 1000,
927
- orderDirection: "desc",
928
- orderBy: "proposalId",
929
- where: {
930
- chainId: ${params.environment.chainId}
931
- ${params.id ? `, proposalId: ${params.id}` : lastId >= 0 ? `, proposalId_lt: ${lastId}` : ""}
932
- }
933
- ) {
934
- items {
935
- id
936
- proposalId
937
- description
938
- targets
939
- calldatas
940
- signatures
941
- stateChanges(orderBy: "blockNumber") {
942
- items {
943
- txnHash
944
- blockNumber
945
- newState
946
- }
947
- }
948
- }
949
- }
950
- }
951
- `,
952
- });
953
-
954
- if (response.status === 200 && response.data?.data?.proposals) {
955
- const proposals = response.data.data.proposals.items.map((item) => {
956
- const extendedProposalData: PonderExtendedProposalData = {
957
- id: item.proposalId,
958
- title: `Proposal #${item.proposalId}`,
959
- subtitle: extractProposalSubtitle(item.description),
960
- description: item.description,
961
- calldatas: item.calldatas,
962
- signatures: item.signatures,
963
- stateChanges: item.stateChanges.items.map((change) => {
964
- return {
965
- blockNumber: change.blockNumber,
966
- state: change.newState,
967
- transactionHash: change.txnHash,
968
- };
969
- }),
970
- targets: item.targets,
971
- };
972
-
973
- return extendedProposalData;
974
- });
975
-
976
- if (proposals.length < 1000 || proposals.length === 0) {
977
- shouldContinue = false;
978
- } else {
979
- lastId = last(proposals)!.id;
980
- }
981
-
982
- result.push(...proposals);
983
- } else {
984
- shouldContinue = false;
985
- }
986
- }
987
- } catch (error) {
988
- console.warn(
989
- `[getExtendedProposalData] Ponder failed for chain ${params.environment.chainId}:`,
990
- error,
991
- );
992
- params.environment.onError?.(error, {
993
- source: "governance-proposals",
994
- chainId: params.environment.chainId,
995
- });
996
- return result;
997
- }
998
-
999
- return result;
1000
- };