@poa-box/core 0.1.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/README.md +201 -0
- package/dist/abis/DirectDemocracyVotingNew.d.ts +749 -0
- package/dist/abis/DirectDemocracyVotingNew.js +961 -0
- package/dist/abis/ERC20.d.ts +90 -0
- package/dist/abis/ERC20.js +125 -0
- package/dist/abis/EducationHubNew.d.ts +590 -0
- package/dist/abis/EducationHubNew.js +762 -0
- package/dist/abis/EligibilityModuleNew.d.ts +1615 -0
- package/dist/abis/EligibilityModuleNew.js +2082 -0
- package/dist/abis/Executor.d.ts +619 -0
- package/dist/abis/Executor.js +796 -0
- package/dist/abis/HybridVotingNew.d.ts +898 -0
- package/dist/abis/HybridVotingNew.js +1149 -0
- package/dist/abis/ImplementationRegistry.d.ts +270 -0
- package/dist/abis/ImplementationRegistry.js +355 -0
- package/dist/abis/OrgDeployerNew.d.ts +1275 -0
- package/dist/abis/OrgDeployerNew.js +1630 -0
- package/dist/abis/OrgRegistry.d.ts +670 -0
- package/dist/abis/OrgRegistry.js +868 -0
- package/dist/abis/ParticipationToken.d.ts +1086 -0
- package/dist/abis/ParticipationToken.js +1415 -0
- package/dist/abis/PasskeyAccount.d.ts +788 -0
- package/dist/abis/PasskeyAccount.js +1019 -0
- package/dist/abis/PasskeyAccountFactory.d.ts +344 -0
- package/dist/abis/PasskeyAccountFactory.js +449 -0
- package/dist/abis/PaymasterHub.d.ts +1603 -0
- package/dist/abis/PaymasterHub.js +2048 -0
- package/dist/abis/PaymentManager.d.ts +520 -0
- package/dist/abis/PaymentManager.js +671 -0
- package/dist/abis/PoaManager.d.ts +344 -0
- package/dist/abis/PoaManager.js +449 -0
- package/dist/abis/QuickJoinNew.d.ts +855 -0
- package/dist/abis/QuickJoinNew.js +1098 -0
- package/dist/abis/TaskManagerNew.d.ts +1236 -0
- package/dist/abis/TaskManagerNew.js +1573 -0
- package/dist/abis/ToggleModule.d.ts +193 -0
- package/dist/abis/ToggleModule.js +255 -0
- package/dist/abis/UniversalAccountRegistry.d.ts +577 -0
- package/dist/abis/UniversalAccountRegistry.js +750 -0
- package/dist/abis/ZkEmailInvites.d.ts +823 -0
- package/dist/abis/ZkEmailInvites.js +1060 -0
- package/dist/abis/external/AaveGovernanceV2.d.ts +128 -0
- package/dist/abis/external/AaveGovernanceV2.js +177 -0
- package/dist/abis/external/AaveGovernanceV3.d.ts +160 -0
- package/dist/abis/external/AaveGovernanceV3.js +220 -0
- package/dist/abis/external/AragonVoting.d.ts +107 -0
- package/dist/abis/external/AragonVoting.js +150 -0
- package/dist/abis/external/CurveGaugeController.d.ts +95 -0
- package/dist/abis/external/CurveGaugeController.js +130 -0
- package/dist/abis/external/CurveVotingEscrow.d.ts +86 -0
- package/dist/abis/external/CurveVotingEscrow.js +116 -0
- package/dist/abis/external/GovernorAlpha.d.ts +172 -0
- package/dist/abis/external/GovernorAlpha.js +228 -0
- package/dist/abis/external/MakerDAOChief.d.ts +92 -0
- package/dist/abis/external/MakerDAOChief.js +129 -0
- package/dist/abis/external/OZGovernor.d.ts +227 -0
- package/dist/abis/external/OZGovernor.js +317 -0
- package/dist/abis/external/SolidlyVotingEscrow.d.ts +196 -0
- package/dist/abis/external/SolidlyVotingEscrow.js +260 -0
- package/dist/abis/index.d.ts +62 -0
- package/dist/abis/index.js +98 -0
- package/dist/chains.d.ts +150 -0
- package/dist/chains.js +320 -0
- package/dist/context.d.ts +48 -0
- package/dist/context.js +38 -0
- package/dist/contracts.d.ts +24 -0
- package/dist/contracts.js +41 -0
- package/dist/encoding.d.ts +58 -0
- package/dist/encoding.js +243 -0
- package/dist/env.d.ts +10 -0
- package/dist/env.js +4 -0
- package/dist/error-catalog.d.ts +41 -0
- package/dist/error-catalog.js +941 -0
- package/dist/errors.d.ts +27 -0
- package/dist/errors.js +60 -0
- package/dist/execute/ethers.d.ts +93 -0
- package/dist/execute/ethers.js +323 -0
- package/dist/execute/index.d.ts +7 -0
- package/dist/execute/index.js +23 -0
- package/dist/execute/sponsored.d.ts +68 -0
- package/dist/execute/sponsored.js +277 -0
- package/dist/exit-codes.d.ts +18 -0
- package/dist/exit-codes.js +21 -0
- package/dist/format.d.ts +21 -0
- package/dist/format.js +89 -0
- package/dist/graph/client.d.ts +298 -0
- package/dist/graph/client.js +702 -0
- package/dist/graph/documents/activity.d.ts +9 -0
- package/dist/graph/documents/activity.js +164 -0
- package/dist/graph/documents/beacons.d.ts +172 -0
- package/dist/graph/documents/beacons.js +345 -0
- package/dist/graph/documents/index.d.ts +21 -0
- package/dist/graph/documents/index.js +57 -0
- package/dist/graph/documents/infrastructure.d.ts +30 -0
- package/dist/graph/documents/infrastructure.js +34 -0
- package/dist/graph/documents/org.d.ts +15 -0
- package/dist/graph/documents/org.js +249 -0
- package/dist/graph/documents/paymaster.d.ts +95 -0
- package/dist/graph/documents/paymaster.js +101 -0
- package/dist/graph/documents/role.d.ts +6 -0
- package/dist/graph/documents/role.js +41 -0
- package/dist/graph/documents/roles.d.ts +50 -0
- package/dist/graph/documents/roles.js +112 -0
- package/dist/graph/documents/task.d.ts +84 -0
- package/dist/graph/documents/task.js +236 -0
- package/dist/graph/documents/token.d.ts +13 -0
- package/dist/graph/documents/token.js +126 -0
- package/dist/graph/documents/treasury.d.ts +39 -0
- package/dist/graph/documents/treasury.js +136 -0
- package/dist/graph/documents/user.d.ts +120 -0
- package/dist/graph/documents/user.js +302 -0
- package/dist/graph/documents/voting-classes.d.ts +69 -0
- package/dist/graph/documents/voting-classes.js +149 -0
- package/dist/graph/documents/voting.d.ts +28 -0
- package/dist/graph/documents/voting.js +163 -0
- package/dist/graph/documents/vouch.d.ts +79 -0
- package/dist/graph/documents/vouch.js +171 -0
- package/dist/graph/documents/zkemail.d.ts +63 -0
- package/dist/graph/documents/zkemail.js +188 -0
- package/dist/graph/index.d.ts +2 -0
- package/dist/graph/index.js +41 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.js +92 -0
- package/dist/ipfs.d.ts +39 -0
- package/dist/ipfs.js +214 -0
- package/dist/label-aliases.d.ts +31 -0
- package/dist/label-aliases.js +70 -0
- package/dist/metadata/education.d.ts +30 -0
- package/dist/metadata/education.js +31 -0
- package/dist/metadata/index.d.ts +12 -0
- package/dist/metadata/index.js +48 -0
- package/dist/metadata/org.d.ts +92 -0
- package/dist/metadata/org.js +93 -0
- package/dist/metadata/proposal.d.ts +44 -0
- package/dist/metadata/proposal.js +45 -0
- package/dist/metadata/role.d.ts +38 -0
- package/dist/metadata/role.js +35 -0
- package/dist/metadata/task.d.ts +108 -0
- package/dist/metadata/task.js +81 -0
- package/dist/metadata/token.d.ts +32 -0
- package/dist/metadata/token.js +36 -0
- package/dist/metadata/user.d.ts +63 -0
- package/dist/metadata/user.js +70 -0
- package/dist/multicall.d.ts +32 -0
- package/dist/multicall.js +96 -0
- package/dist/payout.d.ts +59 -0
- package/dist/payout.js +96 -0
- package/dist/perms.d.ts +47 -0
- package/dist/perms.js +120 -0
- package/dist/preflight.d.ts +72 -0
- package/dist/preflight.js +269 -0
- package/dist/reads/education.d.ts +79 -0
- package/dist/reads/education.js +122 -0
- package/dist/reads/eligibility.d.ts +377 -0
- package/dist/reads/eligibility.js +687 -0
- package/dist/reads/index.d.ts +16 -0
- package/dist/reads/index.js +55 -0
- package/dist/reads/org.d.ts +258 -0
- package/dist/reads/org.js +220 -0
- package/dist/reads/paymaster.d.ts +147 -0
- package/dist/reads/paymaster.js +235 -0
- package/dist/reads/project.d.ts +33 -0
- package/dist/reads/project.js +62 -0
- package/dist/reads/resolve.d.ts +34 -0
- package/dist/reads/resolve.js +64 -0
- package/dist/reads/task.d.ts +286 -0
- package/dist/reads/task.js +280 -0
- package/dist/reads/token.d.ts +94 -0
- package/dist/reads/token.js +75 -0
- package/dist/reads/treasury.d.ts +222 -0
- package/dist/reads/treasury.js +241 -0
- package/dist/reads/user.d.ts +229 -0
- package/dist/reads/user.js +155 -0
- package/dist/reads/vote.d.ts +296 -0
- package/dist/reads/vote.js +392 -0
- package/dist/reads/zkemail.d.ts +117 -0
- package/dist/reads/zkemail.js +217 -0
- package/dist/similarity.d.ts +36 -0
- package/dist/similarity.js +67 -0
- package/dist/sponsorship-config.d.ts +47 -0
- package/dist/sponsorship-config.js +41 -0
- package/dist/stats.d.ts +11 -0
- package/dist/stats.js +27 -0
- package/dist/task-lens.d.ts +113 -0
- package/dist/task-lens.js +210 -0
- package/dist/tx/education.d.ts +169 -0
- package/dist/tx/education.js +344 -0
- package/dist/tx/eligibility.d.ts +511 -0
- package/dist/tx/eligibility.js +818 -0
- package/dist/tx/governance.d.ts +85 -0
- package/dist/tx/governance.js +87 -0
- package/dist/tx/index.d.ts +17 -0
- package/dist/tx/index.js +56 -0
- package/dist/tx/intent.d.ts +67 -0
- package/dist/tx/intent.js +48 -0
- package/dist/tx/org.d.ts +321 -0
- package/dist/tx/org.js +544 -0
- package/dist/tx/paymaster.d.ts +140 -0
- package/dist/tx/paymaster.js +251 -0
- package/dist/tx/project.d.ts +146 -0
- package/dist/tx/project.js +217 -0
- package/dist/tx/task.d.ts +480 -0
- package/dist/tx/task.js +1149 -0
- package/dist/tx/token.d.ts +96 -0
- package/dist/tx/token.js +165 -0
- package/dist/tx/treasury.d.ts +474 -0
- package/dist/tx/treasury.js +911 -0
- package/dist/tx/user.d.ts +177 -0
- package/dist/tx/user.js +292 -0
- package/dist/tx/vote.d.ts +332 -0
- package/dist/tx/vote.js +762 -0
- package/dist/tx/zkemail.d.ts +90 -0
- package/dist/tx/zkemail.js +210 -0
- package/dist/validation.d.ts +8 -0
- package/dist/validation.js +39 -0
- package/dist/version.d.ts +142 -0
- package/dist/version.js +257 -0
- package/dist/zkemail.d.ts +161 -0
- package/dist/zkemail.js +299 -0
- package/package.json +130 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Vote/governance reads — typed wrappers over the voting graph documents for
|
|
4
|
+
* the entities the CLI's `pop vote list / results / execute / classes show`
|
|
5
|
+
* commands read, plus the pure selection helpers those commands share
|
|
6
|
+
* (fuzzy --proposal resolution, announce-all candidate selection, the
|
|
7
|
+
* execute state gate, results tallying, class-snapshot normalization).
|
|
8
|
+
*
|
|
9
|
+
* Raw subgraph entity shapes only — scalars arrive as strings.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.GET_PROPOSAL_EXECUTION_STATE = void 0;
|
|
13
|
+
exports.fetchVotingData = fetchVotingData;
|
|
14
|
+
exports.fetchRecentProposals = fetchRecentProposals;
|
|
15
|
+
exports.resolveProposalId = resolveProposalId;
|
|
16
|
+
exports.selectAnnounceCandidates = selectAnnounceCandidates;
|
|
17
|
+
exports.fetchProposalExecutionState = fetchProposalExecutionState;
|
|
18
|
+
exports.evaluateExecuteGate = evaluateExecuteGate;
|
|
19
|
+
exports.buildProposalResultsQuery = buildProposalResultsQuery;
|
|
20
|
+
exports.computeProposalResults = computeProposalResults;
|
|
21
|
+
exports.fetchProposalResults = fetchProposalResults;
|
|
22
|
+
exports.normalizeSubgraphClasses = normalizeSubgraphClasses;
|
|
23
|
+
exports.fetchClassConfig = fetchClassConfig;
|
|
24
|
+
const ethers_1 = require("ethers");
|
|
25
|
+
const voting_1 = require("../graph/documents/voting");
|
|
26
|
+
const voting_classes_1 = require("../graph/documents/voting-classes");
|
|
27
|
+
const resolve_1 = require("./resolve");
|
|
28
|
+
const similarity_1 = require("../similarity");
|
|
29
|
+
const errors_1 = require("../errors");
|
|
30
|
+
const exit_codes_1 = require("../exit-codes");
|
|
31
|
+
// ────────────────────────── proposals list ──────────────────────────
|
|
32
|
+
/**
|
|
33
|
+
* Port of the `pop vote list` / `pop vote announce-all` data fetch
|
|
34
|
+
* (src/commands/vote/list.ts, src/commands/vote/announce-all.ts):
|
|
35
|
+
* FETCH_VOTING_DATA with the pre-#195 legacy tier as fallback.
|
|
36
|
+
*
|
|
37
|
+
* A GraphQL document validates as a whole, so one unknown field fails the
|
|
38
|
+
* entire query — the legacy tier keeps listing (and announcing) working
|
|
39
|
+
* against a pre-#195 subgraph deployment.
|
|
40
|
+
*/
|
|
41
|
+
async function fetchVotingData(client, orgIdOrName, chainId) {
|
|
42
|
+
const orgId = await (0, resolve_1.resolveOrgId)(client, orgIdOrName, chainId);
|
|
43
|
+
const { data } = await client.queryWithFieldFallback([
|
|
44
|
+
{ query: voting_1.FETCH_VOTING_DATA, variables: { orgId } },
|
|
45
|
+
{ query: voting_1.FETCH_VOTING_DATA_LEGACY, variables: { orgId } },
|
|
46
|
+
], { chainId });
|
|
47
|
+
return data.organization ?? null;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Port of `fetchRecentProposals` (src/commands/vote/helpers.ts): recent
|
|
51
|
+
* proposal titles for ONE voting contract, addressed by contract address —
|
|
52
|
+
* works for both Hybrid and DD (whichever entity matches answers, the other
|
|
53
|
+
* root field returns null).
|
|
54
|
+
*/
|
|
55
|
+
async function fetchRecentProposals(client, contractAddr, chainId, first = 50) {
|
|
56
|
+
const data = await client.query(voting_1.RECENT_PROPOSALS_FOR_RESOLVE, { votingId: contractAddr.toLowerCase(), first }, chainId);
|
|
57
|
+
const raw = [
|
|
58
|
+
...(data?.hybridVotingContract?.proposals ?? []),
|
|
59
|
+
...(data?.directDemocracyVotingContract?.ddvProposals ?? []),
|
|
60
|
+
];
|
|
61
|
+
return raw.map((p) => ({
|
|
62
|
+
proposalId: String(p.proposalId),
|
|
63
|
+
title: String(p.title ?? ''),
|
|
64
|
+
status: String(p.status ?? ''),
|
|
65
|
+
endTimestamp: p.endTimestamp !== undefined && p.endTimestamp !== null ? String(p.endTimestamp) : undefined,
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
function candidateLine(c) {
|
|
69
|
+
return `#${c.proposalId} '${c.title}' (${c.status})`;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Port of `resolveProposalId` (src/commands/vote/helpers.ts): resolve a
|
|
73
|
+
* user-supplied proposal argument to a numeric proposal ID.
|
|
74
|
+
*
|
|
75
|
+
* - Numeric input → returned as-is (no network round-trip).
|
|
76
|
+
* - Anything else → fuzzy title query against the voting contract's recent
|
|
77
|
+
* proposals, scored with Jaccard bestMatches at threshold 0.5.
|
|
78
|
+
* - exactly one match → resolved
|
|
79
|
+
* - multiple matches → CliError listing the candidates (the CLI's
|
|
80
|
+
* non-TTY behavior; the interactive picker is CLI UX and not ported)
|
|
81
|
+
* - zero matches → CliError listing the 5 most recent titles
|
|
82
|
+
*
|
|
83
|
+
* opts.preferActive (used by `vote cast`): when several proposals match but
|
|
84
|
+
* exactly ONE is still Active, that one wins — you can only cast on an open
|
|
85
|
+
* proposal, so ended homonyms are not ambiguity.
|
|
86
|
+
*/
|
|
87
|
+
async function resolveProposalId(client, input, contractAddr, chainId, opts) {
|
|
88
|
+
const trimmed = input.trim();
|
|
89
|
+
const n = Number(trimmed);
|
|
90
|
+
if (Number.isFinite(n) && Number.isInteger(n) && n >= 0 && String(n) === trimmed) {
|
|
91
|
+
return n;
|
|
92
|
+
}
|
|
93
|
+
const candidates = await fetchRecentProposals(client, contractAddr, chainId);
|
|
94
|
+
if (candidates.length === 0) {
|
|
95
|
+
throw new errors_1.CliError(`Cannot resolve proposal '${input}': no proposals are indexed for this voting contract yet.`, exit_codes_1.EXIT.USAGE, 'Check the org/--type, or pass the numeric proposal ID (pop vote list).');
|
|
96
|
+
}
|
|
97
|
+
let matches = (0, similarity_1.bestMatches)(trimmed, candidates, c => c.title, { threshold: 0.5, topN: 5 });
|
|
98
|
+
if (matches.length > 1 && opts?.preferActive) {
|
|
99
|
+
const active = matches.filter(m => m.item.status === 'Active');
|
|
100
|
+
if (active.length === 1)
|
|
101
|
+
matches = active;
|
|
102
|
+
}
|
|
103
|
+
if (matches.length === 1) {
|
|
104
|
+
return Number(matches[0].item.proposalId);
|
|
105
|
+
}
|
|
106
|
+
if (matches.length > 1) {
|
|
107
|
+
throw new errors_1.CliError(`Proposal query '${input}' is ambiguous — ${matches.length} proposals match:\n` +
|
|
108
|
+
matches.map(m => ` ${candidateLine(m.item)}`).join('\n'), exit_codes_1.EXIT.USAGE, `Pass the numeric ID instead, e.g. --proposal ${matches[0].item.proposalId}`);
|
|
109
|
+
}
|
|
110
|
+
const recent = candidates.slice(0, 5);
|
|
111
|
+
throw new errors_1.CliError(`No proposal title matches '${input}'. The ${recent.length} most recent proposals are:\n` +
|
|
112
|
+
recent.map(c => ` ${candidateLine(c)}`).join('\n'), exit_codes_1.EXIT.USAGE, 'Pass a numeric proposal ID from pop vote list, or refine the title query.');
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Pure port of the `pop vote announce-all` candidate filter
|
|
116
|
+
* (src/commands/vote/announce-all.ts): proposals ready to announce are
|
|
117
|
+
* - status "Ended" (subgraph updated), OR
|
|
118
|
+
* - status "Active" but endTimestamp has passed (subgraph hasn't updated)
|
|
119
|
+
* excluding already-announced ones — EXCEPT the H-05 retry case: a FAILED
|
|
120
|
+
* execution is not "done". Audit H-05 releases the in-flight `executed` lock
|
|
121
|
+
* in the catch branch, so the proposal is re-announceable — but it already
|
|
122
|
+
* has a winningOption (Winner is emitted regardless), so the plain
|
|
123
|
+
* already-announced test would skip it forever and silently.
|
|
124
|
+
*
|
|
125
|
+
* Callers should still per-proposal callStatic-probe announceWinner before
|
|
126
|
+
* broadcasting (the CLI skips would-revert candidates without burning gas).
|
|
127
|
+
*
|
|
128
|
+
* @param nowSeconds unix seconds "now" — pass chain time when available.
|
|
129
|
+
*/
|
|
130
|
+
function selectAnnounceCandidates(org, nowSeconds) {
|
|
131
|
+
const toAnnounce = [];
|
|
132
|
+
const isEnded = (p) => p.status === 'Ended' ||
|
|
133
|
+
(p.status === 'Active' && !!p.endTimestamp && parseInt(p.endTimestamp) < nowSeconds);
|
|
134
|
+
for (const p of org.hybridVoting?.proposals ?? []) {
|
|
135
|
+
const alreadyDone = (p.status === 'Executed' || p.winningOption != null) && !p.executionFailed;
|
|
136
|
+
if (alreadyDone)
|
|
137
|
+
continue;
|
|
138
|
+
if (isEnded(p)) {
|
|
139
|
+
toAnnounce.push({
|
|
140
|
+
id: p.proposalId,
|
|
141
|
+
type: 'hybrid',
|
|
142
|
+
title: p.title || `Proposal #${p.proposalId}`,
|
|
143
|
+
retry: Boolean(p.executionFailed),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
for (const p of org.directDemocracyVoting?.ddvProposals ?? []) {
|
|
148
|
+
// Same H-05 retry case as hybrid above.
|
|
149
|
+
const alreadyDone = (p.status === 'Executed' || p.winningOption != null) && !p.executionFailed;
|
|
150
|
+
if (alreadyDone)
|
|
151
|
+
continue;
|
|
152
|
+
if (isEnded(p)) {
|
|
153
|
+
toAnnounce.push({
|
|
154
|
+
id: p.proposalId,
|
|
155
|
+
type: 'dd',
|
|
156
|
+
title: p.title || `DD Proposal #${p.proposalId}`,
|
|
157
|
+
retry: Boolean(p.executionFailed),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return toAnnounce;
|
|
162
|
+
}
|
|
163
|
+
/** The `pop vote execute` state-gate document (src/commands/vote/execute.ts). */
|
|
164
|
+
exports.GET_PROPOSAL_EXECUTION_STATE = `
|
|
165
|
+
query GetProposal($votingId: String!, $proposalId: String!) {
|
|
166
|
+
proposals(where: { hybridVoting: $votingId, proposalId: $proposalId }, first: 1) {
|
|
167
|
+
proposalId
|
|
168
|
+
status
|
|
169
|
+
winningOption
|
|
170
|
+
wasExecuted
|
|
171
|
+
executionFailed
|
|
172
|
+
isValid
|
|
173
|
+
winnerAnnouncedAt
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
`;
|
|
177
|
+
/**
|
|
178
|
+
* Port of the `pop vote execute` subgraph state gate: fetch the one proposal
|
|
179
|
+
* row the finalize decision is made from. Null row means the proposal is not
|
|
180
|
+
* indexed (the CLI treats that as not found).
|
|
181
|
+
*/
|
|
182
|
+
async function fetchProposalExecutionState(client, hybridVotingAddress, proposalId, chainId) {
|
|
183
|
+
const result = await client.query(exports.GET_PROPOSAL_EXECUTION_STATE, { votingId: hybridVotingAddress, proposalId: proposalId.toString() }, chainId);
|
|
184
|
+
return result.proposals?.[0] ?? null;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Pure port of the `pop vote execute` decision ladder over the subgraph row
|
|
188
|
+
* (same evaluation order as src/commands/vote/execute.ts). The caller maps
|
|
189
|
+
* 'not-ended' to the CLI's PRECONDITION error and the two "nothing to do"
|
|
190
|
+
* kinds to graceful no-ops.
|
|
191
|
+
*/
|
|
192
|
+
function evaluateExecuteGate(proposal) {
|
|
193
|
+
if (proposal.wasExecuted)
|
|
194
|
+
return { kind: 'already-executed' };
|
|
195
|
+
// executionFailed proposals ARE announced — the announce succeeded and the
|
|
196
|
+
// execution reverted. Audit H-05 made that retryable, so it must NOT be
|
|
197
|
+
// treated as already-announced.
|
|
198
|
+
if (proposal.winnerAnnouncedAt && !proposal.executionFailed) {
|
|
199
|
+
return { kind: 'already-announced', isValid: proposal.isValid };
|
|
200
|
+
}
|
|
201
|
+
if (proposal.status !== 'Ended')
|
|
202
|
+
return { kind: 'not-ended', status: proposal.status };
|
|
203
|
+
return { kind: 'ready', retryingFailedExecution: Boolean(proposal.executionFailed) };
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* The `pop vote results` proposal document (src/commands/vote/results.ts).
|
|
207
|
+
* Two tiers: the #195 attribution/provenance fields, then the pre-#195 field
|
|
208
|
+
* set. A GraphQL document validates as a whole, so without the fallback an
|
|
209
|
+
* endpoint that predates #195 would lose the rankings and voter breakdown
|
|
210
|
+
* this read exists to serve. The legacy tier is spelled out rather than
|
|
211
|
+
* derived so a reformat cannot silently turn it into a copy of the modern one.
|
|
212
|
+
*/
|
|
213
|
+
function buildProposalResultsQuery(orgId, proposalId, modern) {
|
|
214
|
+
const proposalCore = 'proposalId title status';
|
|
215
|
+
return `{
|
|
216
|
+
organization(id: "${orgId}") {
|
|
217
|
+
hybridVoting {
|
|
218
|
+
thresholdPct
|
|
219
|
+
quorum
|
|
220
|
+
${modern ? 'classVersion' : ''}
|
|
221
|
+
proposals(where: {proposalId: ${proposalId}}) {
|
|
222
|
+
${proposalCore}
|
|
223
|
+
${modern ? 'proposer proposerUsername creatorUsername' : ''}
|
|
224
|
+
${modern ? 'classesVersion winnerAnnouncedAt executedAt executedCallsCount' : ''}
|
|
225
|
+
metadata { description optionNames${modern ? ' actionSummaries promotedFrom' : ''} }
|
|
226
|
+
votes { voterUsername optionIndexes optionWeights }
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}`;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Pure port of the `pop vote results` tally/ranking/attribution computation
|
|
234
|
+
* (src/commands/vote/results.ts) over the fetched hybridVoting row.
|
|
235
|
+
*
|
|
236
|
+
* Attribution: subgraph #195. proposer* are the current names; creator* the
|
|
237
|
+
* older aliases populated identically from transaction.from. proposedBy is an
|
|
238
|
+
* IDENTITY or null — never an address: most proposers are Executor or
|
|
239
|
+
* smart-account addresses with no username, and returning the address here
|
|
240
|
+
* would make `proposedBy === 'someone'` silently false for them while looking
|
|
241
|
+
* like a resolved name. The address is always available as proposerAddress.
|
|
242
|
+
*/
|
|
243
|
+
function computeProposalResults(hybridVoting) {
|
|
244
|
+
const proposal = hybridVoting.proposals?.[0];
|
|
245
|
+
if (!proposal)
|
|
246
|
+
return null;
|
|
247
|
+
// Two DISTINCT validity parameters — threshold is a % of weighted power,
|
|
248
|
+
// quorum is a raw voter count. Never conflate them.
|
|
249
|
+
const supportThresholdPct = hybridVoting.thresholdPct !== undefined ? Number(hybridVoting.thresholdPct) : undefined;
|
|
250
|
+
const quorumVoterCount = hybridVoting.quorum !== undefined ? Number(hybridVoting.quorum) : undefined;
|
|
251
|
+
const optionNames = proposal.metadata?.optionNames || [];
|
|
252
|
+
const votes = proposal.votes || [];
|
|
253
|
+
// Tally weighted votes per option
|
|
254
|
+
const tallies = new Array(Math.max(optionNames.length, 1)).fill(0);
|
|
255
|
+
for (const v of votes) {
|
|
256
|
+
for (let i = 0; i < (v.optionIndexes || []).length; i++) {
|
|
257
|
+
const idx = parseInt(v.optionIndexes[i]);
|
|
258
|
+
const weight = parseInt(v.optionWeights[i]);
|
|
259
|
+
if (idx < tallies.length)
|
|
260
|
+
tallies[idx] += weight;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// Rank options
|
|
264
|
+
const ranked = optionNames
|
|
265
|
+
.map((name, i) => ({ rank: 0, option: i, name, score: tallies[i] || 0 }))
|
|
266
|
+
.sort((a, b) => b.score - a.score);
|
|
267
|
+
ranked.forEach((r, i) => { r.rank = i + 1; });
|
|
268
|
+
// Per-voter breakdown
|
|
269
|
+
const voterBreakdown = votes.map((v) => {
|
|
270
|
+
const allocations = {};
|
|
271
|
+
for (let i = 0; i < (v.optionIndexes || []).length; i++) {
|
|
272
|
+
const idx = parseInt(v.optionIndexes[i]);
|
|
273
|
+
const name = optionNames[idx] || `Option ${idx}`;
|
|
274
|
+
allocations[name] = parseInt(v.optionWeights[i]);
|
|
275
|
+
}
|
|
276
|
+
return { voter: v.voterUsername || 'unknown', allocations };
|
|
277
|
+
});
|
|
278
|
+
const proposedBy = proposal.proposerUsername || proposal.creatorUsername || null;
|
|
279
|
+
// A proposal is tallied against the voting-class config in force when it was
|
|
280
|
+
// CREATED, so a live config change mid-flight makes the org-level
|
|
281
|
+
// threshold/quorum shown here stale.
|
|
282
|
+
const classVersionAtCreation = proposal.classesVersion != null ? String(proposal.classesVersion) : null;
|
|
283
|
+
const liveClassVersion = hybridVoting.classVersion != null ? String(hybridVoting.classVersion) : null;
|
|
284
|
+
const classConfigDrifted = Boolean(classVersionAtCreation && liveClassVersion && classVersionAtCreation !== liveClassVersion);
|
|
285
|
+
return {
|
|
286
|
+
proposalId: proposal.proposalId,
|
|
287
|
+
title: proposal.title,
|
|
288
|
+
status: proposal.status,
|
|
289
|
+
proposedBy,
|
|
290
|
+
proposerAddress: proposal.proposer || null,
|
|
291
|
+
classVersionAtCreation,
|
|
292
|
+
liveClassVersion,
|
|
293
|
+
classConfigDrifted,
|
|
294
|
+
winnerAnnouncedAt: proposal.winnerAnnouncedAt ? Number(proposal.winnerAnnouncedAt) : null,
|
|
295
|
+
executedAt: proposal.executedAt ? Number(proposal.executedAt) : null,
|
|
296
|
+
executedCallsCount: proposal.executedCallsCount != null ? Number(proposal.executedCallsCount) : null,
|
|
297
|
+
actionSummaries: proposal.metadata?.actionSummaries || [],
|
|
298
|
+
promotedFrom: proposal.metadata?.promotedFrom || null,
|
|
299
|
+
totalVoters: votes.length,
|
|
300
|
+
supportThresholdPct,
|
|
301
|
+
quorumVoterCount,
|
|
302
|
+
ranking: ranked,
|
|
303
|
+
voters: voterBreakdown,
|
|
304
|
+
winner: ranked[0],
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Port of `pop vote results` (src/commands/vote/results.ts): rankings +
|
|
309
|
+
* per-voter breakdown for one proposal. Throws the CLI's not-found error when
|
|
310
|
+
* the proposal is not indexed.
|
|
311
|
+
*/
|
|
312
|
+
async function fetchProposalResults(client, orgIdOrName, proposalId, chainId) {
|
|
313
|
+
const orgId = await (0, resolve_1.resolveOrgId)(client, orgIdOrName, chainId);
|
|
314
|
+
const { data } = await client.queryWithFieldFallback([
|
|
315
|
+
{ query: buildProposalResultsQuery(orgId, proposalId, true) },
|
|
316
|
+
{ query: buildProposalResultsQuery(orgId, proposalId, false) },
|
|
317
|
+
], { chainId });
|
|
318
|
+
const hybridVoting = data.organization?.hybridVoting;
|
|
319
|
+
const report = hybridVoting ? computeProposalResults(hybridVoting) : null;
|
|
320
|
+
if (!report)
|
|
321
|
+
throw new errors_1.CliError(`Proposal #${proposalId} not found`, exit_codes_1.EXIT.USAGE);
|
|
322
|
+
return report;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Subgraph rows → the exact shape the contract path produces. `asset` is
|
|
326
|
+
* checksummed because ethers returns a checksummed address and the subgraph
|
|
327
|
+
* returns lowercase Bytes; without this the `asset` value would change casing
|
|
328
|
+
* depending on which source answered.
|
|
329
|
+
*/
|
|
330
|
+
function normalizeSubgraphClasses(rows) {
|
|
331
|
+
return rows.map((c, i) => ({
|
|
332
|
+
classIndex: i,
|
|
333
|
+
strategy: String(c.strategy),
|
|
334
|
+
slicePct: Number(c.slicePct),
|
|
335
|
+
quadratic: Boolean(c.quadratic),
|
|
336
|
+
minBalance: ethers_1.ethers.BigNumber.from(String(c.minBalance ?? '0')).toString(),
|
|
337
|
+
asset: ethers_1.ethers.utils.getAddress(String(c.asset)),
|
|
338
|
+
hatIds: (c.hatIds ?? []).map(h => String(h)),
|
|
339
|
+
}));
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Port of `pop vote classes show`'s subgraph read
|
|
343
|
+
* (fetchClassConfigFromSubgraph, src/commands/vote/classes.ts): the class
|
|
344
|
+
* table + both validity parameters. Returns null whenever the subgraph cannot
|
|
345
|
+
* answer authoritatively (contract not indexed, no rows for the requested
|
|
346
|
+
* version, missing schema fields, network error) so the caller falls back to
|
|
347
|
+
* the contract (getClasses()/getProposalClasses()/thresholdPct()/quorum())
|
|
348
|
+
* instead of showing an empty table.
|
|
349
|
+
*/
|
|
350
|
+
async function fetchClassConfig(client, hybridVotingAddress, proposalId, chainId) {
|
|
351
|
+
try {
|
|
352
|
+
const hybridVoting = hybridVotingAddress.toLowerCase();
|
|
353
|
+
// The proposal-snapshot query has a single tier on purpose: a deployment
|
|
354
|
+
// without Proposal.classesVersion cannot identify the frozen snapshot, and
|
|
355
|
+
// guessing the newest version would silently misreport an old proposal.
|
|
356
|
+
const tiers = proposalId === undefined
|
|
357
|
+
? [
|
|
358
|
+
{ query: voting_classes_1.FETCH_VOTING_CLASS_CONFIG, variables: { hybridVoting } },
|
|
359
|
+
{ query: voting_classes_1.FETCH_VOTING_CLASS_CONFIG_LEGACY, variables: { hybridVoting } },
|
|
360
|
+
]
|
|
361
|
+
: [
|
|
362
|
+
{ query: voting_classes_1.FETCH_PROPOSAL_VOTING_CLASSES, variables: { hybridVoting, proposalId: String(proposalId) } },
|
|
363
|
+
];
|
|
364
|
+
const { data } = await client.queryWithFieldFallback(tiers, { chainId });
|
|
365
|
+
const contract = data?.hybridVotingContract;
|
|
366
|
+
if (!contract)
|
|
367
|
+
return null;
|
|
368
|
+
const version = proposalId === undefined
|
|
369
|
+
? contract.classVersion
|
|
370
|
+
: contract.proposals?.[0]?.classesVersion;
|
|
371
|
+
// For a proposal we must know its frozen version — no version, no answer.
|
|
372
|
+
if (proposalId !== undefined && (version === null || version === undefined))
|
|
373
|
+
return null;
|
|
374
|
+
const rows = (0, voting_classes_1.selectClassSnapshot)(contract.votingClasses, version);
|
|
375
|
+
if (rows.length === 0)
|
|
376
|
+
return null;
|
|
377
|
+
if (contract.thresholdPct === null || contract.thresholdPct === undefined)
|
|
378
|
+
return null;
|
|
379
|
+
if (contract.quorum === null || contract.quorum === undefined)
|
|
380
|
+
return null;
|
|
381
|
+
return {
|
|
382
|
+
classes: normalizeSubgraphClasses(rows),
|
|
383
|
+
supportThresholdPct: Number(contract.thresholdPct),
|
|
384
|
+
quorumVoterCount: Number(contract.quorum),
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
catch {
|
|
388
|
+
// Any subgraph problem (unknown field on an old deployment, lag, HTTP) —
|
|
389
|
+
// the contract is authoritative anyway, so just fall back.
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZK Email reads — module resolution + state for `pop zkemail status` /
|
|
3
|
+
* `pop zkemail claims`. Port of src/commands/zkemail/helpers.ts (client- and
|
|
4
|
+
* provider-injected) plus the claim-history read from
|
|
5
|
+
* src/commands/zkemail/claims.ts.
|
|
6
|
+
*
|
|
7
|
+
* The ZkEmailInvites module is OPTIONAL per org — `organization.zkEmailInvites`
|
|
8
|
+
* is null for orgs deployed without it — and a deployed module with
|
|
9
|
+
* `merkleRoot == 0` is DORMANT (every claim reverts until governance publishes
|
|
10
|
+
* an allowlist). Both states get a named error rather than a confusing revert.
|
|
11
|
+
*/
|
|
12
|
+
import { ethers } from 'ethers';
|
|
13
|
+
import type { GraphClient } from '../graph/client';
|
|
14
|
+
import type { EnvSource } from '../env';
|
|
15
|
+
export interface ZkEmailModuleState {
|
|
16
|
+
orgId: string;
|
|
17
|
+
orgName: string | null;
|
|
18
|
+
address: string;
|
|
19
|
+
/** On-chain active merkle root; ethers.constants.HashZero when dormant. */
|
|
20
|
+
merkleRoot: string;
|
|
21
|
+
/** On-chain allowlist CID digest (bytes32). */
|
|
22
|
+
allowlistCidDigest: string;
|
|
23
|
+
/** allowlistCidDigest decoded back to a CIDv0, or null when unset. */
|
|
24
|
+
allowlistCid: string | null;
|
|
25
|
+
/** True when no allowlist is committed — every claim reverts with AllowlistNotActive. */
|
|
26
|
+
dormant: boolean;
|
|
27
|
+
executor?: string;
|
|
28
|
+
domainVerifier?: string;
|
|
29
|
+
emailVerifier?: string;
|
|
30
|
+
dkimRegistry?: string;
|
|
31
|
+
accountRegistry?: string;
|
|
32
|
+
universalFactory?: string;
|
|
33
|
+
/** True when the wiring came from the subgraph rather than eth_calls. */
|
|
34
|
+
indexedWiring: boolean;
|
|
35
|
+
}
|
|
36
|
+
export interface SubgraphAllowlistEntry {
|
|
37
|
+
index: number;
|
|
38
|
+
entryType: string;
|
|
39
|
+
identifier: string | null;
|
|
40
|
+
identifierHash: string | null;
|
|
41
|
+
hatIds: string[];
|
|
42
|
+
roleIndexes: number[];
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Resolve the org's ZkEmailInvites proxy from the subgraph. Port of
|
|
46
|
+
* resolveZkEmailModule in src/commands/zkemail/helpers.ts.
|
|
47
|
+
* Throws a named error when the org has no module rather than letting a null
|
|
48
|
+
* address turn into an opaque call failure.
|
|
49
|
+
*/
|
|
50
|
+
export declare function resolveZkEmailModule(client: GraphClient, orgArg: string | undefined, chainId?: number): Promise<{
|
|
51
|
+
orgId: string;
|
|
52
|
+
orgName: string | null;
|
|
53
|
+
address: string;
|
|
54
|
+
subgraph: any;
|
|
55
|
+
indexedWiring: boolean;
|
|
56
|
+
}>;
|
|
57
|
+
/**
|
|
58
|
+
* Read the module's state, preferring the subgraph. Port of readModuleState in
|
|
59
|
+
* src/commands/zkemail/helpers.ts (the read behind `pop zkemail status`).
|
|
60
|
+
*
|
|
61
|
+
* `activeRoot` and `activeAllowlistCid` are indexed on the ZkEmailInvites entity, so the
|
|
62
|
+
* allowlist half needs no RPC at all — `resolveZkEmailModule` has already fetched it. Only the
|
|
63
|
+
* wiring addresses (executor + the four verifier/registry pointers) are unindexed pre-#197, so
|
|
64
|
+
* they are fetched on demand: pass `withWiring` when the caller actually renders them.
|
|
65
|
+
*
|
|
66
|
+
* Pass an existing `provider` to avoid opening a second connection; when omitted and an RPC
|
|
67
|
+
* read is needed, a JsonRpcProvider is built from resolveNetworkConfig(chainId, env).
|
|
68
|
+
*/
|
|
69
|
+
export declare function readModuleState(address: string, orgId: string, orgName: string | null, chainId?: number, provider?: ethers.providers.Provider, opts?: {
|
|
70
|
+
subgraph?: any;
|
|
71
|
+
withWiring?: boolean;
|
|
72
|
+
withDkimRegistry?: boolean;
|
|
73
|
+
env?: EnvSource;
|
|
74
|
+
}): Promise<ZkEmailModuleState>;
|
|
75
|
+
/**
|
|
76
|
+
* The indexed active root for an org's module, identity-guarded — the display
|
|
77
|
+
* read used by `pop zkemail propose-allowlist`
|
|
78
|
+
* (src/commands/zkemail/propose-allowlist.ts).
|
|
79
|
+
*
|
|
80
|
+
* Returns:
|
|
81
|
+
* - a root string (HashZero for a dormant module whose row EXISTS), or
|
|
82
|
+
* - null when the row is missing / the indexed module id does not match
|
|
83
|
+
* `moduleAddress` (a stale org->module pointer must not describe a
|
|
84
|
+
* different contract than the one a proposal targets). Callers fall back
|
|
85
|
+
* to merkleRoot() on-chain for the null case.
|
|
86
|
+
*/
|
|
87
|
+
export declare function fetchIndexedActiveRoot(client: GraphClient, orgId: string, moduleAddress: string, chainId?: number): Promise<string | null>;
|
|
88
|
+
export interface ZkEmailClaimRow {
|
|
89
|
+
id: string;
|
|
90
|
+
kind: string;
|
|
91
|
+
claimer: string;
|
|
92
|
+
claimerUsername: string | null;
|
|
93
|
+
/** The allowlist leaf the claim used — may belong to a SUPERSEDED allowlist. */
|
|
94
|
+
identifierHash: string;
|
|
95
|
+
hatIds: string[];
|
|
96
|
+
/** Absent on the passkey onboarding path, which does not emit it (majority of live Gnosis claims). */
|
|
97
|
+
nullifier: string | null;
|
|
98
|
+
registeredUsername: string | null;
|
|
99
|
+
claimedAt: string;
|
|
100
|
+
claimedAtBlock: string;
|
|
101
|
+
transactionHash: string;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Claim history for a module, most recent first — the read behind
|
|
105
|
+
* `pop zkemail claims` (src/commands/zkemail/claims.ts). Indexed since
|
|
106
|
+
* subgraph-pop #197; on a chain still serving the legacy tier the claim events
|
|
107
|
+
* were seen but every parameter was discarded, so there is nothing to read —
|
|
108
|
+
* callers should gate on `indexedWiring` from resolveZkEmailModule like the
|
|
109
|
+
* CLI does.
|
|
110
|
+
*/
|
|
111
|
+
export declare function fetchZkEmailClaims(client: GraphClient, moduleAddress: string, chainId?: number, limit?: number): Promise<ZkEmailClaimRow[]>;
|
|
112
|
+
/** Map hatId -> role name so allowlists render as "gmail.com -> Member". */
|
|
113
|
+
export declare function fetchRoleNames(client: GraphClient, orgId: string, chainId?: number): Promise<Map<string, string>>;
|
|
114
|
+
/** Render a hat ID as "Member (0x2a)" when the name is known, else the raw id. */
|
|
115
|
+
export declare function labelHat(hatId: string, names: Map<string, string>): string;
|
|
116
|
+
/** Normalize the subgraph's allowlist entries into a stable shape. */
|
|
117
|
+
export declare function normalizeEntries(allowlist: any): SubgraphAllowlistEntry[];
|