@moonwell-fi/moonwell-sdk 0.20.2 → 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 (98) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/_cjs/actions/governance/common.js +9 -7
  3. package/_cjs/actions/governance/common.js.map +1 -1
  4. package/_cjs/actions/governance/getStakingInfo.js +1 -1
  5. package/_cjs/actions/governance/getStakingInfo.js.map +1 -1
  6. package/_cjs/actions/governance/getUserStakingInfo.js +2 -2
  7. package/_cjs/actions/governance/getUserStakingInfo.js.map +1 -1
  8. package/_cjs/actions/governance/governor-api-client.js +3 -2
  9. package/_cjs/actions/governance/governor-api-client.js.map +1 -1
  10. package/_cjs/actions/governance/proposals/common.js +11 -296
  11. package/_cjs/actions/governance/proposals/common.js.map +1 -1
  12. package/_cjs/actions/governance/proposals/getProposal.js +9 -25
  13. package/_cjs/actions/governance/proposals/getProposal.js.map +1 -1
  14. package/_cjs/actions/governance/proposals/getProposals.js +23 -14
  15. package/_cjs/actions/governance/proposals/getProposals.js.map +1 -1
  16. package/_cjs/actions/morpho/user-rewards/common.js +2 -1
  17. package/_cjs/actions/morpho/user-rewards/common.js.map +1 -1
  18. package/_cjs/common/lunar-indexer-helpers.js +6 -0
  19. package/_cjs/common/lunar-indexer-helpers.js.map +1 -1
  20. package/_cjs/environments/definitions/base/environment.js +2 -1
  21. package/_cjs/environments/definitions/base/environment.js.map +1 -1
  22. package/_cjs/environments/definitions/ethereum/environment.js +2 -1
  23. package/_cjs/environments/definitions/ethereum/environment.js.map +1 -1
  24. package/_cjs/environments/definitions/moonbeam/environment.js +2 -1
  25. package/_cjs/environments/definitions/moonbeam/environment.js.map +1 -1
  26. package/_cjs/environments/definitions/optimism/environment.js +2 -1
  27. package/_cjs/environments/definitions/optimism/environment.js.map +1 -1
  28. package/_cjs/errors/version.js +1 -1
  29. package/_cjs/types/proposal.js.map +1 -1
  30. package/_esm/actions/governance/common.js +12 -7
  31. package/_esm/actions/governance/common.js.map +1 -1
  32. package/_esm/actions/governance/getStakingInfo.js +1 -1
  33. package/_esm/actions/governance/getStakingInfo.js.map +1 -1
  34. package/_esm/actions/governance/getUserStakingInfo.js +2 -2
  35. package/_esm/actions/governance/getUserStakingInfo.js.map +1 -1
  36. package/_esm/actions/governance/governor-api-client.js +21 -4
  37. package/_esm/actions/governance/governor-api-client.js.map +1 -1
  38. package/_esm/actions/governance/ipfs.js +2 -2
  39. package/_esm/actions/governance/proposals/common.js +28 -302
  40. package/_esm/actions/governance/proposals/common.js.map +1 -1
  41. package/_esm/actions/governance/proposals/getProposal.js +23 -35
  42. package/_esm/actions/governance/proposals/getProposal.js.map +1 -1
  43. package/_esm/actions/governance/proposals/getProposals.js +48 -20
  44. package/_esm/actions/governance/proposals/getProposals.js.map +1 -1
  45. package/_esm/actions/morpho/user-rewards/common.js +2 -1
  46. package/_esm/actions/morpho/user-rewards/common.js.map +1 -1
  47. package/_esm/common/lunar-indexer-helpers.js +23 -0
  48. package/_esm/common/lunar-indexer-helpers.js.map +1 -1
  49. package/_esm/environments/definitions/base/environment.js +2 -1
  50. package/_esm/environments/definitions/base/environment.js.map +1 -1
  51. package/_esm/environments/definitions/ethereum/environment.js +2 -1
  52. package/_esm/environments/definitions/ethereum/environment.js.map +1 -1
  53. package/_esm/environments/definitions/moonbeam/environment.js +2 -1
  54. package/_esm/environments/definitions/moonbeam/environment.js.map +1 -1
  55. package/_esm/environments/definitions/optimism/environment.js +2 -1
  56. package/_esm/environments/definitions/optimism/environment.js.map +1 -1
  57. package/_esm/errors/version.js +1 -1
  58. package/_esm/types/proposal.js.map +1 -1
  59. package/_types/actions/governance/common.d.ts +3 -3
  60. package/_types/actions/governance/common.d.ts.map +1 -1
  61. package/_types/actions/governance/getUserStakingInfo.d.ts.map +1 -1
  62. package/_types/actions/governance/getUserVoteReceipt.d.ts +4 -4
  63. package/_types/actions/governance/governor-api-client.d.ts +34 -4
  64. package/_types/actions/governance/governor-api-client.d.ts.map +1 -1
  65. package/_types/actions/governance/ipfs.d.ts +2 -2
  66. package/_types/actions/governance/proposals/common.d.ts +12 -39
  67. package/_types/actions/governance/proposals/common.d.ts.map +1 -1
  68. package/_types/actions/governance/proposals/getProposal.d.ts +2 -1
  69. package/_types/actions/governance/proposals/getProposal.d.ts.map +1 -1
  70. package/_types/actions/governance/proposals/getProposals.d.ts.map +1 -1
  71. package/_types/actions/morpho/user-rewards/common.d.ts.map +1 -1
  72. package/_types/common/lunar-indexer-helpers.d.ts +19 -0
  73. package/_types/common/lunar-indexer-helpers.d.ts.map +1 -1
  74. package/_types/environments/definitions/base/environment.d.ts.map +1 -1
  75. package/_types/environments/definitions/ethereum/environment.d.ts.map +1 -1
  76. package/_types/environments/definitions/moonbeam/environment.d.ts.map +1 -1
  77. package/_types/environments/definitions/optimism/environment.d.ts.map +1 -1
  78. package/_types/errors/version.d.ts +1 -1
  79. package/_types/types/proposal.d.ts +11 -0
  80. package/_types/types/proposal.d.ts.map +1 -1
  81. package/actions/governance/common.ts +14 -5
  82. package/actions/governance/getStakingInfo.ts +1 -1
  83. package/actions/governance/getUserStakingInfo.ts +4 -1
  84. package/actions/governance/getUserVoteReceipt.ts +4 -4
  85. package/actions/governance/governor-api-client.ts +35 -4
  86. package/actions/governance/ipfs.ts +2 -2
  87. package/actions/governance/proposals/common.ts +28 -466
  88. package/actions/governance/proposals/getProposal.ts +25 -49
  89. package/actions/governance/proposals/getProposals.ts +57 -30
  90. package/actions/morpho/user-rewards/common.ts +2 -1
  91. package/common/lunar-indexer-helpers.ts +26 -0
  92. package/environments/definitions/base/environment.ts +2 -2
  93. package/environments/definitions/ethereum/environment.ts +2 -2
  94. package/environments/definitions/moonbeam/environment.ts +2 -2
  95. package/environments/definitions/optimism/environment.ts +2 -2
  96. package/errors/version.ts +1 -1
  97. package/package.json +1 -1
  98. package/types/proposal.ts +11 -0
@@ -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
- };
@@ -6,17 +6,13 @@ import type { Chain, Environment } from "../../../environments/index.js";
6
6
  import { type Proposal, ProposalState } from "../../../types/proposal.js";
7
7
  import {
8
8
  type ApiProposal,
9
- SUPPORTED_GOVERNOR_CHAIN_IDS,
9
+ MULTIGOV_PROPOSAL_FALLBACK_CHAIN_IDS,
10
10
  fetchProposal,
11
11
  isNotFoundError,
12
12
  } from "../governor-api-client.js";
13
13
  import { resolveIpfsDescriptions } from "../ipfs.js";
14
14
  import {
15
- appendProposalExtendedData,
16
15
  formatApiProposalData,
17
- getCrossChainProposalData,
18
- getExtendedProposalData,
19
- getProposalData,
20
16
  getProposalsOnChainData,
21
17
  readCrossChainQuorums,
22
18
  } from "./common.js";
@@ -28,7 +24,8 @@ export type GetProposalParameters<
28
24
  proposalId: number;
29
25
  /**
30
26
  * The chain the proposal lives on (1 = Ethereum multigov,
31
- * 1284 = Moonbeam historical). When omitted, both are tried in turn.
27
+ * 1284 = Moonbeam historical, 1285 = Moonriver legacy). When omitted, the
28
+ * supported chains are tried in turn.
32
29
  */
33
30
  chainId?: number;
34
31
  };
@@ -51,7 +48,7 @@ export async function getProposal<
51
48
 
52
49
  // Ethereum-home multigov proposals are served by the same Governor API as
53
50
  // historical Moonbeam ones (the lunar indexer fans out both chainIds), so
54
- // route them through `getMoonbeamProposal` using the Moonbeam env as the
51
+ // route them through `getGovernorApiProposal` using the Moonbeam env as the
55
52
  // indexer source. Without this, a caller resolving the env by `chainId: 1`
56
53
  // would bail out on the `!moonbeam && !moonriver` check below and the page
57
54
  // reload path returns undefined.
@@ -62,7 +59,7 @@ export async function getProposal<
62
59
  if (!moonbeamEnv) {
63
60
  return undefined;
64
61
  }
65
- return getMoonbeamProposal(
62
+ return getGovernorApiProposal(
66
63
  moonbeamEnv,
67
64
  proposalId,
68
65
  args.chainId ?? mainnet.id,
@@ -77,25 +74,29 @@ export async function getProposal<
77
74
  }
78
75
 
79
76
  if (environment.chainId === moonbeam.id) {
80
- return getMoonbeamProposal(environment, proposalId, args.chainId);
77
+ return getGovernorApiProposal(environment, proposalId, args.chainId);
81
78
  }
82
- return getMoonriverProposal(environment, proposalId);
79
+ return getGovernorApiProposal(environment, proposalId, moonriver.id);
83
80
  }
84
81
 
85
82
  /**
86
- * Fetch a single proposal for Moonbeam using the Governor API.
83
+ * Fetch a single proposal from the Governor API (Moonbeam/Ethereum multigov or
84
+ * Moonriver legacy governor).
87
85
  *
88
86
  * When `chainId` is provided we hit only that chain. When omitted we try the
89
- * supported chains in order (Ethereum first since that's where active multigov
90
- * proposals live) and fall back on `NotFoundError`. Real outages (5xx, network
91
- * errors) propagate so callers can distinguish "missing" from "broken".
87
+ * multigov chains in order (Ethereum first since that's where active multigov
88
+ * proposals live) and fall back on `NotFoundError`. Moonriver (1285) is
89
+ * deliberately absent from the fallback it has its own explicit route, and a
90
+ * bare lookup resolving to it through a non-Moonriver env would be degraded.
91
+ * Real outages (5xx, network errors) propagate so callers can distinguish
92
+ * "missing" from "broken".
92
93
  */
93
- async function getMoonbeamProposal(
94
+ async function getGovernorApiProposal(
94
95
  governanceEnvironment: Environment,
95
96
  proposalId: number,
96
97
  chainId?: number,
97
98
  ): Promise<Proposal | undefined> {
98
- const tryChains = chainId ? [chainId] : SUPPORTED_GOVERNOR_CHAIN_IDS;
99
+ const tryChains = chainId ? [chainId] : MULTIGOV_PROPOSAL_FALLBACK_CHAIN_IDS;
99
100
 
100
101
  let apiProposal: ApiProposal | undefined;
101
102
  for (const cid of tryChains) {
@@ -180,11 +181,18 @@ async function getMoonbeamProposal(
180
181
  description: apiProposal.description,
181
182
  targets: apiProposal.targets,
182
183
  calldatas: apiProposal.calldatas,
183
- signatures: [],
184
+ // Legacy-governor proposals (Moonriver, early Moonbeam) carry the function
185
+ // signature separately from the selector-less calldata; pass it through so
186
+ // consumers can decode the call. Empty for multichain-governor proposals.
187
+ signatures: apiProposal.signatures ?? [],
184
188
  stateChanges: formattedData.stateChanges,
185
189
  environment: governanceEnvironment,
186
190
  };
187
191
 
192
+ if (apiProposal.snapshotBlocks) {
193
+ proposal.snapshotBlocks = apiProposal.snapshotBlocks;
194
+ }
195
+
188
196
  if (isMultichain) {
189
197
  proposal.multichain = {
190
198
  id: apiProposal.proposalId,
@@ -194,35 +202,3 @@ async function getMoonbeamProposal(
194
202
 
195
203
  return proposal;
196
204
  }
197
-
198
- /**
199
- * Fetch a single proposal for Moonriver using the old Ponder-based approach
200
- */
201
- async function getMoonriverProposal(
202
- governanceEnvironment: Environment,
203
- proposalId: number,
204
- ): Promise<Proposal | undefined> {
205
- const [_proposals, _xcProposals, _extendedDatas] = await Promise.all([
206
- getProposalData({ environment: governanceEnvironment, id: proposalId }),
207
- getCrossChainProposalData({
208
- environment: governanceEnvironment,
209
- id: proposalId,
210
- }),
211
- getExtendedProposalData({
212
- environment: governanceEnvironment,
213
- id: proposalId,
214
- }),
215
- ]);
216
-
217
- const proposals = [..._proposals, ..._xcProposals];
218
-
219
- proposals.forEach((proposal) => {
220
- proposal.environment = governanceEnvironment;
221
- });
222
-
223
- appendProposalExtendedData(proposals, _extendedDatas);
224
-
225
- return proposals.find(
226
- (p) => p.proposalId === proposalId || p.id === proposalId,
227
- );
228
- }