@moonwell-fi/moonwell-sdk 0.21.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/README.md +9 -3
- package/_cjs/actions/beam/common.js +1 -2
- package/_cjs/actions/beam/common.js.map +1 -1
- package/_cjs/actions/core/markets/common.js +146 -96
- package/_cjs/actions/core/markets/common.js.map +1 -1
- package/_cjs/actions/governance/getDelegates.js +2 -7
- package/_cjs/actions/governance/getDelegates.js.map +1 -1
- package/_cjs/actions/governance/getGovernanceTokenInfo.js +7 -12
- package/_cjs/actions/governance/getGovernanceTokenInfo.js.map +1 -1
- package/_cjs/actions/governance/getUserVoteReceipt.js +5 -4
- package/_cjs/actions/governance/getUserVoteReceipt.js.map +1 -1
- package/_cjs/actions/governance/governor-api-client.js.map +1 -1
- package/_cjs/actions/governance/proposals/common.js +73 -6
- package/_cjs/actions/governance/proposals/common.js.map +1 -1
- package/_cjs/actions/governance/proposals/getProposal.js +6 -73
- package/_cjs/actions/governance/proposals/getProposal.js.map +1 -1
- package/_cjs/actions/governance/proposals/getProposals.js +12 -92
- package/_cjs/actions/governance/proposals/getProposals.js.map +1 -1
- package/_cjs/client/createMoonwellClient.js +17 -15
- package/_cjs/client/createMoonwellClient.js.map +1 -1
- package/_cjs/common/index.js +4 -4
- package/_cjs/common/index.js.map +1 -1
- package/_cjs/environments/definitions/governance.js +1 -8
- package/_cjs/environments/definitions/governance.js.map +1 -1
- package/_cjs/environments/index.js +28 -42
- package/_cjs/environments/index.js.map +1 -1
- package/_cjs/environments/types/config.js.map +1 -1
- package/_cjs/errors/version.js +1 -1
- package/_esm/actions/beam/common.js +3 -2
- package/_esm/actions/beam/common.js.map +1 -1
- package/_esm/actions/core/markets/common.js +192 -104
- package/_esm/actions/core/markets/common.js.map +1 -1
- package/_esm/actions/governance/getDelegates.js +8 -8
- package/_esm/actions/governance/getDelegates.js.map +1 -1
- package/_esm/actions/governance/getGovernanceTokenInfo.js +20 -13
- package/_esm/actions/governance/getGovernanceTokenInfo.js.map +1 -1
- package/_esm/actions/governance/getUserVoteReceipt.js +16 -5
- package/_esm/actions/governance/getUserVoteReceipt.js.map +1 -1
- package/_esm/actions/governance/governor-api-client.js +14 -5
- package/_esm/actions/governance/governor-api-client.js.map +1 -1
- package/_esm/actions/governance/proposals/common.js +122 -10
- package/_esm/actions/governance/proposals/common.js.map +1 -1
- package/_esm/actions/governance/proposals/getProposal.js +16 -93
- package/_esm/actions/governance/proposals/getProposal.js.map +1 -1
- package/_esm/actions/governance/proposals/getProposals.js +37 -132
- package/_esm/actions/governance/proposals/getProposals.js.map +1 -1
- package/_esm/client/createMoonwellClient.js +24 -16
- package/_esm/client/createMoonwellClient.js.map +1 -1
- package/_esm/common/index.js +15 -4
- package/_esm/common/index.js.map +1 -1
- package/_esm/environments/definitions/governance.js +5 -9
- package/_esm/environments/definitions/governance.js.map +1 -1
- package/_esm/environments/index.js +2 -14
- package/_esm/environments/index.js.map +1 -1
- package/_esm/environments/types/config.js.map +1 -1
- package/_esm/errors/version.js +1 -1
- package/_types/actions/beam/common.d.ts +1 -1
- package/_types/actions/beam/common.d.ts.map +1 -1
- package/_types/actions/core/markets/common.d.ts.map +1 -1
- package/_types/actions/governance/getGovernanceTokenInfo.d.ts.map +1 -1
- package/_types/actions/governance/getUserVoteReceipt.d.ts.map +1 -1
- package/_types/actions/governance/governor-api-client.d.ts +14 -5
- package/_types/actions/governance/governor-api-client.d.ts.map +1 -1
- package/_types/actions/governance/proposals/common.d.ts +41 -6
- package/_types/actions/governance/proposals/common.d.ts.map +1 -1
- package/_types/actions/governance/proposals/getProposal.d.ts +1 -1
- package/_types/actions/governance/proposals/getProposal.d.ts.map +1 -1
- package/_types/actions/governance/proposals/getProposals.d.ts +1 -1
- package/_types/actions/governance/proposals/getProposals.d.ts.map +1 -1
- package/_types/client/createMoonwellClient.d.ts +9 -839
- package/_types/client/createMoonwellClient.d.ts.map +1 -1
- package/_types/common/index.d.ts +11 -0
- package/_types/common/index.d.ts.map +1 -1
- package/_types/environments/definitions/governance.d.ts +0 -7
- package/_types/environments/definitions/governance.d.ts.map +1 -1
- package/_types/environments/index.d.ts +5 -516
- package/_types/environments/index.d.ts.map +1 -1
- package/_types/environments/types/config.d.ts +14 -0
- package/_types/environments/types/config.d.ts.map +1 -1
- package/_types/errors/version.d.ts +1 -1
- package/actions/beam/common.ts +4 -9
- package/actions/core/markets/common.ts +307 -166
- package/actions/governance/getDelegates.ts +8 -8
- package/actions/governance/getGovernanceTokenInfo.ts +23 -15
- package/actions/governance/getUserVoteReceipt.ts +19 -5
- package/actions/governance/governor-api-client.ts +14 -5
- package/actions/governance/proposals/common.ts +143 -10
- package/actions/governance/proposals/getProposal.ts +29 -112
- package/actions/governance/proposals/getProposals.ts +52 -153
- package/client/createMoonwellClient.ts +32 -40
- package/common/index.ts +17 -6
- package/environments/definitions/governance.ts +5 -9
- package/environments/index.ts +28 -70
- package/environments/types/config.ts +14 -0
- package/errors/version.ts +1 -1
- package/package.json +1 -1
- package/_cjs/environments/definitions/moonbeam/contracts.js +0 -22
- package/_cjs/environments/definitions/moonbeam/contracts.js.map +0 -1
- package/_cjs/environments/definitions/moonbeam/core-markets.js +0 -65
- package/_cjs/environments/definitions/moonbeam/core-markets.js.map +0 -1
- package/_cjs/environments/definitions/moonbeam/custom.js +0 -24
- package/_cjs/environments/definitions/moonbeam/custom.js.map +0 -1
- package/_cjs/environments/definitions/moonbeam/environment.js +0 -38
- package/_cjs/environments/definitions/moonbeam/environment.js.map +0 -1
- package/_cjs/environments/definitions/moonbeam/tokens.js +0 -176
- package/_cjs/environments/definitions/moonbeam/tokens.js.map +0 -1
- package/_cjs/environments/definitions/moonriver/contracts.js +0 -19
- package/_cjs/environments/definitions/moonriver/contracts.js.map +0 -1
- package/_cjs/environments/definitions/moonriver/core-markets.js +0 -46
- package/_cjs/environments/definitions/moonriver/core-markets.js.map +0 -1
- package/_cjs/environments/definitions/moonriver/custom.js +0 -12
- package/_cjs/environments/definitions/moonriver/custom.js.map +0 -1
- package/_cjs/environments/definitions/moonriver/environment.js +0 -36
- package/_cjs/environments/definitions/moonriver/environment.js.map +0 -1
- package/_cjs/environments/definitions/moonriver/tokens.js +0 -110
- package/_cjs/environments/definitions/moonriver/tokens.js.map +0 -1
- package/_esm/environments/definitions/moonbeam/contracts.js +0 -19
- package/_esm/environments/definitions/moonbeam/contracts.js.map +0 -1
- package/_esm/environments/definitions/moonbeam/core-markets.js +0 -62
- package/_esm/environments/definitions/moonbeam/core-markets.js.map +0 -1
- package/_esm/environments/definitions/moonbeam/custom.js +0 -21
- package/_esm/environments/definitions/moonbeam/custom.js.map +0 -1
- package/_esm/environments/definitions/moonbeam/environment.js +0 -32
- package/_esm/environments/definitions/moonbeam/environment.js.map +0 -1
- package/_esm/environments/definitions/moonbeam/tokens.js +0 -173
- package/_esm/environments/definitions/moonbeam/tokens.js.map +0 -1
- package/_esm/environments/definitions/moonriver/contracts.js +0 -16
- package/_esm/environments/definitions/moonriver/contracts.js.map +0 -1
- package/_esm/environments/definitions/moonriver/core-markets.js +0 -43
- package/_esm/environments/definitions/moonriver/core-markets.js.map +0 -1
- package/_esm/environments/definitions/moonriver/custom.js +0 -9
- package/_esm/environments/definitions/moonriver/custom.js.map +0 -1
- package/_esm/environments/definitions/moonriver/environment.js +0 -31
- package/_esm/environments/definitions/moonriver/environment.js.map +0 -1
- package/_esm/environments/definitions/moonriver/tokens.js +0 -107
- package/_esm/environments/definitions/moonriver/tokens.js.map +0 -1
- package/_types/environments/definitions/moonbeam/contracts.d.ts +0 -14
- package/_types/environments/definitions/moonbeam/contracts.d.ts.map +0 -1
- package/_types/environments/definitions/moonbeam/core-markets.d.ts +0 -57
- package/_types/environments/definitions/moonbeam/core-markets.d.ts.map +0 -1
- package/_types/environments/definitions/moonbeam/custom.d.ts +0 -25
- package/_types/environments/definitions/moonbeam/custom.d.ts.map +0 -1
- package/_types/environments/definitions/moonbeam/environment.d.ts +0 -265
- package/_types/environments/definitions/moonbeam/environment.d.ts.map +0 -1
- package/_types/environments/definitions/moonbeam/tokens.d.ts +0 -171
- package/_types/environments/definitions/moonbeam/tokens.d.ts.map +0 -1
- package/_types/environments/definitions/moonriver/contracts.d.ts +0 -11
- package/_types/environments/definitions/moonriver/contracts.d.ts.map +0 -1
- package/_types/environments/definitions/moonriver/core-markets.d.ts +0 -38
- package/_types/environments/definitions/moonriver/core-markets.d.ts.map +0 -1
- package/_types/environments/definitions/moonriver/custom.d.ts +0 -8
- package/_types/environments/definitions/moonriver/custom.d.ts.map +0 -1
- package/_types/environments/definitions/moonriver/environment.d.ts +0 -159
- package/_types/environments/definitions/moonriver/environment.d.ts.map +0 -1
- package/_types/environments/definitions/moonriver/tokens.d.ts +0 -105
- package/_types/environments/definitions/moonriver/tokens.d.ts.map +0 -1
- package/environments/definitions/moonbeam/contracts.ts +0 -19
- package/environments/definitions/moonbeam/core-markets.ts +0 -62
- package/environments/definitions/moonbeam/custom.ts +0 -21
- package/environments/definitions/moonbeam/environment.ts +0 -39
- package/environments/definitions/moonbeam/tokens.ts +0 -173
- package/environments/definitions/moonriver/contracts.ts +0 -16
- package/environments/definitions/moonriver/core-markets.ts +0 -43
- package/environments/definitions/moonriver/custom.ts +0 -9
- package/environments/definitions/moonriver/environment.ts +0 -38
- package/environments/definitions/moonriver/tokens.ts +0 -107
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isAddress } from "viem";
|
|
2
|
-
import { base, mainnet,
|
|
2
|
+
import { base, mainnet, optimism } from "viem/chains";
|
|
3
3
|
import type { MoonwellClient } from "../../client/createMoonwellClient.js";
|
|
4
4
|
import {
|
|
5
5
|
type Environment,
|
|
@@ -23,17 +23,17 @@ export async function getDelegates(
|
|
|
23
23
|
): GetDelegatesReturnType {
|
|
24
24
|
const logId = logger.start("getDelegates", "Starting to get delegates...");
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
// Any environment resolves the same `governanceIndexerUrl`; the Ethereum hub
|
|
27
|
+
// replaces Moonbeam here now that the sunset chain is gone (MOO-551).
|
|
28
|
+
const governanceEnvironment = publicEnvironments.ethereum;
|
|
27
29
|
|
|
28
30
|
const apiVoters = await fetchAllVoters(governanceEnvironment);
|
|
29
31
|
const forumProfiles = await getForumProfiles();
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
mainnet.id,
|
|
36
|
-
] as const;
|
|
33
|
+
// Moonbeam dropped with the sunset: the chain is halted, so it can no longer
|
|
34
|
+
// serve `getUserVotingPower`. Delegate totals therefore no longer include any
|
|
35
|
+
// WELL that remained on Moonbeam.
|
|
36
|
+
const targetChainIds = [base.id, optimism.id, mainnet.id] as const;
|
|
37
37
|
const envs = Object.values(client.environments as Environment[]).filter(
|
|
38
38
|
(env) =>
|
|
39
39
|
env.contracts.views !== undefined &&
|
|
@@ -19,6 +19,8 @@ export type GetGovernanceTokenInfoReturnType = Promise<
|
|
|
19
19
|
|
|
20
20
|
export async function getGovernanceTokenInfo(
|
|
21
21
|
_client: MoonwellClient,
|
|
22
|
+
// Retained for API compatibility, but WELL is the only governance token left
|
|
23
|
+
// now that MFAM went with Moonriver (MOO-551), so there is nothing to branch on.
|
|
22
24
|
args: GetGovernanceTokenInfoParameters,
|
|
23
25
|
): GetGovernanceTokenInfoReturnType {
|
|
24
26
|
const logId = logger.start(
|
|
@@ -26,22 +28,28 @@ export async function getGovernanceTokenInfo(
|
|
|
26
28
|
"Starting to get governance token info...",
|
|
27
29
|
);
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
// TypeScript already narrows `GovernanceToken` to "WELL", but a JS consumer
|
|
32
|
+
// still passing "MFAM" would otherwise receive WELL's supply as a normal
|
|
33
|
+
// success — silently wrong data where TS consumers get a compile error.
|
|
34
|
+
// Reject it rather than let the removed token look like it still resolves.
|
|
35
|
+
if (args.governanceToken !== undefined && args.governanceToken !== "WELL") {
|
|
33
36
|
logger.end(logId);
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Unsupported governance token "${args.governanceToken}". MFAM was removed with the Moonriver sunset (MOO-551); WELL is the only governance token.`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
34
41
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
// WELL supply used to be read from Moonbeam, the original mint. That chain is
|
|
43
|
+
// halted (MOO-551), so the read now targets the Ethereum multigov hub — the
|
|
44
|
+
// home of live governance. Note this reports the hub's xWELL supply rather
|
|
45
|
+
// than Moonbeam's historical total, so the number can differ from pre-sunset
|
|
46
|
+
// releases. MFAM is gone entirely: Apollo governance ended with Moonriver.
|
|
47
|
+
const totalSupply =
|
|
48
|
+
await publicEnvironments.ethereum.contracts.governanceToken.read.totalSupply();
|
|
41
49
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
50
|
+
logger.end(logId);
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
totalSupply: new Amount(totalSupply || 0n, 18),
|
|
54
|
+
};
|
|
47
55
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Address, Chain } from "viem";
|
|
2
2
|
import type { MoonwellClient } from "../../client/createMoonwellClient.js";
|
|
3
|
-
import { Amount
|
|
3
|
+
import { Amount } from "../../common/index.js";
|
|
4
4
|
import type { NetworkParameterType } from "../../common/types.js";
|
|
5
|
+
import type { Environment } from "../../environments/index.js";
|
|
5
6
|
import type { VoteReceipt } from "../../types/voteReceipt.js";
|
|
6
7
|
import {
|
|
7
8
|
type ApiVoteReceipt,
|
|
@@ -10,6 +11,7 @@ import {
|
|
|
10
11
|
fetchUserVoteReceipt,
|
|
11
12
|
isNotFoundError,
|
|
12
13
|
} from "./governor-api-client.js";
|
|
14
|
+
import { resolveGovernanceEnvironment } from "./proposals/common.js";
|
|
13
15
|
|
|
14
16
|
export type GetUserVoteReceiptParameters<
|
|
15
17
|
environments,
|
|
@@ -50,8 +52,17 @@ export async function getUserVoteReceipt<
|
|
|
50
52
|
): GetUserVoteReceiptReturnType {
|
|
51
53
|
const { proposalId, userAddress, chainId } = args;
|
|
52
54
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
// `args.chainId` identifies the chain the PROPOSAL lives on, not the
|
|
56
|
+
// environment we read through — the same distinction `getProposal` draws
|
|
57
|
+
// (MOO-551). Resolving the environment from it meant an archive lookup
|
|
58
|
+
// (1284/1285) matched no registered environment post-sunset and returned `[]`,
|
|
59
|
+
// a third shape callers don't handle: every historical vote read as "didn't
|
|
60
|
+
// vote". Resolve the indexer source independently and pass `chainId` through
|
|
61
|
+
// untouched.
|
|
62
|
+
const governanceEnvironment = resolveGovernanceEnvironment(
|
|
63
|
+
Object.values(client.environments as Record<string, Environment>),
|
|
64
|
+
);
|
|
65
|
+
if (!governanceEnvironment) {
|
|
55
66
|
return [];
|
|
56
67
|
}
|
|
57
68
|
|
|
@@ -64,7 +75,7 @@ export async function getUserVoteReceipt<
|
|
|
64
75
|
for (const cid of tryChains) {
|
|
65
76
|
try {
|
|
66
77
|
const apiVoteReceipts = await fetchUserVoteReceipt(
|
|
67
|
-
|
|
78
|
+
governanceEnvironment,
|
|
68
79
|
cid,
|
|
69
80
|
proposalId,
|
|
70
81
|
userAddress,
|
|
@@ -87,7 +98,10 @@ export async function getUserVoteReceipt<
|
|
|
87
98
|
if (collected.length === 0) {
|
|
88
99
|
return [
|
|
89
100
|
{
|
|
90
|
-
|
|
101
|
+
// An explicit request names the chain the stub is about; a bare lookup
|
|
102
|
+
// queried every supported chain, so the source we read through is the
|
|
103
|
+
// only chain it can honestly claim.
|
|
104
|
+
chainId: chainId ?? governanceEnvironment.chainId,
|
|
91
105
|
proposalId,
|
|
92
106
|
account: userAddress,
|
|
93
107
|
voted: false,
|
|
@@ -15,11 +15,20 @@ const getGovernorApiUrl = (environment: Environment): string => {
|
|
|
15
15
|
* active multigov contract lives; Moonbeam follows for the historical archive,
|
|
16
16
|
* then Moonriver (legacy standalone governor).
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
* queried on every chain and non-empty receipts are concatenated
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
18
|
+
* The fan-out set for two consumers: `getUserVoteReceipt` (a bare proposalId is
|
|
19
|
+
* queried on every chain and non-empty receipts are concatenated) and
|
|
20
|
+
* `getProposals` (every chain's list is fetched and merged, so the archive stays
|
|
21
|
+
* listable after the sunset).
|
|
22
|
+
*
|
|
23
|
+
* Since MOO-551 no Moonbeam/Moonriver environment exists, so 1284 and 1285 are
|
|
24
|
+
* served through whichever governance environment resolved — reporting
|
|
25
|
+
* `quorum: 0` and no `eta` because nothing on-chain can be read for a halted
|
|
26
|
+
* chain. That degradation is the accepted post-sunset design here, not a bug.
|
|
27
|
+
*
|
|
28
|
+
* Still do NOT use this set as the *single-proposal* fallback in `getProposal`:
|
|
29
|
+
* a bare lookup must not silently resolve to a 1285 proposal it wasn't asked
|
|
30
|
+
* for — that stays an explicit `chainId` route (see
|
|
31
|
+
* `MULTIGOV_PROPOSAL_FALLBACK_CHAIN_IDS`).
|
|
23
32
|
*/
|
|
24
33
|
export const SUPPORTED_GOVERNOR_CHAIN_IDS = [1, 1284, 1285] as const;
|
|
25
34
|
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import axios from "axios";
|
|
2
|
+
import { mainnet } from "viem/chains";
|
|
2
3
|
import { Amount } from "../../../common/index.js";
|
|
3
4
|
import type { Environment } from "../../../environments/index.js";
|
|
4
5
|
import { publicEnvironments } from "../../../environments/index.js";
|
|
5
6
|
import {
|
|
6
7
|
MultichainProposalState,
|
|
7
8
|
MultichainProposalStateMapping,
|
|
9
|
+
type Proposal,
|
|
8
10
|
ProposalState,
|
|
9
11
|
} from "../../../types/proposal.js";
|
|
10
12
|
import type { ApiProposal } from "../governor-api-client.js";
|
|
@@ -176,14 +178,24 @@ export type ApiProposalFormatted = {
|
|
|
176
178
|
/**
|
|
177
179
|
* Derive a ProposalState value from API data alone (no on-chain read).
|
|
178
180
|
*
|
|
179
|
-
* Used for proposals whose chainId
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
181
|
+
* Used for proposals whose chainId has no resolvable environment, or whose
|
|
182
|
+
* on-chain state read failed. Since the sunset (MOO-551) this is the permanent
|
|
183
|
+
* — not transient — route for every Moonbeam (1284) and Moonriver (1285)
|
|
184
|
+
* proposal, because no environment exists for those chains any more.
|
|
183
185
|
*
|
|
184
|
-
* Precedence (highest wins):
|
|
186
|
+
* Precedence (highest wins):
|
|
187
|
+
* Executed → Canceled → Queued → Active → Succeeded/Defeated → Pending.
|
|
185
188
|
* Executed wins over Canceled because the SDK treats EXECUTED state changes as
|
|
186
189
|
* the terminal truth even when an earlier CANCELED event is present.
|
|
190
|
+
*
|
|
191
|
+
* Once voting has closed with no terminal state change, the outcome is decided
|
|
192
|
+
* from the tallies. Without that branch every defeated archive proposal fell
|
|
193
|
+
* through to `Pending` and listed as perpetually pending — the pre-sunset
|
|
194
|
+
* Moonbeam governor read used to report the real `Defeated`. The tallies are
|
|
195
|
+
* the only signal available here: quorum needs an RPC and reports 0 for the
|
|
196
|
+
* archive, so a for-majority that missed quorum reads as Succeeded rather than
|
|
197
|
+
* Defeated. That is the same degradation quorum already carries, and it is
|
|
198
|
+
* strictly closer to the truth than Pending.
|
|
187
199
|
*/
|
|
188
200
|
export const deriveProposalStateFromApi = (
|
|
189
201
|
formatted: ApiProposalFormatted,
|
|
@@ -199,6 +211,11 @@ export const deriveProposalStateFromApi = (
|
|
|
199
211
|
if (now >= apiProposal.votingStartTime && now <= apiProposal.votingEndTime) {
|
|
200
212
|
return ProposalState.Active;
|
|
201
213
|
}
|
|
214
|
+
if (now > apiProposal.votingEndTime) {
|
|
215
|
+
return formatted.forVotes.exponential > formatted.againstVotes.exponential
|
|
216
|
+
? ProposalState.Succeeded
|
|
217
|
+
: ProposalState.Defeated;
|
|
218
|
+
}
|
|
202
219
|
return ProposalState.Pending;
|
|
203
220
|
};
|
|
204
221
|
|
|
@@ -332,6 +349,26 @@ export const getEnvironmentByChainId = (
|
|
|
332
349
|
(e) => e.chainId === chainId,
|
|
333
350
|
);
|
|
334
351
|
|
|
352
|
+
/**
|
|
353
|
+
* The environment that drives Governor-API reads for the whole proposal surface.
|
|
354
|
+
*
|
|
355
|
+
* Before the sunset this was the caller's Moonbeam/Moonriver environment, and
|
|
356
|
+
* both `getProposals` and `getProposal` gated on one of those being registered.
|
|
357
|
+
* Those chains are gone (MOO-551), so governance is now homed on the Ethereum
|
|
358
|
+
* multigov hub. Every environment resolves the same `governanceIndexerUrl`, so
|
|
359
|
+
* any registered chain can serve the indexer — Ethereum is merely preferred
|
|
360
|
+
* because its `multichainGovernor` also answers the live quorum/state reads.
|
|
361
|
+
*
|
|
362
|
+
* Consequence for the historical archive: Moonbeam (1284) and Moonriver (1285)
|
|
363
|
+
* proposals still list, because the indexer keeps serving them, but nothing
|
|
364
|
+
* on-chain can be read for them any more. They surface with indexer-derived
|
|
365
|
+
* state and no quorum or eta — see `getProposalsOnChainData`'s `!homeEnv` path.
|
|
366
|
+
*/
|
|
367
|
+
export const resolveGovernanceEnvironment = (
|
|
368
|
+
environments: Environment[],
|
|
369
|
+
): Environment | undefined =>
|
|
370
|
+
environments.find((e) => e.chainId === mainnet.id) ?? environments[0];
|
|
371
|
+
|
|
335
372
|
export const readCrossChainQuorums = async (
|
|
336
373
|
apiProposals: ApiProposal[],
|
|
337
374
|
governanceEnvironment: Environment,
|
|
@@ -376,12 +413,18 @@ export const getProposalsOnChainData = async (
|
|
|
376
413
|
): Promise<ProposalOnChainData[]> => {
|
|
377
414
|
let quorum = 0n;
|
|
378
415
|
|
|
379
|
-
|
|
416
|
+
// The governance environment is now the Ethereum multigov hub (MOO-551), which
|
|
417
|
+
// has a `multichainGovernor` and no legacy `governor`. Read whichever the env
|
|
418
|
+
// actually wires, otherwise a hub-local proposal would report quorum 0.
|
|
419
|
+
const localQuorumRead = governanceEnvironment.contracts.governor
|
|
420
|
+
? () => governanceEnvironment.contracts.governor?.read.getQuorum()
|
|
421
|
+
: governanceEnvironment.contracts.multichainGovernor
|
|
422
|
+
? () => governanceEnvironment.contracts.multichainGovernor?.read.quorum()
|
|
423
|
+
: undefined;
|
|
424
|
+
|
|
425
|
+
if (localQuorumRead) {
|
|
380
426
|
try {
|
|
381
|
-
quorum =
|
|
382
|
-
governanceEnvironment.chainId === 1284
|
|
383
|
-
? await governanceEnvironment.contracts.governor.read.quorumVotes()
|
|
384
|
-
: await governanceEnvironment.contracts.governor.read.getQuorum();
|
|
427
|
+
quorum = (await localQuorumRead()) ?? 0n;
|
|
385
428
|
} catch (error) {
|
|
386
429
|
console.warn("Failed to fetch quorum:", error);
|
|
387
430
|
// Report so a swallowed quorum failure (which leaves quorum at 0n) is
|
|
@@ -571,3 +614,93 @@ export const getProposalsOnChainData = async (
|
|
|
571
614
|
|
|
572
615
|
return onChainDataList;
|
|
573
616
|
};
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Map an indexer proposal plus its on-chain data onto the public `Proposal`.
|
|
620
|
+
*
|
|
621
|
+
* `getProposal` and `getProposals` both used to carry their own copy of this
|
|
622
|
+
* block, and both carried a comment warning the other not to drift from it —
|
|
623
|
+
* jscpd measured 69 duplicated lines between them. One implementation removes
|
|
624
|
+
* the drift risk those comments were describing.
|
|
625
|
+
*/
|
|
626
|
+
export const mapApiProposalToProposal = (
|
|
627
|
+
apiProposal: ApiProposal,
|
|
628
|
+
onChainData: ProposalOnChainData,
|
|
629
|
+
governanceEnvironment: Environment,
|
|
630
|
+
): Proposal => {
|
|
631
|
+
const formattedData = formatApiProposalData(apiProposal);
|
|
632
|
+
// getProposalsOnChainData already classified this proposal and routed its
|
|
633
|
+
// reads accordingly; re-classifying here would drift and drop `multichain`.
|
|
634
|
+
const isMultichain = onChainData.isMultichain;
|
|
635
|
+
|
|
636
|
+
const now = Math.floor(Date.now() / 1000);
|
|
637
|
+
let proposalState = onChainData.state;
|
|
638
|
+
|
|
639
|
+
if (
|
|
640
|
+
proposalState === ProposalState.Pending &&
|
|
641
|
+
now >= apiProposal.votingStartTime &&
|
|
642
|
+
now <= apiProposal.votingEndTime
|
|
643
|
+
) {
|
|
644
|
+
proposalState = ProposalState.Active;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
if (formattedData.executed) {
|
|
648
|
+
proposalState = ProposalState.Executed;
|
|
649
|
+
} else if (
|
|
650
|
+
isMultichain &&
|
|
651
|
+
onChainData.votesCollected &&
|
|
652
|
+
now > apiProposal.votingEndTime &&
|
|
653
|
+
proposalState === ProposalState.Succeeded
|
|
654
|
+
) {
|
|
655
|
+
// Succeeded with collection done means "awaiting execution" — surface as
|
|
656
|
+
// Queued so the frontend renders the "Ready to Execute" timeline step.
|
|
657
|
+
// Defeated/Canceled/Executed must NOT be promoted: under the
|
|
658
|
+
// state-machine-based votesCollected those terminal states also satisfy
|
|
659
|
+
// `votesCollected: true`, so a `< Queued` gate would mislabel them.
|
|
660
|
+
proposalState = ProposalState.Queued;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
const proposal: Proposal = {
|
|
664
|
+
id: apiProposal.proposalId,
|
|
665
|
+
chainId: apiProposal.chainId,
|
|
666
|
+
proposalId: apiProposal.proposalId,
|
|
667
|
+
proposer: apiProposal.proposer as `0x${string}`,
|
|
668
|
+
eta: onChainData.eta,
|
|
669
|
+
startTimestamp: apiProposal.votingStartTime,
|
|
670
|
+
endTimestamp: apiProposal.votingEndTime,
|
|
671
|
+
startBlock: Number(apiProposal.blockNumber),
|
|
672
|
+
forVotes: formattedData.forVotes,
|
|
673
|
+
againstVotes: formattedData.againstVotes,
|
|
674
|
+
abstainVotes: formattedData.abstainVotes,
|
|
675
|
+
totalVotes: formattedData.totalVotes,
|
|
676
|
+
canceled: formattedData.canceled,
|
|
677
|
+
executed: formattedData.executed,
|
|
678
|
+
quorum: new Amount(onChainData.quorum, 18),
|
|
679
|
+
state: proposalState,
|
|
680
|
+
// Extended data
|
|
681
|
+
title: formattedData.title,
|
|
682
|
+
subtitle: formattedData.subtitle,
|
|
683
|
+
description: apiProposal.description,
|
|
684
|
+
targets: apiProposal.targets,
|
|
685
|
+
calldatas: apiProposal.calldatas,
|
|
686
|
+
// Legacy-governor proposals (Moonriver, early Moonbeam) carry the function
|
|
687
|
+
// signature separately from the selector-less calldata; pass it through so
|
|
688
|
+
// consumers can decode the call. Empty for multichain-governor proposals.
|
|
689
|
+
signatures: apiProposal.signatures ?? [],
|
|
690
|
+
stateChanges: formattedData.stateChanges,
|
|
691
|
+
environment: governanceEnvironment,
|
|
692
|
+
};
|
|
693
|
+
|
|
694
|
+
if (apiProposal.snapshotBlocks) {
|
|
695
|
+
proposal.snapshotBlocks = apiProposal.snapshotBlocks;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
if (isMultichain) {
|
|
699
|
+
proposal.multichain = {
|
|
700
|
+
id: apiProposal.proposalId,
|
|
701
|
+
votesCollected: onChainData.votesCollected,
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
return proposal;
|
|
706
|
+
};
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { mainnet, moonbeam, moonriver } from "viem/chains";
|
|
2
1
|
import type { MoonwellClient } from "../../../client/createMoonwellClient.js";
|
|
3
|
-
import { Amount, getEnvironmentFromArgs } from "../../../common/index.js";
|
|
4
2
|
import type { NetworkParameterType } from "../../../common/types.js";
|
|
5
3
|
import type { Chain, Environment } from "../../../environments/index.js";
|
|
6
|
-
import {
|
|
4
|
+
import type { Proposal } from "../../../types/proposal.js";
|
|
7
5
|
import {
|
|
8
6
|
type ApiProposal,
|
|
9
7
|
MULTIGOV_PROPOSAL_FALLBACK_CHAIN_IDS,
|
|
@@ -12,9 +10,10 @@ import {
|
|
|
12
10
|
} from "../governor-api-client.js";
|
|
13
11
|
import { resolveIpfsDescriptions } from "../ipfs.js";
|
|
14
12
|
import {
|
|
15
|
-
formatApiProposalData,
|
|
16
13
|
getProposalsOnChainData,
|
|
14
|
+
mapApiProposalToProposal,
|
|
17
15
|
readCrossChainQuorums,
|
|
16
|
+
resolveGovernanceEnvironment,
|
|
18
17
|
} from "./common.js";
|
|
19
18
|
|
|
20
19
|
export type GetProposalParameters<
|
|
@@ -41,42 +40,24 @@ export async function getProposal<
|
|
|
41
40
|
): GetProposalReturnType {
|
|
42
41
|
const { proposalId } = args;
|
|
43
42
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
// reload path returns undefined.
|
|
55
|
-
if (environment.chainId === mainnet.id) {
|
|
56
|
-
const moonbeamEnv = Object.values(
|
|
57
|
-
client.environments as Record<string, Environment>,
|
|
58
|
-
).find((e) => e.chainId === moonbeam.id);
|
|
59
|
-
if (!moonbeamEnv) {
|
|
60
|
-
return undefined;
|
|
61
|
-
}
|
|
62
|
-
return getGovernorApiProposal(
|
|
63
|
-
moonbeamEnv,
|
|
64
|
-
proposalId,
|
|
65
|
-
args.chainId ?? mainnet.id,
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (
|
|
70
|
-
environment.chainId !== moonbeam.id &&
|
|
71
|
-
environment.chainId !== moonriver.id
|
|
72
|
-
) {
|
|
43
|
+
// `args.chainId` identifies the chain the PROPOSAL lives on, which is no longer
|
|
44
|
+
// the same thing as the environment we read through. Before the sunset it was
|
|
45
|
+
// both, and the action resolved a Moonbeam/Moonriver environment from it —
|
|
46
|
+
// which now means a `chainId: 1284` lookup would resolve nothing and the whole
|
|
47
|
+
// historical archive would 404 (MOO-551). Resolve the indexer source
|
|
48
|
+
// independently and pass `chainId` through untouched.
|
|
49
|
+
const governanceEnvironment = resolveGovernanceEnvironment(
|
|
50
|
+
Object.values(client.environments as Record<string, Environment>),
|
|
51
|
+
);
|
|
52
|
+
if (!governanceEnvironment) {
|
|
73
53
|
return undefined;
|
|
74
54
|
}
|
|
75
55
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
56
|
+
return getGovernorApiProposal(
|
|
57
|
+
governanceEnvironment,
|
|
58
|
+
proposalId,
|
|
59
|
+
args.chainId,
|
|
60
|
+
);
|
|
80
61
|
}
|
|
81
62
|
|
|
82
63
|
/**
|
|
@@ -118,87 +99,23 @@ async function getGovernorApiProposal(
|
|
|
118
99
|
readCrossChainQuorums([apiProposal], governanceEnvironment),
|
|
119
100
|
]);
|
|
120
101
|
|
|
121
|
-
const formattedData = formatApiProposalData(apiProposal);
|
|
122
102
|
const onChainDataList = await getProposalsOnChainData(
|
|
123
103
|
[apiProposal],
|
|
124
104
|
governanceEnvironment,
|
|
125
105
|
{ crossChainQuorums },
|
|
126
106
|
);
|
|
127
|
-
const onChainData = onChainDataList[0]!;
|
|
128
|
-
// Single source of truth: getProposalsOnChainData already classified this
|
|
129
|
-
// proposal with the caller env's Artemis cutoff and used it to route the
|
|
130
|
-
// on-chain reads. Reusing it avoids the drift that left Moonbeam-homed
|
|
131
|
-
// local-target proposals (and hub-local Ethereum ones) without `multichain`.
|
|
132
|
-
const isMultichain = onChainData.isMultichain;
|
|
133
|
-
|
|
134
|
-
const now = Math.floor(Date.now() / 1000);
|
|
135
|
-
let proposalState = onChainData.state;
|
|
136
107
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
) {
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (formattedData.executed) {
|
|
146
|
-
proposalState = ProposalState.Executed;
|
|
147
|
-
} else if (
|
|
148
|
-
isMultichain &&
|
|
149
|
-
onChainData.votesCollected &&
|
|
150
|
-
now > apiProposal.votingEndTime &&
|
|
151
|
-
proposalState === ProposalState.Succeeded
|
|
152
|
-
) {
|
|
153
|
-
// Succeeded with collection done means "awaiting execution" — surface as
|
|
154
|
-
// Queued so the frontend renders the "Ready to Execute" timeline step.
|
|
155
|
-
// Defeated/Canceled/Executed must NOT be promoted: under the new
|
|
156
|
-
// state-machine-based votesCollected, those terminal states also satisfy
|
|
157
|
-
// `votesCollected: true`, so a `< Queued` gate would mislabel them.
|
|
158
|
-
proposalState = ProposalState.Queued;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const proposal: Proposal = {
|
|
162
|
-
id: apiProposal.proposalId,
|
|
163
|
-
chainId: apiProposal.chainId,
|
|
164
|
-
proposalId: apiProposal.proposalId,
|
|
165
|
-
proposer: apiProposal.proposer as `0x${string}`,
|
|
166
|
-
eta: onChainData.eta,
|
|
167
|
-
startTimestamp: apiProposal.votingStartTime,
|
|
168
|
-
endTimestamp: apiProposal.votingEndTime,
|
|
169
|
-
startBlock: Number(apiProposal.blockNumber),
|
|
170
|
-
forVotes: formattedData.forVotes,
|
|
171
|
-
againstVotes: formattedData.againstVotes,
|
|
172
|
-
abstainVotes: formattedData.abstainVotes,
|
|
173
|
-
totalVotes: formattedData.totalVotes,
|
|
174
|
-
canceled: formattedData.canceled,
|
|
175
|
-
executed: formattedData.executed,
|
|
176
|
-
quorum: new Amount(onChainData.quorum, 18),
|
|
177
|
-
state: proposalState,
|
|
178
|
-
// Extended data
|
|
179
|
-
title: formattedData.title,
|
|
180
|
-
subtitle: formattedData.subtitle,
|
|
181
|
-
description: apiProposal.description,
|
|
182
|
-
targets: apiProposal.targets,
|
|
183
|
-
calldatas: apiProposal.calldatas,
|
|
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 ?? [],
|
|
188
|
-
stateChanges: formattedData.stateChanges,
|
|
189
|
-
environment: governanceEnvironment,
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
if (apiProposal.snapshotBlocks) {
|
|
193
|
-
proposal.snapshotBlocks = apiProposal.snapshotBlocks;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (isMultichain) {
|
|
197
|
-
proposal.multichain = {
|
|
198
|
-
id: apiProposal.proposalId,
|
|
199
|
-
votesCollected: onChainData.votesCollected,
|
|
200
|
-
};
|
|
108
|
+
// `getProposalsOnChainData` maps 1:1 over its input, so the single entry is
|
|
109
|
+
// always present — guard rather than assert, so the invariant is enforced
|
|
110
|
+
// instead of asserted away if that ever stops holding.
|
|
111
|
+
const onChainData = onChainDataList[0];
|
|
112
|
+
if (!onChainData) {
|
|
113
|
+
return undefined;
|
|
201
114
|
}
|
|
202
115
|
|
|
203
|
-
return
|
|
116
|
+
return mapApiProposalToProposal(
|
|
117
|
+
apiProposal,
|
|
118
|
+
onChainData,
|
|
119
|
+
governanceEnvironment,
|
|
120
|
+
);
|
|
204
121
|
}
|