@poa-box/core 0.1.0 → 1.0.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 +2 -0
- package/dist/abis/AuthorityRouter.d.ts +448 -0
- package/dist/abis/AuthorityRouter.js +584 -0
- package/dist/abis/CutoverVerifier.d.ts +194 -0
- package/dist/abis/CutoverVerifier.js +259 -0
- package/dist/abis/DirectDemocracyVotingNew.d.ts +168 -30
- package/dist/abis/DirectDemocracyVotingNew.js +214 -35
- package/dist/abis/EducationHubNew.d.ts +32 -75
- package/dist/abis/EducationHubNew.js +38 -94
- package/dist/abis/Executor.d.ts +56 -86
- package/dist/abis/Executor.js +68 -104
- package/dist/abis/HybridVotingNew.d.ts +279 -35
- package/dist/abis/HybridVotingNew.js +357 -40
- package/dist/abis/{EligibilityModuleNew.d.ts → MembershipAuthority.d.ts} +1437 -718
- package/dist/abis/{EligibilityModuleNew.js → MembershipAuthority.js} +1821 -899
- package/dist/abis/OrgDeployerNew.d.ts +193 -127
- package/dist/abis/OrgDeployerNew.js +244 -160
- package/dist/abis/OrgRegistry.d.ts +40 -0
- package/dist/abis/OrgRegistry.js +51 -0
- package/dist/abis/ParticipationToken.d.ts +32 -75
- package/dist/abis/ParticipationToken.js +38 -94
- package/dist/abis/PasskeyAccount.d.ts +28 -0
- package/dist/abis/PasskeyAccount.js +35 -0
- package/dist/abis/PaymasterHub.d.ts +527 -47
- package/dist/abis/PaymasterHub.js +750 -135
- package/dist/abis/QuickJoinNew.d.ts +59 -170
- package/dist/abis/QuickJoinNew.js +71 -215
- package/dist/abis/TaskManagerNew.d.ts +27 -44
- package/dist/abis/TaskManagerNew.js +31 -52
- package/dist/abis/UniversalAccountRegistry.d.ts +24 -0
- package/dist/abis/UniversalAccountRegistry.js +30 -0
- package/dist/abis/ZkEmailInvites.d.ts +44 -0
- package/dist/abis/ZkEmailInvites.js +55 -0
- package/dist/abis/index.d.ts +6 -4
- package/dist/abis/index.js +10 -7
- package/dist/chains.js +3 -3
- package/dist/error-catalog.js +125 -7
- package/dist/graph/documents/activity.d.ts +3 -1
- package/dist/graph/documents/activity.js +19 -14
- package/dist/graph/documents/org.d.ts +4 -4
- package/dist/graph/documents/org.js +10 -4
- package/dist/graph/documents/task.d.ts +10 -1
- package/dist/graph/documents/task.js +157 -3
- package/dist/graph/documents/user.d.ts +5 -30
- package/dist/graph/documents/user.js +2 -42
- package/dist/graph/documents/voting-classes.d.ts +23 -2
- package/dist/graph/documents/voting-classes.js +119 -10
- package/dist/metadata/index.d.ts +0 -1
- package/dist/metadata/index.js +1 -2
- package/dist/perms.d.ts +0 -15
- package/dist/perms.js +1 -21
- package/dist/preflight.d.ts +2 -2
- package/dist/preflight.js +14 -22
- package/dist/reads/authority.d.ts +33 -0
- package/dist/reads/authority.js +196 -0
- package/dist/reads/index.d.ts +1 -1
- package/dist/reads/index.js +2 -2
- package/dist/reads/org.d.ts +20 -20
- package/dist/reads/org.js +94 -28
- package/dist/reads/project.js +3 -3
- package/dist/reads/resolve.d.ts +1 -1
- package/dist/reads/resolve.js +12 -3
- package/dist/reads/task.d.ts +44 -22
- package/dist/reads/task.js +126 -67
- package/dist/reads/user.d.ts +8 -43
- package/dist/reads/user.js +27 -44
- package/dist/reads/vote.d.ts +26 -2
- package/dist/reads/vote.js +104 -23
- package/dist/tx/authority.d.ts +33 -0
- package/dist/tx/authority.js +62 -0
- package/dist/tx/governance.d.ts +6 -0
- package/dist/tx/governance.js +20 -2
- package/dist/tx/index.d.ts +1 -1
- package/dist/tx/index.js +2 -2
- package/dist/tx/org.d.ts +23 -18
- package/dist/tx/org.js +203 -67
- package/dist/tx/project.d.ts +3 -2
- package/dist/tx/project.js +28 -10
- package/dist/tx/task.d.ts +13 -24
- package/dist/tx/task.js +45 -85
- package/dist/tx/user.d.ts +2 -40
- package/dist/tx/user.js +2 -76
- package/dist/tx/vote.d.ts +5 -0
- package/dist/tx/vote.js +54 -43
- package/dist/version.d.ts +0 -5
- package/dist/version.js +1 -13
- package/package.json +1 -1
- package/dist/abis/ToggleModule.d.ts +0 -193
- package/dist/abis/ToggleModule.js +0 -255
- package/dist/metadata/role.d.ts +0 -38
- package/dist/metadata/role.js +0 -35
- package/dist/reads/eligibility.d.ts +0 -377
- package/dist/reads/eligibility.js +0 -687
- package/dist/tx/eligibility.d.ts +0 -511
- package/dist/tx/eligibility.js +0 -818
package/dist/reads/user.d.ts
CHANGED
|
@@ -1,21 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* User & account reads — typed wrappers over graph/documents/user.
|
|
3
|
-
*
|
|
4
|
-
* Serves the read paths of `pop user whoami` (src/commands/user/whoami.ts),
|
|
5
|
-
* `pop user profile` (src/commands/user/profile.ts), and the pre-write reads
|
|
6
|
-
* of `pop user join` / `pop user claim-hats` / `pop user update-profile`.
|
|
7
|
-
*
|
|
8
|
-
* Usernames and profile metadata are HOME-CHAIN account-registry state
|
|
9
|
-
* (that's where `pop user register` writes and `pop user profile` reads), so
|
|
10
|
-
* the account-scoped helpers default to HOME_CHAIN_ID rather than the
|
|
11
|
-
* client's default chain. An explicit chainId always wins.
|
|
12
|
-
*
|
|
13
|
-
* A null row from any of these means NOT-INDEXED, not nonexistent — the CLI
|
|
14
|
-
* keeps live RPC fallbacks (getUsername, balanceOf, Hats.balanceOf) for every
|
|
15
|
-
* decision where that difference matters, and `isAccountAuthoritative`
|
|
16
|
-
* (re-exported below) is the guard that says when an indexed account may
|
|
17
|
-
* stand in for a live registry read.
|
|
18
|
-
*/
|
|
1
|
+
/** Account/token history is retained; current organization membership comes from MembershipAuthority. */
|
|
19
2
|
import type { GraphClient } from '../graph/client';
|
|
20
3
|
import { IndexedAccount, IndexedQuickJoin } from '../graph/documents/user';
|
|
21
4
|
export { isAccountAuthoritative } from '../graph/documents/user';
|
|
@@ -73,13 +56,7 @@ export declare function getQuickJoinAccount(client: GraphClient, quickJoinAddres
|
|
|
73
56
|
quickJoinContract: IndexedQuickJoin | null;
|
|
74
57
|
account: IndexedAccount | null;
|
|
75
58
|
}>;
|
|
76
|
-
/**
|
|
77
|
-
* Just the QuickJoin module pointers (accountRegistry / hatsContract /
|
|
78
|
-
* memberHatIds) — the address read of `pop user claim-hats`
|
|
79
|
-
* (src/commands/user/claim-hats.ts). The pointers are static deploy-time
|
|
80
|
-
* values, byte-verified against the live eth_calls on Gnosis and Arbitrum;
|
|
81
|
-
* the claim pre-flight probes themselves stay on-chain in the CLI.
|
|
82
|
-
*/
|
|
59
|
+
/** Read the indexed account registry pointer for QuickJoin. */
|
|
83
60
|
export declare function getQuickJoinModules(client: GraphClient, quickJoinAddress: string, chainId?: number): Promise<IndexedQuickJoin | null>;
|
|
84
61
|
/** Account row served by FETCH_REGISTRY_AND_ACCOUNT (profile-merge shape). */
|
|
85
62
|
export interface RegistryAccount {
|
|
@@ -110,6 +87,8 @@ export interface OrgUser {
|
|
|
110
87
|
id: string;
|
|
111
88
|
address: string;
|
|
112
89
|
participationTokenBalance: string | null;
|
|
90
|
+
/** False when authority membership exists but its historical User is not indexed. */
|
|
91
|
+
historyIndexed?: boolean;
|
|
113
92
|
membershipStatus: string | null;
|
|
114
93
|
currentHatIds: string[] | null;
|
|
115
94
|
joinMethod: string | null;
|
|
@@ -179,10 +158,8 @@ export interface WhoamiOrgData {
|
|
|
179
158
|
name: string | null;
|
|
180
159
|
roles: Array<{
|
|
181
160
|
hatId: string;
|
|
161
|
+
subjectId: string;
|
|
182
162
|
name: string | null;
|
|
183
|
-
hat: {
|
|
184
|
-
active: boolean;
|
|
185
|
-
} | null;
|
|
186
163
|
}>;
|
|
187
164
|
} | null;
|
|
188
165
|
quickJoinContract: IndexedQuickJoin | null;
|
|
@@ -196,27 +173,15 @@ export interface WhoamiOrgData {
|
|
|
196
173
|
membershipStatus: string | null;
|
|
197
174
|
participationTokenBalance: string | null;
|
|
198
175
|
currentHatIds: string[] | null;
|
|
176
|
+
/** False when current membership is indexed before its historical User row. */
|
|
177
|
+
historyIndexed?: boolean;
|
|
199
178
|
} | null;
|
|
200
179
|
/** Pending requests by this user — the CLI reports `tokenRequests.length`. */
|
|
201
180
|
tokenRequests: Array<{
|
|
202
181
|
id: string;
|
|
203
182
|
}>;
|
|
204
183
|
}
|
|
205
|
-
/**
|
|
206
|
-
* One-round-trip org snapshot for `pop user whoami`
|
|
207
|
-
* (src/commands/user/whoami.ts): org name + role hats (with the Hats toggle
|
|
208
|
-
* flag), QuickJoin pointers, the caller's account/PT balance/org-user row,
|
|
209
|
-
* and their pending token requests. Variables are composed exactly as the
|
|
210
|
-
* CLI does — AddressZero placeholders keep the document valid when a module
|
|
211
|
-
* is missing (those selections then simply return null).
|
|
212
|
-
*
|
|
213
|
-
* Interpretation stays with the caller (the CLI keeps RPC fallbacks for
|
|
214
|
-
* username/PT/membership): gate `account.username` with
|
|
215
|
-
* isAccountAuthoritative(account, quickJoinContract?.accountRegistry), AND
|
|
216
|
-
* membership derived from currentHatIds with the role's `hat.active` flag —
|
|
217
|
-
* a toggled-off hat still appears in currentHatIds because Hats does not
|
|
218
|
-
* burn the token.
|
|
219
|
-
*/
|
|
184
|
+
/** Account/token history merged with current authority roles and membership. */
|
|
220
185
|
export declare function getWhoamiOrgData(client: GraphClient, params: {
|
|
221
186
|
/** Org hex ID (as returned by resolveOrgId / OrgModules.orgId). */
|
|
222
187
|
orgId: string;
|
package/dist/reads/user.js
CHANGED
|
@@ -1,22 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* User & account reads — typed wrappers over graph/documents/user.
|
|
4
|
-
*
|
|
5
|
-
* Serves the read paths of `pop user whoami` (src/commands/user/whoami.ts),
|
|
6
|
-
* `pop user profile` (src/commands/user/profile.ts), and the pre-write reads
|
|
7
|
-
* of `pop user join` / `pop user claim-hats` / `pop user update-profile`.
|
|
8
|
-
*
|
|
9
|
-
* Usernames and profile metadata are HOME-CHAIN account-registry state
|
|
10
|
-
* (that's where `pop user register` writes and `pop user profile` reads), so
|
|
11
|
-
* the account-scoped helpers default to HOME_CHAIN_ID rather than the
|
|
12
|
-
* client's default chain. An explicit chainId always wins.
|
|
13
|
-
*
|
|
14
|
-
* A null row from any of these means NOT-INDEXED, not nonexistent — the CLI
|
|
15
|
-
* keeps live RPC fallbacks (getUsername, balanceOf, Hats.balanceOf) for every
|
|
16
|
-
* decision where that difference matters, and `isAccountAuthoritative`
|
|
17
|
-
* (re-exported below) is the guard that says when an indexed account may
|
|
18
|
-
* stand in for a live registry read.
|
|
19
|
-
*/
|
|
2
|
+
/** Account/token history is retained; current organization membership comes from MembershipAuthority. */
|
|
20
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
4
|
exports.isAccountAuthoritative = void 0;
|
|
22
5
|
exports.getAccountByUsername = getAccountByUsername;
|
|
@@ -31,6 +14,7 @@ const ethers_1 = require("ethers");
|
|
|
31
14
|
const user_1 = require("../graph/documents/user");
|
|
32
15
|
const chains_1 = require("../chains");
|
|
33
16
|
const resolve_1 = require("./resolve");
|
|
17
|
+
const authority_1 = require("./authority");
|
|
34
18
|
// Pure selection helpers live in the documents module — re-exported here so
|
|
35
19
|
// read-layer consumers get the guard next to the reads it guards.
|
|
36
20
|
var user_2 = require("../graph/documents/user");
|
|
@@ -80,13 +64,7 @@ async function getQuickJoinAccount(client, quickJoinAddress, accountAddress, cha
|
|
|
80
64
|
account: result?.account ?? null,
|
|
81
65
|
};
|
|
82
66
|
}
|
|
83
|
-
/**
|
|
84
|
-
* Just the QuickJoin module pointers (accountRegistry / hatsContract /
|
|
85
|
-
* memberHatIds) — the address read of `pop user claim-hats`
|
|
86
|
-
* (src/commands/user/claim-hats.ts). The pointers are static deploy-time
|
|
87
|
-
* values, byte-verified against the live eth_calls on Gnosis and Arbitrum;
|
|
88
|
-
* the claim pre-flight probes themselves stay on-chain in the CLI.
|
|
89
|
-
*/
|
|
67
|
+
/** Read the indexed account registry pointer for QuickJoin. */
|
|
90
68
|
async function getQuickJoinModules(client, quickJoinAddress, chainId) {
|
|
91
69
|
const result = await client.query(user_1.FETCH_QUICKJOIN_MODULES, { quickJoinAddress: quickJoinAddress.toLowerCase() }, chainId);
|
|
92
70
|
return result?.quickJoinContract ?? null;
|
|
@@ -117,39 +95,44 @@ async function getOrgUserProfile(client, orgIdOrName, address, chainId) {
|
|
|
117
95
|
const orgId = await (0, resolve_1.resolveOrgId)(client, orgIdOrName, chainId);
|
|
118
96
|
const orgUserID = `${orgId.toLowerCase()}-${address.toLowerCase()}`;
|
|
119
97
|
const { data, tierIndex } = await client.queryWithFieldFallback((0, user_1.userDataTiers)(orgUserID, address.toLowerCase()), { chainId });
|
|
98
|
+
const users = await (0, authority_1.readAuthorityUsers)(client, orgId, data.user ? [data.user] : [], chainId);
|
|
99
|
+
const currentUser = users.find(user => user.address.toLowerCase() === address.toLowerCase());
|
|
120
100
|
return {
|
|
121
|
-
user:
|
|
101
|
+
user: currentUser ?? null,
|
|
122
102
|
account: data.account ?? null,
|
|
123
103
|
hasChurnCounters: tierIndex === 0,
|
|
124
104
|
};
|
|
125
105
|
}
|
|
126
|
-
/**
|
|
127
|
-
* One-round-trip org snapshot for `pop user whoami`
|
|
128
|
-
* (src/commands/user/whoami.ts): org name + role hats (with the Hats toggle
|
|
129
|
-
* flag), QuickJoin pointers, the caller's account/PT balance/org-user row,
|
|
130
|
-
* and their pending token requests. Variables are composed exactly as the
|
|
131
|
-
* CLI does — AddressZero placeholders keep the document valid when a module
|
|
132
|
-
* is missing (those selections then simply return null).
|
|
133
|
-
*
|
|
134
|
-
* Interpretation stays with the caller (the CLI keeps RPC fallbacks for
|
|
135
|
-
* username/PT/membership): gate `account.username` with
|
|
136
|
-
* isAccountAuthoritative(account, quickJoinContract?.accountRegistry), AND
|
|
137
|
-
* membership derived from currentHatIds with the role's `hat.active` flag —
|
|
138
|
-
* a toggled-off hat still appears in currentHatIds because Hats does not
|
|
139
|
-
* burn the token.
|
|
140
|
-
*/
|
|
106
|
+
/** Account/token history merged with current authority roles and membership. */
|
|
141
107
|
async function getWhoamiOrgData(client, params, chainId) {
|
|
108
|
+
const orgId = await (0, resolve_1.resolveOrgId)(client, params.orgId, chainId);
|
|
142
109
|
const address = params.address.toLowerCase();
|
|
143
110
|
const tokenAddress = params.participationTokenAddress || ethers_1.ethers.constants.AddressZero;
|
|
144
111
|
const quickJoinAddress = (params.quickJoinAddress || ethers_1.ethers.constants.AddressZero).toLowerCase();
|
|
145
112
|
const result = await client.query(user_1.FETCH_WHOAMI_ORG_DATA, {
|
|
146
|
-
orgId
|
|
147
|
-
orgUserID: `${
|
|
113
|
+
orgId,
|
|
114
|
+
orgUserID: `${orgId}-${address}`,
|
|
148
115
|
tokenAddress,
|
|
149
116
|
userAddress: address,
|
|
150
117
|
quickJoinAddress,
|
|
151
118
|
accountID: address,
|
|
152
119
|
tokenBalanceID: `${tokenAddress.toLowerCase()}-${address}`,
|
|
153
120
|
}, chainId);
|
|
154
|
-
|
|
121
|
+
if (!result)
|
|
122
|
+
return null;
|
|
123
|
+
const [subjects, memberships] = await Promise.all([
|
|
124
|
+
(0, authority_1.readAuthorityRows)(client, orgId, authority_1.FETCH_AUTHORITY_SUBJECTS, 'subjects', chainId),
|
|
125
|
+
(0, authority_1.readAuthorityMemberships)(client, orgId, chainId),
|
|
126
|
+
]);
|
|
127
|
+
const current = memberships.filter(row => row.user.toLowerCase() === address && row.isMember && row.subject.kind === 'Role');
|
|
128
|
+
if (result.organization)
|
|
129
|
+
result.organization.roles = subjects.filter(subject => subject.kind === 'Role').map(subject => ({ hatId: subject.subjectId, subjectId: subject.subjectId, name: subject.name }));
|
|
130
|
+
if (result.user || current.length)
|
|
131
|
+
result.user = {
|
|
132
|
+
id: `${orgId}-${address}`, participationTokenBalance: null,
|
|
133
|
+
...result.user, historyIndexed: result.user != null,
|
|
134
|
+
membershipStatus: current.length ? 'Active' : 'Inactive',
|
|
135
|
+
currentHatIds: [...new Set(current.map(row => row.subject.subjectId))],
|
|
136
|
+
};
|
|
137
|
+
return result;
|
|
155
138
|
}
|
package/dist/reads/vote.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type { GraphClient } from '../graph/client';
|
|
|
11
11
|
import { SubgraphVotingClass } from '../graph/documents/voting-classes';
|
|
12
12
|
/** One ballot as indexed by the subgraph (FETCH_VOTING_DATA field set). */
|
|
13
13
|
export interface SubgraphProposalVote {
|
|
14
|
+
id?: string;
|
|
14
15
|
voter: string;
|
|
15
16
|
voterUsername?: string | null;
|
|
16
17
|
optionIndexes: string[];
|
|
@@ -216,9 +217,19 @@ export interface ProposalResults {
|
|
|
216
217
|
totalVoters: number;
|
|
217
218
|
supportThresholdPct?: number;
|
|
218
219
|
quorumVoterCount?: number;
|
|
220
|
+
/** Compatibility quorumVoterCount is the live global config, not a restricted poll's override. */
|
|
221
|
+
quorumSource: 'current-global-config';
|
|
222
|
+
/** Null when the subgraph cannot identify the proposal's per-poll override. */
|
|
223
|
+
effectiveQuorumVoterCount: number | null;
|
|
219
224
|
ranking: ProposalResultsRankEntry[];
|
|
220
225
|
voters: ProposalResultsVoter[];
|
|
221
|
-
winner
|
|
226
|
+
/** Raw ballot allocation leader; this is not the class-weighted on-chain winner. */
|
|
227
|
+
allocationLeader: ProposalResultsRankEntry | null;
|
|
228
|
+
rankingBasis: 'sum-of-ballot-allocations';
|
|
229
|
+
winnerSource: 'announced' | 'unannounced' | 'invalid';
|
|
230
|
+
announcedWinningOption: number | null;
|
|
231
|
+
announcedValid: boolean | null;
|
|
232
|
+
winner: ProposalResultsRankEntry | null;
|
|
222
233
|
}
|
|
223
234
|
/**
|
|
224
235
|
* The `pop vote results` proposal document (src/commands/vote/results.ts).
|
|
@@ -228,7 +239,7 @@ export interface ProposalResults {
|
|
|
228
239
|
* this read exists to serve. The legacy tier is spelled out rather than
|
|
229
240
|
* derived so a reformat cannot silently turn it into a copy of the modern one.
|
|
230
241
|
*/
|
|
231
|
-
export declare function buildProposalResultsQuery(orgId: string, proposalId: number, modern: boolean): string;
|
|
242
|
+
export declare function buildProposalResultsQuery(orgId: string, proposalId: number, modern: boolean, after?: string): string;
|
|
232
243
|
/**
|
|
233
244
|
* Pure port of the `pop vote results` tally/ranking/attribution computation
|
|
234
245
|
* (src/commands/vote/results.ts) over the fetched hybridVoting row.
|
|
@@ -262,6 +273,15 @@ export declare function computeProposalResults(hybridVoting: {
|
|
|
262
273
|
* the proposal is not indexed.
|
|
263
274
|
*/
|
|
264
275
|
export declare function fetchProposalResults(client: GraphClient, orgIdOrName: string, proposalId: number, chainId?: number): Promise<ProposalResults>;
|
|
276
|
+
/** Shared CLI/SDK read after organization resolution; cursor-pagination prevents truncated tallies. */
|
|
277
|
+
export declare function fetchProposalResultsByOrgId(client: Pick<GraphClient, 'queryWithFieldFallback'>, orgId: string, proposalId: number, chainId?: number): Promise<ProposalResults>;
|
|
278
|
+
/** Full indexed ballot history plus exact class-emission pointers, with legacy schema fallback. */
|
|
279
|
+
export declare function fetchProposalVoteAnalysis(client: Pick<GraphClient, 'queryWithFieldFallback'>, hybridVotingAddress: string, proposalId: number, chainId?: number): Promise<any>;
|
|
280
|
+
/** Reproduce HybridVotingCore vote rounding and VotingMath.pickWinnerNSlices scores. */
|
|
281
|
+
export declare function computeClassWeightedScores(ballots: Array<{
|
|
282
|
+
weights: number[];
|
|
283
|
+
classRawPowers: bigint[];
|
|
284
|
+
}>, slices: number[], numOptions: number): number[];
|
|
265
285
|
/** Normalized class row — identical shape from the subgraph and the contract. */
|
|
266
286
|
export interface NormalizedClass {
|
|
267
287
|
classIndex: number;
|
|
@@ -271,11 +291,15 @@ export interface NormalizedClass {
|
|
|
271
291
|
minBalance: string;
|
|
272
292
|
asset: string;
|
|
273
293
|
hatIds: string[];
|
|
294
|
+
/** On-chain stable authority binding is not indexed; hatIds are only its fallback list. */
|
|
295
|
+
subjectId: string | null;
|
|
296
|
+
subjectBindingKnown: boolean;
|
|
274
297
|
}
|
|
275
298
|
export interface ClassConfigSnapshot {
|
|
276
299
|
classes: NormalizedClass[];
|
|
277
300
|
supportThresholdPct: number;
|
|
278
301
|
quorumVoterCount: number;
|
|
302
|
+
effectiveQuorumVoterCount: number | null;
|
|
279
303
|
}
|
|
280
304
|
/**
|
|
281
305
|
* Subgraph rows → the exact shape the contract path produces. `asset` is
|
package/dist/reads/vote.js
CHANGED
|
@@ -19,6 +19,9 @@ exports.evaluateExecuteGate = evaluateExecuteGate;
|
|
|
19
19
|
exports.buildProposalResultsQuery = buildProposalResultsQuery;
|
|
20
20
|
exports.computeProposalResults = computeProposalResults;
|
|
21
21
|
exports.fetchProposalResults = fetchProposalResults;
|
|
22
|
+
exports.fetchProposalResultsByOrgId = fetchProposalResultsByOrgId;
|
|
23
|
+
exports.fetchProposalVoteAnalysis = fetchProposalVoteAnalysis;
|
|
24
|
+
exports.computeClassWeightedScores = computeClassWeightedScores;
|
|
22
25
|
exports.normalizeSubgraphClasses = normalizeSubgraphClasses;
|
|
23
26
|
exports.fetchClassConfig = fetchClassConfig;
|
|
24
27
|
const ethers_1 = require("ethers");
|
|
@@ -210,8 +213,8 @@ function evaluateExecuteGate(proposal) {
|
|
|
210
213
|
* this read exists to serve. The legacy tier is spelled out rather than
|
|
211
214
|
* derived so a reformat cannot silently turn it into a copy of the modern one.
|
|
212
215
|
*/
|
|
213
|
-
function buildProposalResultsQuery(orgId, proposalId, modern) {
|
|
214
|
-
const proposalCore = 'proposalId title status';
|
|
216
|
+
function buildProposalResultsQuery(orgId, proposalId, modern, after = '') {
|
|
217
|
+
const proposalCore = 'proposalId title status numOptions isHatRestricted winningOption isValid';
|
|
215
218
|
return `{
|
|
216
219
|
organization(id: "${orgId}") {
|
|
217
220
|
hybridVoting {
|
|
@@ -223,7 +226,9 @@ function buildProposalResultsQuery(orgId, proposalId, modern) {
|
|
|
223
226
|
${modern ? 'proposer proposerUsername creatorUsername' : ''}
|
|
224
227
|
${modern ? 'classesVersion winnerAnnouncedAt executedAt executedCallsCount' : ''}
|
|
225
228
|
metadata { description optionNames${modern ? ' actionSummaries promotedFrom' : ''} }
|
|
226
|
-
votes {
|
|
229
|
+
votes(first: 1000, orderBy: id, orderDirection: asc, where: { id_gt: ${JSON.stringify(after)} }) {
|
|
230
|
+
id voter voterUsername optionIndexes optionWeights
|
|
231
|
+
}
|
|
227
232
|
}
|
|
228
233
|
}
|
|
229
234
|
}
|
|
@@ -246,17 +251,19 @@ function computeProposalResults(hybridVoting) {
|
|
|
246
251
|
return null;
|
|
247
252
|
// Two DISTINCT validity parameters — threshold is a % of weighted power,
|
|
248
253
|
// quorum is a raw voter count. Never conflate them.
|
|
249
|
-
const supportThresholdPct = hybridVoting.thresholdPct
|
|
250
|
-
const quorumVoterCount = hybridVoting.quorum
|
|
251
|
-
const
|
|
254
|
+
const supportThresholdPct = hybridVoting.thresholdPct != null ? Number(hybridVoting.thresholdPct) : undefined;
|
|
255
|
+
const quorumVoterCount = hybridVoting.quorum != null ? Number(hybridVoting.quorum) : undefined;
|
|
256
|
+
const namedOptions = proposal.metadata?.optionNames ?? [];
|
|
257
|
+
const optionCount = Number(proposal.numOptions ?? namedOptions.length);
|
|
258
|
+
const optionNames = Array.from({ length: optionCount }, (_, i) => namedOptions[i] || `Option ${i}`);
|
|
252
259
|
const votes = proposal.votes || [];
|
|
253
|
-
//
|
|
260
|
+
// Compatibility ranking sums ballot percentages; it does not apply class power.
|
|
254
261
|
const tallies = new Array(Math.max(optionNames.length, 1)).fill(0);
|
|
255
262
|
for (const v of votes) {
|
|
256
263
|
for (let i = 0; i < (v.optionIndexes || []).length; i++) {
|
|
257
264
|
const idx = parseInt(v.optionIndexes[i]);
|
|
258
265
|
const weight = parseInt(v.optionWeights[i]);
|
|
259
|
-
if (idx < tallies.length)
|
|
266
|
+
if (idx >= 0 && idx < tallies.length)
|
|
260
267
|
tallies[idx] += weight;
|
|
261
268
|
}
|
|
262
269
|
}
|
|
@@ -273,12 +280,10 @@ function computeProposalResults(hybridVoting) {
|
|
|
273
280
|
const name = optionNames[idx] || `Option ${idx}`;
|
|
274
281
|
allocations[name] = parseInt(v.optionWeights[i]);
|
|
275
282
|
}
|
|
276
|
-
return { voter: v.voterUsername || 'unknown', allocations };
|
|
283
|
+
return { voter: v.voterUsername || v.voter || 'unknown', allocations };
|
|
277
284
|
});
|
|
278
285
|
const proposedBy = proposal.proposerUsername || proposal.creatorUsername || null;
|
|
279
|
-
//
|
|
280
|
-
// CREATED, so a live config change mid-flight makes the org-level
|
|
281
|
-
// threshold/quorum shown here stale.
|
|
286
|
+
// Class membership and slices are snapshotted; threshold and global quorum remain live.
|
|
282
287
|
const classVersionAtCreation = proposal.classesVersion != null ? String(proposal.classesVersion) : null;
|
|
283
288
|
const liveClassVersion = hybridVoting.classVersion != null ? String(hybridVoting.classVersion) : null;
|
|
284
289
|
const classConfigDrifted = Boolean(classVersionAtCreation && liveClassVersion && classVersionAtCreation !== liveClassVersion);
|
|
@@ -299,9 +304,17 @@ function computeProposalResults(hybridVoting) {
|
|
|
299
304
|
totalVoters: votes.length,
|
|
300
305
|
supportThresholdPct,
|
|
301
306
|
quorumVoterCount,
|
|
307
|
+
quorumSource: 'current-global-config',
|
|
308
|
+
effectiveQuorumVoterCount: proposal.isHatRestricted === false ? quorumVoterCount ?? null : null,
|
|
302
309
|
ranking: ranked,
|
|
303
310
|
voters: voterBreakdown,
|
|
304
|
-
|
|
311
|
+
rankingBasis: 'sum-of-ballot-allocations',
|
|
312
|
+
allocationLeader: ranked[0] ?? null,
|
|
313
|
+
announcedWinningOption: proposal.winningOption != null ? Number(proposal.winningOption) : null,
|
|
314
|
+
announcedValid: proposal.isValid ?? null,
|
|
315
|
+
winnerSource: proposal.winningOption == null ? 'unannounced' : proposal.isValid === true ? 'announced' : 'invalid',
|
|
316
|
+
winner: proposal.winningOption != null && proposal.isValid === true
|
|
317
|
+
? ranked.find(r => r.option === Number(proposal.winningOption)) ?? null : null,
|
|
305
318
|
};
|
|
306
319
|
}
|
|
307
320
|
/**
|
|
@@ -311,15 +324,73 @@ function computeProposalResults(hybridVoting) {
|
|
|
311
324
|
*/
|
|
312
325
|
async function fetchProposalResults(client, orgIdOrName, proposalId, chainId) {
|
|
313
326
|
const orgId = await (0, resolve_1.resolveOrgId)(client, orgIdOrName, chainId);
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
327
|
+
return fetchProposalResultsByOrgId(client, orgId, proposalId, chainId);
|
|
328
|
+
}
|
|
329
|
+
/** Shared CLI/SDK read after organization resolution; cursor-pagination prevents truncated tallies. */
|
|
330
|
+
async function fetchProposalResultsByOrgId(client, orgId, proposalId, chainId) {
|
|
331
|
+
let after = '';
|
|
332
|
+
let hybridVoting;
|
|
333
|
+
const votes = [];
|
|
334
|
+
for (;;) {
|
|
335
|
+
const { data } = await client.queryWithFieldFallback([
|
|
336
|
+
{ query: buildProposalResultsQuery(orgId, proposalId, true, after) },
|
|
337
|
+
{ query: buildProposalResultsQuery(orgId, proposalId, false, after) },
|
|
338
|
+
], { chainId });
|
|
339
|
+
const row = data.organization?.hybridVoting;
|
|
340
|
+
if (!row?.proposals?.[0])
|
|
341
|
+
throw new errors_1.CliError(`Proposal #${proposalId} not found`, exit_codes_1.EXIT.USAGE);
|
|
342
|
+
hybridVoting ??= row;
|
|
343
|
+
const page = row.proposals[0].votes ?? [];
|
|
344
|
+
votes.push(...page);
|
|
345
|
+
if (page.length < 1000)
|
|
346
|
+
break;
|
|
347
|
+
const next = page[page.length - 1].id;
|
|
348
|
+
if (!next || next <= after)
|
|
349
|
+
throw new errors_1.CliError('Cannot paginate proposal votes without an advancing vote ID.', exit_codes_1.EXIT.PRECONDITION);
|
|
350
|
+
after = next;
|
|
351
|
+
}
|
|
352
|
+
hybridVoting.proposals[0].votes = votes;
|
|
353
|
+
return computeProposalResults(hybridVoting);
|
|
354
|
+
}
|
|
355
|
+
/** Full indexed ballot history plus exact class-emission pointers, with legacy schema fallback. */
|
|
356
|
+
async function fetchProposalVoteAnalysis(client, hybridVotingAddress, proposalId, chainId) {
|
|
357
|
+
let after = '';
|
|
358
|
+
let first;
|
|
359
|
+
const votes = [];
|
|
360
|
+
for (;;) {
|
|
361
|
+
const variables = { hybridVoting: hybridVotingAddress.toLowerCase(), proposalId: String(proposalId), after };
|
|
362
|
+
const { data } = await client.queryWithFieldFallback([
|
|
363
|
+
{ query: voting_classes_1.FETCH_PROPOSAL_VOTE_ANALYSIS_EXACT, variables },
|
|
364
|
+
{ query: voting_classes_1.FETCH_PROPOSAL_VOTE_ANALYSIS, variables },
|
|
365
|
+
], { chainId });
|
|
366
|
+
first ??= data;
|
|
367
|
+
const page = data.hybridVotingContract?.proposals?.[0]?.votes ?? [];
|
|
368
|
+
votes.push(...page);
|
|
369
|
+
if (page.length < 1000)
|
|
370
|
+
break;
|
|
371
|
+
const next = page[page.length - 1].id;
|
|
372
|
+
if (!next || next <= after)
|
|
373
|
+
throw new errors_1.CliError('Cannot paginate proposal votes without an advancing vote ID.', exit_codes_1.EXIT.PRECONDITION);
|
|
374
|
+
after = next;
|
|
375
|
+
}
|
|
376
|
+
if (first.hybridVotingContract?.proposals?.[0])
|
|
377
|
+
first.hybridVotingContract.proposals[0].votes = votes;
|
|
378
|
+
return first;
|
|
379
|
+
}
|
|
380
|
+
/** Reproduce HybridVotingCore vote rounding and VotingMath.pickWinnerNSlices scores. */
|
|
381
|
+
function computeClassWeightedScores(ballots, slices, numOptions) {
|
|
382
|
+
const totals = slices.map((_, cls) => ballots.reduce((sum, v) => sum + (v.classRawPowers[cls] ?? 0n), 0n));
|
|
383
|
+
return Array.from({ length: numOptions }, (_, option) => {
|
|
384
|
+
let score = 0n;
|
|
385
|
+
for (let cls = 0; cls < slices.length; cls++) {
|
|
386
|
+
if (totals[cls] === 0n)
|
|
387
|
+
continue;
|
|
388
|
+
const optionRaw = ballots.reduce((sum, v) => sum + (v.classRawPowers[cls] ?? 0n) * BigInt(v.weights[option] ?? 0) / 100n, 0n);
|
|
389
|
+
score += optionRaw * BigInt(slices[cls]) * 10000n / totals[cls];
|
|
390
|
+
}
|
|
391
|
+
// Scores are bounded by 100 * 10000, so this conversion is exact.
|
|
392
|
+
return Number(score) / 10000;
|
|
393
|
+
});
|
|
323
394
|
}
|
|
324
395
|
/**
|
|
325
396
|
* Subgraph rows → the exact shape the contract path produces. `asset` is
|
|
@@ -336,6 +407,8 @@ function normalizeSubgraphClasses(rows) {
|
|
|
336
407
|
minBalance: ethers_1.ethers.BigNumber.from(String(c.minBalance ?? '0')).toString(),
|
|
337
408
|
asset: ethers_1.ethers.utils.getAddress(String(c.asset)),
|
|
338
409
|
hatIds: (c.hatIds ?? []).map(h => String(h)),
|
|
410
|
+
subjectId: null,
|
|
411
|
+
subjectBindingKnown: false,
|
|
339
412
|
}));
|
|
340
413
|
}
|
|
341
414
|
/**
|
|
@@ -355,23 +428,30 @@ async function fetchClassConfig(client, hybridVotingAddress, proposalId, chainId
|
|
|
355
428
|
// guessing the newest version would silently misreport an old proposal.
|
|
356
429
|
const tiers = proposalId === undefined
|
|
357
430
|
? [
|
|
431
|
+
{ query: voting_classes_1.FETCH_VOTING_CLASS_CONFIG_EXACT, variables: { hybridVoting } },
|
|
358
432
|
{ query: voting_classes_1.FETCH_VOTING_CLASS_CONFIG, variables: { hybridVoting } },
|
|
359
433
|
{ query: voting_classes_1.FETCH_VOTING_CLASS_CONFIG_LEGACY, variables: { hybridVoting } },
|
|
360
434
|
]
|
|
361
435
|
: [
|
|
436
|
+
{ query: voting_classes_1.FETCH_PROPOSAL_VOTING_CLASSES_EXACT, variables: { hybridVoting, proposalId: String(proposalId) } },
|
|
362
437
|
{ query: voting_classes_1.FETCH_PROPOSAL_VOTING_CLASSES, variables: { hybridVoting, proposalId: String(proposalId) } },
|
|
363
438
|
];
|
|
364
439
|
const { data } = await client.queryWithFieldFallback(tiers, { chainId });
|
|
365
440
|
const contract = data?.hybridVotingContract;
|
|
366
441
|
if (!contract)
|
|
367
442
|
return null;
|
|
443
|
+
// createProposalV2 may snapshot a synthetic equal-weight class for a restricted poll.
|
|
444
|
+
// The index has no ProposalConfigV2/synthetic snapshot fields; its ordinary class version
|
|
445
|
+
// cannot distinguish those proposals. Unknown restriction metadata is equally untrusted.
|
|
446
|
+
if (proposalId !== undefined && contract.proposals?.[0]?.isHatRestricted !== false)
|
|
447
|
+
return null;
|
|
368
448
|
const version = proposalId === undefined
|
|
369
449
|
? contract.classVersion
|
|
370
450
|
: contract.proposals?.[0]?.classesVersion;
|
|
371
451
|
// For a proposal we must know its frozen version — no version, no answer.
|
|
372
452
|
if (proposalId !== undefined && (version === null || version === undefined))
|
|
373
453
|
return null;
|
|
374
|
-
const rows = (0, voting_classes_1.
|
|
454
|
+
const rows = (0, voting_classes_1.selectIndexedClassSnapshot)(contract, proposalId);
|
|
375
455
|
if (rows.length === 0)
|
|
376
456
|
return null;
|
|
377
457
|
if (contract.thresholdPct === null || contract.thresholdPct === undefined)
|
|
@@ -382,6 +462,7 @@ async function fetchClassConfig(client, hybridVotingAddress, proposalId, chainId
|
|
|
382
462
|
classes: normalizeSubgraphClasses(rows),
|
|
383
463
|
supportThresholdPct: Number(contract.thresholdPct),
|
|
384
464
|
quorumVoterCount: Number(contract.quorum),
|
|
465
|
+
effectiveQuorumVoterCount: proposalId === undefined || contract.proposals?.[0]?.isHatRestricted === false ? Number(contract.quorum) : null,
|
|
385
466
|
};
|
|
386
467
|
}
|
|
387
468
|
catch {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** MembershipAuthority intent builders. No legacy Hats/EligibilityModule write surface. */
|
|
2
|
+
import { ethers } from 'ethers';
|
|
3
|
+
import type { TxIntent } from './intent';
|
|
4
|
+
/** The high byte selects the fold; the remaining 248 bits are the label hash shifted right. */
|
|
5
|
+
export declare function permissionKey(label: string, foldTag?: number): string;
|
|
6
|
+
export declare const AUTHORITY_KEYS: {
|
|
7
|
+
DD_VOTE: string;
|
|
8
|
+
DD_CREATE: string;
|
|
9
|
+
HV_CREATE: string;
|
|
10
|
+
TM_PERMS: string;
|
|
11
|
+
PT_MEMBER: string;
|
|
12
|
+
PT_APPROVE: string;
|
|
13
|
+
EDU_CREATE: string;
|
|
14
|
+
EDU_MEMBER: string;
|
|
15
|
+
QJ_AUTOJOIN: string;
|
|
16
|
+
PAY_CREATE: string;
|
|
17
|
+
SUBJECT_RENAME: string;
|
|
18
|
+
};
|
|
19
|
+
export declare const EXISTS_BIT: ethers.BigNumber;
|
|
20
|
+
export declare const INHERIT_GLOBAL_BIT: ethers.BigNumber;
|
|
21
|
+
export declare const VALUE_MASK: ethers.BigNumber;
|
|
22
|
+
export declare function permissionWord(value: ethers.BigNumberish, inheritGlobal?: boolean): ethers.BigNumber;
|
|
23
|
+
/** Project zero must not collide with the global context. */
|
|
24
|
+
export declare function projectContext(projectId: ethers.BigNumberish): string;
|
|
25
|
+
/** Explicit allowlist excludes initialization, migration seeds and compatibility mint selectors. */
|
|
26
|
+
export declare const AUTHORITY_METHODS: readonly ["claim", "renounce", "reconcile(uint256,address)", "reconcile(uint256,address[])", "grant", "offer", "withdrawOffer", "remove", "unremove", "setRule", "clearRule", "delegatedGrant", "delegatedOffer", "delegatedRemove", "delegatedUnremove", "finalize", "cancel", "setManagerConfig", "createRole", "createGroup", "addRoleToGroup", "removeRoleFromGroup", "renameSubject", "setMaxMembers", "setSubjectDefault", "configureVouchAttestor", "resetVouchEpoch", "clearUserVouches", "setMaxDailyVouches", "vouch", "revokeVouch", "setPerm", "clearPerm", "setPaused"];
|
|
27
|
+
export type AuthorityMethod = typeof AUTHORITY_METHODS[number];
|
|
28
|
+
export declare function buildAuthorityAction(a: {
|
|
29
|
+
authorityAddress: string;
|
|
30
|
+
method: AuthorityMethod;
|
|
31
|
+
args: unknown[];
|
|
32
|
+
orgId?: string;
|
|
33
|
+
}): TxIntent;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AUTHORITY_METHODS = exports.VALUE_MASK = exports.INHERIT_GLOBAL_BIT = exports.EXISTS_BIT = exports.AUTHORITY_KEYS = void 0;
|
|
4
|
+
exports.permissionKey = permissionKey;
|
|
5
|
+
exports.permissionWord = permissionWord;
|
|
6
|
+
exports.projectContext = projectContext;
|
|
7
|
+
exports.buildAuthorityAction = buildAuthorityAction;
|
|
8
|
+
/** MembershipAuthority intent builders. No legacy Hats/EligibilityModule write surface. */
|
|
9
|
+
const ethers_1 = require("ethers");
|
|
10
|
+
const contracts_1 = require("../contracts");
|
|
11
|
+
/** The high byte selects the fold; the remaining 248 bits are the label hash shifted right. */
|
|
12
|
+
function permissionKey(label, foldTag = 0) {
|
|
13
|
+
if (!Number.isInteger(foldTag) || foldTag < 0 || foldTag > 2)
|
|
14
|
+
throw new Error('Invalid permission fold tag');
|
|
15
|
+
return ethers_1.ethers.utils.hexZeroPad(ethers_1.ethers.BigNumber.from(ethers_1.ethers.utils.id(label)).shr(8)
|
|
16
|
+
.or(ethers_1.ethers.BigNumber.from(foldTag).shl(248)).toHexString(), 32);
|
|
17
|
+
}
|
|
18
|
+
exports.AUTHORITY_KEYS = {
|
|
19
|
+
DD_VOTE: permissionKey('poa.perm.dd.vote'), DD_CREATE: permissionKey('poa.perm.dd.create'),
|
|
20
|
+
HV_CREATE: permissionKey('poa.perm.hv.create'), TM_PERMS: permissionKey('poa.perm.tm.perms', 1),
|
|
21
|
+
PT_MEMBER: permissionKey('poa.perm.pt.member'), PT_APPROVE: permissionKey('poa.perm.pt.approve'),
|
|
22
|
+
EDU_CREATE: permissionKey('poa.perm.edu.create'), EDU_MEMBER: permissionKey('poa.perm.edu.member'),
|
|
23
|
+
QJ_AUTOJOIN: permissionKey('poa.perm.qj.autojoin'), PAY_CREATE: permissionKey('poa.perm.pay.create'),
|
|
24
|
+
SUBJECT_RENAME: permissionKey('poa.perm.subject.rename'),
|
|
25
|
+
};
|
|
26
|
+
exports.EXISTS_BIT = ethers_1.ethers.BigNumber.from(1).shl(255);
|
|
27
|
+
exports.INHERIT_GLOBAL_BIT = ethers_1.ethers.BigNumber.from(1).shl(254);
|
|
28
|
+
exports.VALUE_MASK = exports.INHERIT_GLOBAL_BIT.sub(1);
|
|
29
|
+
function permissionWord(value, inheritGlobal = false) {
|
|
30
|
+
const word = ethers_1.ethers.BigNumber.from(value);
|
|
31
|
+
if (word.lt(0) || word.gt(exports.VALUE_MASK))
|
|
32
|
+
throw new Error('Permission value must fit in 254 bits');
|
|
33
|
+
return word.or(exports.EXISTS_BIT).or(inheritGlobal ? exports.INHERIT_GLOBAL_BIT : 0);
|
|
34
|
+
}
|
|
35
|
+
/** Project zero must not collide with the global context. */
|
|
36
|
+
function projectContext(projectId) {
|
|
37
|
+
const id = ethers_1.ethers.BigNumber.from(projectId);
|
|
38
|
+
if (id.lt(0) || id.gte(ethers_1.ethers.constants.MaxUint256))
|
|
39
|
+
throw new Error('Project ID cannot be encoded as projectId + 1');
|
|
40
|
+
return ethers_1.ethers.utils.hexZeroPad(id.add(1).toHexString(), 32);
|
|
41
|
+
}
|
|
42
|
+
/** Explicit allowlist excludes initialization, migration seeds and compatibility mint selectors. */
|
|
43
|
+
exports.AUTHORITY_METHODS = [
|
|
44
|
+
'claim', 'renounce', 'reconcile(uint256,address)', 'reconcile(uint256,address[])',
|
|
45
|
+
'grant', 'offer', 'withdrawOffer', 'remove', 'unremove', 'setRule', 'clearRule',
|
|
46
|
+
'delegatedGrant', 'delegatedOffer', 'delegatedRemove', 'delegatedUnremove', 'finalize', 'cancel',
|
|
47
|
+
'setManagerConfig', 'createRole', 'createGroup', 'addRoleToGroup', 'removeRoleFromGroup',
|
|
48
|
+
'renameSubject', 'setMaxMembers', 'setSubjectDefault', 'configureVouchAttestor',
|
|
49
|
+
'resetVouchEpoch', 'clearUserVouches', 'setMaxDailyVouches', 'vouch', 'revokeVouch',
|
|
50
|
+
'setPerm', 'clearPerm', 'setPaused',
|
|
51
|
+
];
|
|
52
|
+
function buildAuthorityAction(a) {
|
|
53
|
+
if (!exports.AUTHORITY_METHODS.includes(a.method))
|
|
54
|
+
throw new Error('Unsupported authority action');
|
|
55
|
+
if (!ethers_1.ethers.utils.isAddress(a.authorityAddress) || a.authorityAddress === ethers_1.ethers.constants.AddressZero) {
|
|
56
|
+
throw new Error('A nonzero MembershipAuthority address is required');
|
|
57
|
+
}
|
|
58
|
+
const abi = (0, contracts_1.getAbi)('MembershipAuthority');
|
|
59
|
+
new ethers_1.ethers.utils.Interface(abi).encodeFunctionData(a.method, a.args);
|
|
60
|
+
return { to: a.authorityAddress, abi, method: a.method, args: a.args,
|
|
61
|
+
meta: { domain: 'role', action: a.method, orgId: a.orgId, summary: { authority: a.authorityAddress } } };
|
|
62
|
+
}
|
package/dist/tx/governance.d.ts
CHANGED
|
@@ -47,6 +47,12 @@ export interface GovernanceWrapParams {
|
|
|
47
47
|
batches: ExecutionCall[][];
|
|
48
48
|
/** Restricted-voting hats; [] default. */
|
|
49
49
|
hatIds?: ethers.BigNumberish[];
|
|
50
|
+
/** Native MembershipAuthority subjects restricting voting; preferred over the legacy hatIds name. */
|
|
51
|
+
subjectIds?: ethers.BigNumberish[];
|
|
52
|
+
/** Restricted polls only. Executable polls use max(global quorum, override). */
|
|
53
|
+
quorumOverride?: number;
|
|
54
|
+
/** Hybrid restricted polls only: count each eligible voter once. */
|
|
55
|
+
equalWeight?: boolean;
|
|
50
56
|
orgId?: string;
|
|
51
57
|
/** meta.action override (default 'create'). */
|
|
52
58
|
action?: string;
|
package/dist/tx/governance.js
CHANGED
|
@@ -39,11 +39,28 @@ function buildGovernanceProposal(p) {
|
|
|
39
39
|
// [target, BigNumber(value), data] — same tuple construction as the CLI
|
|
40
40
|
// (`c.value || '0'` handles undefined/empty values as zero).
|
|
41
41
|
const batchTuples = p.batches.map(batch => batch.map(call => [call.target, ethers_1.ethers.BigNumber.from(call.value || 0), call.calldata]));
|
|
42
|
-
|
|
42
|
+
if (p.subjectIds !== undefined && p.hatIds !== undefined)
|
|
43
|
+
throw new Error('Specify subjectIds or hatIds, not both.');
|
|
44
|
+
const hatIds = (p.subjectIds ?? p.hatIds ?? []).map(id => {
|
|
45
|
+
const value = ethers_1.ethers.BigNumber.from(id);
|
|
46
|
+
if (value.lt(0) || value.gt(ethers_1.ethers.constants.MaxUint256))
|
|
47
|
+
throw new Error('Subject IDs must fit uint256.');
|
|
48
|
+
return value;
|
|
49
|
+
});
|
|
50
|
+
const quorum = p.quorumOverride ?? 0;
|
|
51
|
+
if (!Number.isInteger(quorum) || quorum < 0 || quorum > 4294967295)
|
|
52
|
+
throw new Error('quorumOverride must be a uint32 voter count.');
|
|
53
|
+
if (p.equalWeight !== undefined && typeof p.equalWeight !== 'boolean')
|
|
54
|
+
throw new Error('equalWeight must be a boolean.');
|
|
55
|
+
if (p.equalWeight && p.votingAbiName !== 'HybridVotingNew')
|
|
56
|
+
throw new Error('Equal-weight voting is supported only by HybridVoting.');
|
|
57
|
+
if ((quorum !== 0 || p.equalWeight) && hatIds.length === 0)
|
|
58
|
+
throw new Error('Quorum overrides and equal-weight voting require restricted subject IDs.');
|
|
59
|
+
const v2 = p.quorumOverride !== undefined || p.equalWeight === true;
|
|
43
60
|
return {
|
|
44
61
|
to: p.votingAddress,
|
|
45
62
|
abi: (0, contracts_1.getAbi)(p.votingAbiName),
|
|
46
|
-
method: 'createProposal',
|
|
63
|
+
method: v2 ? 'createProposalV2' : 'createProposal',
|
|
47
64
|
args: [
|
|
48
65
|
(0, encoding_1.stringToBytes)(p.title),
|
|
49
66
|
p.descriptionHash,
|
|
@@ -51,6 +68,7 @@ function buildGovernanceProposal(p) {
|
|
|
51
68
|
p.numOptions,
|
|
52
69
|
batchTuples,
|
|
53
70
|
hatIds,
|
|
71
|
+
...(v2 ? [quorum, ...(p.votingAbiName === 'HybridVotingNew' ? [p.equalWeight ?? false] : [])] : []),
|
|
54
72
|
],
|
|
55
73
|
meta: {
|
|
56
74
|
domain: p.domain ?? 'vote',
|
package/dist/tx/index.d.ts
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export * from './intent';
|
|
6
6
|
export * as education from './education';
|
|
7
|
-
export * as eligibility from './eligibility';
|
|
8
7
|
export * as governance from './governance';
|
|
9
8
|
export * as org from './org';
|
|
10
9
|
export * as paymaster from './paymaster';
|
|
@@ -15,3 +14,4 @@ export * as treasury from './treasury';
|
|
|
15
14
|
export * as user from './user';
|
|
16
15
|
export * as vote from './vote';
|
|
17
16
|
export * as zkemail from './zkemail';
|
|
17
|
+
export * as authority from './authority';
|