@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.
Files changed (95) hide show
  1. package/README.md +2 -0
  2. package/dist/abis/AuthorityRouter.d.ts +448 -0
  3. package/dist/abis/AuthorityRouter.js +584 -0
  4. package/dist/abis/CutoverVerifier.d.ts +194 -0
  5. package/dist/abis/CutoverVerifier.js +259 -0
  6. package/dist/abis/DirectDemocracyVotingNew.d.ts +168 -30
  7. package/dist/abis/DirectDemocracyVotingNew.js +214 -35
  8. package/dist/abis/EducationHubNew.d.ts +32 -75
  9. package/dist/abis/EducationHubNew.js +38 -94
  10. package/dist/abis/Executor.d.ts +56 -86
  11. package/dist/abis/Executor.js +68 -104
  12. package/dist/abis/HybridVotingNew.d.ts +279 -35
  13. package/dist/abis/HybridVotingNew.js +357 -40
  14. package/dist/abis/{EligibilityModuleNew.d.ts → MembershipAuthority.d.ts} +1437 -718
  15. package/dist/abis/{EligibilityModuleNew.js → MembershipAuthority.js} +1821 -899
  16. package/dist/abis/OrgDeployerNew.d.ts +193 -127
  17. package/dist/abis/OrgDeployerNew.js +244 -160
  18. package/dist/abis/OrgRegistry.d.ts +40 -0
  19. package/dist/abis/OrgRegistry.js +51 -0
  20. package/dist/abis/ParticipationToken.d.ts +32 -75
  21. package/dist/abis/ParticipationToken.js +38 -94
  22. package/dist/abis/PasskeyAccount.d.ts +28 -0
  23. package/dist/abis/PasskeyAccount.js +35 -0
  24. package/dist/abis/PaymasterHub.d.ts +527 -47
  25. package/dist/abis/PaymasterHub.js +750 -135
  26. package/dist/abis/QuickJoinNew.d.ts +59 -170
  27. package/dist/abis/QuickJoinNew.js +71 -215
  28. package/dist/abis/TaskManagerNew.d.ts +27 -44
  29. package/dist/abis/TaskManagerNew.js +31 -52
  30. package/dist/abis/UniversalAccountRegistry.d.ts +24 -0
  31. package/dist/abis/UniversalAccountRegistry.js +30 -0
  32. package/dist/abis/ZkEmailInvites.d.ts +44 -0
  33. package/dist/abis/ZkEmailInvites.js +55 -0
  34. package/dist/abis/index.d.ts +6 -4
  35. package/dist/abis/index.js +10 -7
  36. package/dist/chains.js +3 -3
  37. package/dist/error-catalog.js +125 -7
  38. package/dist/graph/documents/activity.d.ts +3 -1
  39. package/dist/graph/documents/activity.js +19 -14
  40. package/dist/graph/documents/org.d.ts +4 -4
  41. package/dist/graph/documents/org.js +10 -4
  42. package/dist/graph/documents/task.d.ts +10 -1
  43. package/dist/graph/documents/task.js +157 -3
  44. package/dist/graph/documents/user.d.ts +5 -30
  45. package/dist/graph/documents/user.js +2 -42
  46. package/dist/graph/documents/voting-classes.d.ts +23 -2
  47. package/dist/graph/documents/voting-classes.js +119 -10
  48. package/dist/metadata/index.d.ts +0 -1
  49. package/dist/metadata/index.js +1 -2
  50. package/dist/perms.d.ts +0 -15
  51. package/dist/perms.js +1 -21
  52. package/dist/preflight.d.ts +2 -2
  53. package/dist/preflight.js +14 -22
  54. package/dist/reads/authority.d.ts +33 -0
  55. package/dist/reads/authority.js +196 -0
  56. package/dist/reads/index.d.ts +1 -1
  57. package/dist/reads/index.js +2 -2
  58. package/dist/reads/org.d.ts +20 -20
  59. package/dist/reads/org.js +94 -28
  60. package/dist/reads/project.js +3 -3
  61. package/dist/reads/resolve.d.ts +1 -1
  62. package/dist/reads/resolve.js +12 -3
  63. package/dist/reads/task.d.ts +44 -22
  64. package/dist/reads/task.js +126 -67
  65. package/dist/reads/user.d.ts +8 -43
  66. package/dist/reads/user.js +27 -44
  67. package/dist/reads/vote.d.ts +26 -2
  68. package/dist/reads/vote.js +104 -23
  69. package/dist/tx/authority.d.ts +33 -0
  70. package/dist/tx/authority.js +62 -0
  71. package/dist/tx/governance.d.ts +6 -0
  72. package/dist/tx/governance.js +20 -2
  73. package/dist/tx/index.d.ts +1 -1
  74. package/dist/tx/index.js +2 -2
  75. package/dist/tx/org.d.ts +23 -18
  76. package/dist/tx/org.js +203 -67
  77. package/dist/tx/project.d.ts +3 -2
  78. package/dist/tx/project.js +28 -10
  79. package/dist/tx/task.d.ts +13 -24
  80. package/dist/tx/task.js +45 -85
  81. package/dist/tx/user.d.ts +2 -40
  82. package/dist/tx/user.js +2 -76
  83. package/dist/tx/vote.d.ts +5 -0
  84. package/dist/tx/vote.js +54 -43
  85. package/dist/version.d.ts +0 -5
  86. package/dist/version.js +1 -13
  87. package/package.json +1 -1
  88. package/dist/abis/ToggleModule.d.ts +0 -193
  89. package/dist/abis/ToggleModule.js +0 -255
  90. package/dist/metadata/role.d.ts +0 -38
  91. package/dist/metadata/role.js +0 -35
  92. package/dist/reads/eligibility.d.ts +0 -377
  93. package/dist/reads/eligibility.js +0 -687
  94. package/dist/tx/eligibility.d.ts +0 -511
  95. package/dist/tx/eligibility.js +0 -818
package/dist/tx/index.js CHANGED
@@ -40,10 +40,9 @@ var __importStar = (this && this.__importStar) || (function () {
40
40
  };
41
41
  })();
42
42
  Object.defineProperty(exports, "__esModule", { value: true });
43
- exports.zkemail = exports.vote = exports.user = exports.treasury = exports.token = exports.task = exports.project = exports.paymaster = exports.org = exports.governance = exports.eligibility = exports.education = void 0;
43
+ exports.authority = exports.zkemail = exports.vote = exports.user = exports.treasury = exports.token = exports.task = exports.project = exports.paymaster = exports.org = exports.governance = exports.education = void 0;
44
44
  __exportStar(require("./intent"), exports);
45
45
  exports.education = __importStar(require("./education"));
46
- exports.eligibility = __importStar(require("./eligibility"));
47
46
  exports.governance = __importStar(require("./governance"));
48
47
  exports.org = __importStar(require("./org"));
49
48
  exports.paymaster = __importStar(require("./paymaster"));
@@ -54,3 +53,4 @@ exports.treasury = __importStar(require("./treasury"));
54
53
  exports.user = __importStar(require("./user"));
55
54
  exports.vote = __importStar(require("./vote"));
56
55
  exports.zkemail = __importStar(require("./zkemail"));
56
+ exports.authority = __importStar(require("./authority"));
package/dist/tx/org.d.ts CHANGED
@@ -27,44 +27,45 @@ export interface OrgDeployConfig {
27
27
  autoUpgrade?: boolean;
28
28
  hybridVoting: {
29
29
  thresholdPct: number;
30
+ quorum?: number;
30
31
  classes: Array<{
31
32
  strategy: 'DIRECT' | 'ERC20_BAL';
32
33
  slicePct: number;
33
34
  quadratic?: boolean;
34
35
  minBalance?: string;
35
36
  asset?: string;
36
- hatIds?: number[];
37
+ subjectIds?: string[];
37
38
  }>;
38
39
  };
39
- directDemocracy: {
40
+ directDemocracy?: {
40
41
  thresholdPct: number;
42
+ quorum?: number;
41
43
  };
42
44
  roles: Array<{
43
45
  name: string;
44
46
  image?: string;
45
47
  canVote: boolean;
48
+ metadataCID?: string;
49
+ open: boolean;
50
+ maxMembers?: number;
46
51
  vouching?: {
47
52
  enabled: boolean;
48
53
  quorum: number;
49
54
  voucherRoleIndex: number;
50
- combineWithHierarchy?: boolean;
51
- };
52
- defaults?: {
53
- eligible: boolean;
54
- standing: boolean;
55
- };
56
- hierarchy?: {
57
- adminRoleIndex: number;
58
55
  };
59
56
  distribution?: {
60
57
  mintToDeployer: boolean;
61
58
  additionalWearers?: string[];
62
59
  };
63
- hatConfig?: {
64
- maxSupply: number;
65
- mutableHat: boolean;
66
- };
67
60
  }>;
61
+ groups?: Array<{
62
+ name: string;
63
+ memberRoleIndices: number[];
64
+ }>;
65
+ token?: {
66
+ name?: string;
67
+ symbol?: string;
68
+ };
68
69
  roleAssignments: {
69
70
  quickJoinRoles: number[];
70
71
  tokenMemberRoles: number[];
@@ -90,7 +91,7 @@ export interface OrgDeployConfig {
90
91
  };
91
92
  /**
92
93
  * Optional org-wide TaskManager ROLE_PERM grants applied at deploy time
93
- * (OrgDeployer.TaskManagerPermConfig — roleIndices resolve to hat IDs,
94
+ * (OrgDeployer.TaskManagerPermConfig — roleIndices resolve to subject IDs,
94
95
  * masks are TaskPerm bitmasks: 1=create 2=claim 4=review 8=assign …).
95
96
  */
96
97
  taskManagerPerms?: {
@@ -103,13 +104,15 @@ export declare function indicesToBitmap(indices: number[]): ethers.BigNumber;
103
104
  /**
104
105
  * Reject role indices that do not name a role in the config.
105
106
  *
106
- * RoleResolver reverts UnregisteredRole(roleIdx) when a bitmap bit has no registered hat
107
+ * The deployer rejects any bitmap bit without a registered role
107
108
  * (audit M-09) — previously hat 0 was silently stored as an "authorized" hat and the deploy
108
109
  * succeeded with a broken allowlist. Either way the config is wrong, and we know roles.length
109
110
  * here, so name the offending field instead of surfacing a bare index from gas estimation
110
111
  * (older deployers) or an aborted deploy (upgraded ones).
111
112
  */
112
113
  export declare function assertRoleIndices(field: string, indices: number[] | undefined, roleCount: number): void;
114
+ /** Reject legacy access configs and validate the authority's genesis invariants before I/O. */
115
+ export declare function validateOrgDeployConfig(config: OrgDeployConfig): void;
113
116
  /**
114
117
  * Derive the deterministic orgId + normalized name the deployer uses:
115
118
  * orgId = keccak256(orgName.toLowerCase().replace(/\s+/g, '-')).
@@ -189,7 +192,7 @@ export declare const DEPLOY_FULL_ORG_GAS_LIMIT = 15000000;
189
192
  /**
190
193
  * Pure assembly of the DeploymentParams struct — port of the param build in
191
194
  * `pop org deploy` (src/commands/org/deploy.ts:257-403), byte-for-byte the
192
- * same encoding. Validates taskManagerPerms lengths and every roleAssignments
195
+ * native authority encoding. Validates taskManagerPerms lengths and every roleAssignments
193
196
  * index exactly like the CLI (both gate the broadcast).
194
197
  *
195
198
  * ABI field order (verified against src/abi/OrgDeployerNew.json +
@@ -197,10 +200,12 @@ export declare const DEPLOY_FULL_ORG_GAS_LIMIT = 15000000;
197
200
  * orgId, orgName, metadataHash, registryAddr, deployerAddress,
198
201
  * deployerUsername, regDeadline, regNonce, regSignature, autoUpgrade,
199
202
  * hybridThresholdPct, ddThresholdPct, hybridClasses, ddInitialTargets,
200
- * roles, roleAssignments, metadataAdminRoleIndex, passkeyEnabled,
203
+ * roles, groups, roleAssignments, metadataAdminRoleIndex, passkeyEnabled,
201
204
  * educationHubConfig, bootstrap, paymasterConfig, taskManagerPerms
202
205
  */
203
206
  export declare function buildDeploymentParams(config: OrgDeployConfig, resolved: DeploymentResolvedInputs): unknown[];
207
+ /** Fail before publishing or signing when serving infrastructure still uses the retired deployer. */
208
+ export declare function requireAuthorityDeployerVersion(version: string): void;
204
209
  export interface DeployFullOrgArgs {
205
210
  /** OrgDeployer proxy (orgDeployerProxy from infrastructure). */
206
211
  orgDeployerAddress: string;
package/dist/tx/org.js CHANGED
@@ -15,10 +15,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.DEPLOY_FULL_ORG_GAS_LIMIT = exports.REGISTER_ACCOUNT_TYPES = void 0;
16
16
  exports.indicesToBitmap = indicesToBitmap;
17
17
  exports.assertRoleIndices = assertRoleIndices;
18
+ exports.validateOrgDeployConfig = validateOrgDeployConfig;
18
19
  exports.deriveOrgId = deriveOrgId;
19
20
  exports.registerAccountDomain = registerAccountDomain;
20
21
  exports.signRegisterAccount = signRegisterAccount;
21
22
  exports.buildDeploymentParams = buildDeploymentParams;
23
+ exports.requireAuthorityDeployerVersion = requireAuthorityDeployerVersion;
22
24
  exports.buildDeployFullOrg = buildDeployFullOrg;
23
25
  exports.deployFullOrgIntent = deployFullOrgIntent;
24
26
  exports.buildUpdateOrgMetaAsAdmin = buildUpdateOrgMetaAsAdmin;
@@ -64,7 +66,7 @@ function indicesToBitmap(indices) {
64
66
  /**
65
67
  * Reject role indices that do not name a role in the config.
66
68
  *
67
- * RoleResolver reverts UnregisteredRole(roleIdx) when a bitmap bit has no registered hat
69
+ * The deployer rejects any bitmap bit without a registered role
68
70
  * (audit M-09) — previously hat 0 was silently stored as an "authorized" hat and the deploy
69
71
  * succeeded with a broken allowlist. Either way the config is wrong, and we know roles.length
70
72
  * here, so name the offending field instead of surfacing a bare index from gas estimation
@@ -78,6 +80,152 @@ function assertRoleIndices(field, indices, roleCount) {
78
80
  }
79
81
  }
80
82
  }
83
+ /** Reject legacy access configs and validate the authority's genesis invariants before I/O. */
84
+ function validateOrgDeployConfig(config) {
85
+ const invalid = (message) => { throw new errors_1.CliError('Config invalid: ' + message, exit_codes_1.EXIT.USAGE); };
86
+ const uint = (value, label, max = 4294967295) => {
87
+ if (typeof value !== 'number' || !Number.isInteger(value) || value < 0 || value > max)
88
+ invalid(label + ' must be an integer between 0 and ' + max);
89
+ };
90
+ if (!config?.orgName?.trim())
91
+ invalid('orgName is required');
92
+ if (!Array.isArray(config.roles) || config.roles.length < 1 || config.roles.length > 16)
93
+ invalid('roles must contain 1–16 authority roles');
94
+ if (!config.hybridVoting || !Array.isArray(config.hybridVoting.classes) || !config.hybridVoting.classes.length)
95
+ invalid('hybridVoting.classes is required');
96
+ if (config.hybridVoting.classes.length > 8)
97
+ invalid('hybridVoting.classes supports at most 8 classes');
98
+ uint(config.hybridVoting.thresholdPct, 'hybridVoting.thresholdPct', 100);
99
+ uint(config.directDemocracy?.thresholdPct ?? 51, 'directDemocracy.thresholdPct', 100);
100
+ if (config.hybridVoting.thresholdPct === 0 || config.directDemocracy?.thresholdPct === 0)
101
+ invalid('voting thresholds must be between 1 and 100');
102
+ uint(config.hybridVoting.quorum ?? 0, 'hybridVoting.quorum');
103
+ uint(config.directDemocracy?.quorum ?? 0, 'directDemocracy.quorum');
104
+ let slices = 0;
105
+ for (const c of config.hybridVoting.classes) {
106
+ if ('hatIds' in c)
107
+ invalid('hybridVoting.classes.hatIds is unsupported; use authority subjectIds');
108
+ if (c.strategy !== 'DIRECT' && c.strategy !== 'ERC20_BAL')
109
+ invalid('unknown voting class strategy');
110
+ uint(c.slicePct, 'hybridVoting.classes.slicePct', 100);
111
+ if (c.slicePct === 0)
112
+ invalid('hybridVoting.classes.slicePct must be positive');
113
+ slices += c.slicePct;
114
+ if (c.asset && !ethers_1.ethers.utils.isAddress(c.asset))
115
+ invalid('invalid voting asset address');
116
+ if (c.subjectIds && (!Array.isArray(c.subjectIds) || c.subjectIds.some(id => typeof id !== 'string' || !/^[0-9]+$/.test(id))))
117
+ invalid('subjectIds must be decimal strings');
118
+ if (c.subjectIds?.some(id => ethers_1.ethers.BigNumber.from(id).gt(ethers_1.ethers.constants.MaxUint256)))
119
+ invalid('subjectIds must fit uint256');
120
+ if (c.quadratic !== undefined && typeof c.quadratic !== 'boolean')
121
+ invalid('hybridVoting.classes.quadratic must be a boolean');
122
+ try {
123
+ const minimum = ethers_1.ethers.utils.parseUnits(c.minBalance ?? '0', 18);
124
+ if (minimum.lt(0) || minimum.gt(ethers_1.ethers.constants.MaxUint256))
125
+ invalid('hybridVoting.classes.minBalance must fit uint256');
126
+ }
127
+ catch {
128
+ invalid('hybridVoting.classes.minBalance must be a non-negative decimal amount with at most 18 decimals');
129
+ }
130
+ }
131
+ if (slices !== 100)
132
+ invalid('hybridVoting class slices must sum to 100');
133
+ for (const [i, role] of config.roles.entries()) {
134
+ if (!role.name?.trim())
135
+ invalid('roles[' + i + '].name is required');
136
+ if ('hatConfig' in role || 'defaults' in role || 'hierarchy' in role || (role.vouching && 'combineWithHierarchy' in role.vouching))
137
+ invalid('legacy Hats role configuration is unsupported; use open, maxMembers and authority vouching');
138
+ if (typeof role.open !== 'boolean')
139
+ invalid('roles[' + i + '].open must explicitly be true or false');
140
+ if (typeof role.canVote !== 'boolean')
141
+ invalid('roles[' + i + '].canVote must be a boolean');
142
+ uint(role.maxMembers ?? 0, 'roles[' + i + '].maxMembers');
143
+ if (role.metadataCID !== undefined && !ethers_1.ethers.utils.isHexString(role.metadataCID, 32))
144
+ invalid('roles[' + i + '].metadataCID must be a bytes32 digest');
145
+ if (role.vouching?.enabled !== undefined && typeof role.vouching.enabled !== 'boolean')
146
+ invalid('roles[' + i + '].vouching.enabled must be a boolean');
147
+ if (role.distribution?.mintToDeployer !== undefined && typeof role.distribution.mintToDeployer !== 'boolean')
148
+ invalid('roles[' + i + '].distribution.mintToDeployer must be a boolean');
149
+ // Disabled vouch rows are still ABI encoded, so validate every supplied value.
150
+ if (role.vouching?.quorum !== undefined)
151
+ uint(role.vouching.quorum, 'roles[' + i + '].vouching.quorum');
152
+ if (role.vouching?.voucherRoleIndex !== undefined)
153
+ uint(role.vouching.voucherRoleIndex, 'roles[' + i + '].vouching.voucherRoleIndex');
154
+ if (role.vouching?.enabled) {
155
+ uint(role.vouching.quorum, 'roles[' + i + '].vouching.quorum');
156
+ if (!role.vouching.quorum)
157
+ invalid('enabled vouching requires a positive quorum');
158
+ assertRoleIndices('roles[' + i + '].vouching.voucherRoleIndex', [role.vouching.voucherRoleIndex], config.roles.length);
159
+ }
160
+ const wearers = role.distribution?.additionalWearers ?? [];
161
+ if (!Array.isArray(wearers) || wearers.some(w => !ethers_1.ethers.utils.isAddress(w) || w === ethers_1.ethers.constants.AddressZero))
162
+ invalid('invalid additional wearer address');
163
+ if (new Set(wearers.map(w => w.toLowerCase())).size !== wearers.length)
164
+ invalid('duplicate additional wearer');
165
+ const seeded = wearers.length + ((role.distribution?.mintToDeployer ?? true) ? 1 : 0);
166
+ if (role.maxMembers && seeded > role.maxMembers)
167
+ invalid('roles[' + i + '].maxMembers is below the genesis membership count');
168
+ }
169
+ if (!config.roleAssignments || typeof config.roleAssignments !== 'object')
170
+ invalid('roleAssignments is required');
171
+ const assignmentFields = ['quickJoinRoles', 'tokenMemberRoles', 'tokenApproverRoles', 'taskCreatorRoles', 'educationCreatorRoles', 'educationMemberRoles', 'hybridProposalCreatorRoles', 'ddVotingRoles', 'ddCreatorRoles'];
172
+ for (const field of Object.keys(config.roleAssignments))
173
+ if (!assignmentFields.includes(field))
174
+ invalid('unknown roleAssignments.' + field);
175
+ for (const field of assignmentFields) {
176
+ const indices = config.roleAssignments[field] ?? [];
177
+ if (!Array.isArray(indices))
178
+ invalid('roleAssignments.' + field + ' must be an array');
179
+ assertRoleIndices('roleAssignments.' + field, indices, config.roles.length);
180
+ }
181
+ for (const i of config.roleAssignments.quickJoinRoles ?? [])
182
+ if (!config.roles[i].open)
183
+ invalid('QuickJoin role ' + i + ' must be open');
184
+ if (config.groups && (!Array.isArray(config.groups) || config.groups.length > 8))
185
+ invalid('groups must contain at most 8 groups');
186
+ for (const [i, group] of (config.groups ?? []).entries()) {
187
+ if (!group.name?.trim() || !Array.isArray(group.memberRoleIndices) || group.memberRoleIndices.length < 1 || group.memberRoleIndices.length > 16)
188
+ invalid('groups[' + i + '] requires a name and 1–16 memberRoleIndices');
189
+ assertRoleIndices('groups[' + i + '].memberRoleIndices', group.memberRoleIndices, config.roles.length);
190
+ if (new Set(group.memberRoleIndices).size !== group.memberRoleIndices.length)
191
+ invalid('duplicate group member role');
192
+ }
193
+ if (config.metadataAdminRoleIndex !== undefined)
194
+ assertRoleIndices('metadataAdminRoleIndex', [config.metadataAdminRoleIndex], config.roles.length);
195
+ if (config.paymaster) {
196
+ const pm = config.paymaster;
197
+ assertRoleIndices('paymaster.operatorRoleIndex', [pm.operatorRoleIndex], config.roles.length);
198
+ uint(pm.defaultBudgetEpochLen, 'paymaster.defaultBudgetEpochLen');
199
+ for (const [field, decimals, bits] of [['maxFeePerGas', 9, 256], ['maxPriorityFeePerGas', 9, 256], ['defaultBudgetCapPerEpoch', 18, 128], ['funding', 18, 256]]) {
200
+ const raw = pm[field];
201
+ if (field === 'funding' && raw === undefined)
202
+ continue;
203
+ try {
204
+ const value = ethers_1.ethers.utils.parseUnits(raw, decimals);
205
+ if (value.lt(0) || value.gte(ethers_1.ethers.BigNumber.from(2).pow(bits)))
206
+ invalid('paymaster.' + field + ' is outside uint' + bits);
207
+ }
208
+ catch {
209
+ invalid('paymaster.' + field + ' must be a non-negative decimal amount that fits uint' + bits);
210
+ }
211
+ }
212
+ }
213
+ if (config.autoUpgrade !== undefined && typeof config.autoUpgrade !== 'boolean')
214
+ invalid('autoUpgrade must be a boolean');
215
+ if (config.educationHub?.enabled !== undefined && typeof config.educationHub.enabled !== 'boolean')
216
+ invalid('educationHub.enabled must be a boolean');
217
+ for (const field of ['name', 'symbol'])
218
+ if (config.token?.[field] !== undefined && typeof config.token[field] !== 'string')
219
+ invalid('token.' + field + ' must be a string');
220
+ const tm = config.taskManagerPerms;
221
+ if (tm) {
222
+ if (!Array.isArray(tm.roleIndices) || !Array.isArray(tm.masks) || tm.roleIndices.length !== tm.masks.length)
223
+ invalid('taskManagerPerms.roleIndices and masks must be arrays of equal length');
224
+ assertRoleIndices('taskManagerPerms.roleIndices', tm.roleIndices, config.roles.length);
225
+ for (const mask of tm.masks)
226
+ uint(mask, 'taskManagerPerms.masks', 255);
227
+ }
228
+ }
81
229
  /**
82
230
  * Derive the deterministic orgId + normalized name the deployer uses:
83
231
  * orgId = keccak256(orgName.toLowerCase().replace(/\s+/g, '-')).
@@ -129,7 +277,7 @@ exports.DEPLOY_FULL_ORG_GAS_LIMIT = 15000000;
129
277
  /**
130
278
  * Pure assembly of the DeploymentParams struct — port of the param build in
131
279
  * `pop org deploy` (src/commands/org/deploy.ts:257-403), byte-for-byte the
132
- * same encoding. Validates taskManagerPerms lengths and every roleAssignments
280
+ * native authority encoding. Validates taskManagerPerms lengths and every roleAssignments
133
281
  * index exactly like the CLI (both gate the broadcast).
134
282
  *
135
283
  * ABI field order (verified against src/abi/OrgDeployerNew.json +
@@ -137,51 +285,26 @@ exports.DEPLOY_FULL_ORG_GAS_LIMIT = 15000000;
137
285
  * orgId, orgName, metadataHash, registryAddr, deployerAddress,
138
286
  * deployerUsername, regDeadline, regNonce, regSignature, autoUpgrade,
139
287
  * hybridThresholdPct, ddThresholdPct, hybridClasses, ddInitialTargets,
140
- * roles, roleAssignments, metadataAdminRoleIndex, passkeyEnabled,
288
+ * roles, groups, roleAssignments, metadataAdminRoleIndex, passkeyEnabled,
141
289
  * educationHubConfig, bootstrap, paymasterConfig, taskManagerPerms
142
290
  */
143
291
  function buildDeploymentParams(config, resolved) {
144
- // Build hybrid voting classes
145
- // ABI: strategy(uint8), slicePct(uint8), quadratic(bool), minBalance(uint256), asset(address), hatIds(uint256[])
146
- const hybridClasses = config.hybridVoting.classes.map((c) => [
147
- c.strategy === 'DIRECT' ? 0 : 1, // uint8
148
- c.slicePct, // uint8 (0-100)
149
- c.quadratic || false, // bool
150
- c.minBalance ? ethers_1.ethers.utils.parseUnits(c.minBalance, 18) : 0, // uint256
151
- c.asset || ethers_1.ethers.constants.AddressZero, // address
152
- c.hatIds || [], // uint256[]
292
+ validateOrgDeployConfig(config);
293
+ const hybridClasses = config.hybridVoting.classes.map(c => [
294
+ c.strategy === 'DIRECT' ? 0 : 1,
295
+ c.slicePct,
296
+ c.quadratic ?? false,
297
+ c.minBalance ? ethers_1.ethers.utils.parseUnits(c.minBalance, 18) : 0,
298
+ c.asset || ethers_1.ethers.constants.AddressZero,
299
+ c.subjectIds || [], // ABI retains the hatIds label; values are authority subjects.
153
300
  ]);
154
- // Build role configs
155
- // ABI: name(string), image(string), metadataCID(bytes32), canVote(bool),
156
- // vouching(tuple), defaults(tuple), hierarchy(tuple), distribution(tuple), hatConfig(tuple)
157
- const MAX_UINT32 = 4294967295; // 2^32 - 1
158
- const roles = config.roles.map((r) => [
159
- r.name, // string
160
- r.image || '', // string
161
- ethers_1.ethers.constants.HashZero, // bytes32 metadataCID
162
- r.canVote, // bool
163
- [
164
- r.vouching?.enabled || false,
165
- r.vouching?.quorum || 0,
166
- r.vouching?.voucherRoleIndex ?? ethers_1.ethers.constants.MaxUint256,
167
- r.vouching?.combineWithHierarchy || false,
168
- ],
169
- [
170
- r.defaults?.eligible ?? true,
171
- r.defaults?.standing ?? true,
172
- ],
173
- [
174
- r.hierarchy?.adminRoleIndex ?? ethers_1.ethers.constants.MaxUint256,
175
- ],
176
- [
177
- r.distribution?.mintToDeployer ?? true,
178
- r.distribution?.additionalWearers || [],
179
- ],
180
- [
181
- r.hatConfig?.maxSupply ?? MAX_UINT32, // uint32, NOT uint256
182
- r.hatConfig?.mutableHat ?? true,
183
- ],
301
+ const roles = config.roles.map(r => [
302
+ r.name, r.image || '', r.metadataCID || ethers_1.ethers.constants.HashZero,
303
+ r.canVote, r.open, r.maxMembers ?? 0,
304
+ [r.vouching?.enabled ?? false, r.vouching?.quorum ?? 0, r.vouching?.voucherRoleIndex ?? 0],
305
+ [r.distribution?.mintToDeployer ?? true, r.distribution?.additionalWearers || []],
184
306
  ]);
307
+ const groups = (config.groups || []).map(g => [g.name, g.memberRoleIndices]);
185
308
  // Build role assignment bitmaps
186
309
  const ra = config.roleAssignments;
187
310
  // Every index must name a real role BEFORE anything is broadcast — a stale index is
@@ -197,15 +320,15 @@ function buildDeploymentParams(config, resolved) {
197
320
  assertRoleIndices('roleAssignments.ddVotingRoles', ra.ddVotingRoles, roleCount);
198
321
  assertRoleIndices('roleAssignments.ddCreatorRoles', ra.ddCreatorRoles, roleCount);
199
322
  const roleAssignments = [
200
- indicesToBitmap(ra.quickJoinRoles),
201
- indicesToBitmap(ra.tokenMemberRoles),
202
- indicesToBitmap(ra.tokenApproverRoles),
203
- indicesToBitmap(ra.taskCreatorRoles),
323
+ indicesToBitmap(ra.quickJoinRoles || []),
324
+ indicesToBitmap(ra.tokenMemberRoles || []),
325
+ indicesToBitmap(ra.tokenApproverRoles || []),
326
+ indicesToBitmap(ra.taskCreatorRoles || []),
204
327
  indicesToBitmap(ra.educationCreatorRoles || []),
205
328
  indicesToBitmap(ra.educationMemberRoles || []),
206
- indicesToBitmap(ra.hybridProposalCreatorRoles),
207
- indicesToBitmap(ra.ddVotingRoles),
208
- indicesToBitmap(ra.ddCreatorRoles),
329
+ indicesToBitmap(ra.hybridProposalCreatorRoles || []),
330
+ indicesToBitmap(ra.ddVotingRoles || []),
331
+ indicesToBitmap(ra.ddCreatorRoles || []),
209
332
  ];
210
333
  // Build paymaster config
211
334
  // ABI: operatorRoleIndex(uint256), autoWhitelistContracts(bool),
@@ -234,18 +357,15 @@ function buildDeploymentParams(config, resolved) {
234
357
  0, // defaultBudgetCapPerEpoch
235
358
  0, // defaultBudgetEpochLen
236
359
  ];
237
- // Optional org-wide TaskManager ROLE_PERM grants
238
- // (OrgDeployer.TaskManagerPermConfig: roleIndices[] + masks[] — empty
239
- // arrays skip the bootstrapGlobalPerms step entirely; verified against
240
- // contracts origin/main src/OrgDeployer.sol).
241
- const tmPerms = config.taskManagerPerms;
242
- if (tmPerms && (tmPerms.roleIndices?.length || 0) !== (tmPerms.masks?.length || 0)) {
243
- throw new errors_1.CliError('Config invalid: taskManagerPerms.roleIndices and taskManagerPerms.masks must be the same length.', exit_codes_1.EXIT.USAGE);
360
+ // Config task creators can create both projects and tasks. The project creator
361
+ // array alone does not grant native TM_PERMS.CREATE, so seed that bit explicitly.
362
+ const taskMasks = new Map();
363
+ for (const [i, role] of (config.taskManagerPerms?.roleIndices ?? []).entries()) {
364
+ taskMasks.set(role, config.taskManagerPerms.masks[i]);
244
365
  }
245
- const taskManagerPerms = [
246
- tmPerms?.roleIndices || [], // uint256[] roleIndices
247
- tmPerms?.masks || [], // uint8[] masks
248
- ];
366
+ for (const role of config.roleAssignments.taskCreatorRoles ?? [])
367
+ taskMasks.set(role, (taskMasks.get(role) ?? 0) | 1);
368
+ const taskManagerPerms = [[...taskMasks.keys()], [...taskMasks.values()]];
249
369
  return [
250
370
  resolved.orgId, // bytes32
251
371
  config.orgName, // string
@@ -258,10 +378,11 @@ function buildDeploymentParams(config, resolved) {
258
378
  resolved.regSignature, // bytes
259
379
  config.autoUpgrade ?? true, // bool
260
380
  config.hybridVoting.thresholdPct, // uint8
261
- config.directDemocracy?.thresholdPct || 51, // uint8
381
+ config.directDemocracy?.thresholdPct ?? 51, // uint8
262
382
  hybridClasses, // ClassConfig[]
263
383
  [], // address[] ddInitialTargets
264
384
  roles, // RoleConfig[]
385
+ groups, // GroupConfig[]
265
386
  roleAssignments, // RoleAssignments struct
266
387
  config.metadataAdminRoleIndex ?? ethers_1.ethers.constants.MaxUint256, // uint256
267
388
  true, // bool passkeyEnabled
@@ -269,8 +390,17 @@ function buildDeploymentParams(config, resolved) {
269
390
  [[], []], // BootstrapConfig struct (projects, tasks)
270
391
  paymasterConfig, // PaymasterConfig struct
271
392
  taskManagerPerms, // TaskManagerPermConfig struct (roleIndices, masks)
393
+ config.hybridVoting.quorum ?? 0,
394
+ config.directDemocracy?.quorum ?? 0,
395
+ config.token?.name ?? "",
396
+ config.token?.symbol ?? "",
272
397
  ];
273
398
  }
399
+ /** Fail before publishing or signing when serving infrastructure still uses the retired deployer. */
400
+ function requireAuthorityDeployerVersion(version) {
401
+ if (!/^2\./.test(version))
402
+ throw new errors_1.CliError(`OrgDeployer ${version || 'unknown'} is unsupported; deployer version 2 is required for native MembershipAuthority organizations.`, exit_codes_1.EXIT.PRECONDITION);
403
+ }
274
404
  /**
275
405
  * Port of `pop org deploy` — src/commands/org/deploy.ts.
276
406
  * OrgDeployer.deployFullOrg([DeploymentParams]) — the single on-chain write,
@@ -312,12 +442,17 @@ function buildDeployFullOrg(a) {
312
442
  async function deployFullOrgIntent(ctx, params) {
313
443
  const config = params.config;
314
444
  // ── Fail fast on config problems before any network work ───────────
315
- if (!config.orgName)
316
- throw new errors_1.CliError('Config missing: orgName', exit_codes_1.EXIT.USAGE);
317
- if (!config.roles?.length)
318
- throw new errors_1.CliError('Config missing: roles', exit_codes_1.EXIT.USAGE);
319
- if (!config.hybridVoting)
320
- throw new errors_1.CliError('Config missing: hybridVoting', exit_codes_1.EXIT.USAGE);
445
+ validateOrgDeployConfig(config);
446
+ // Validate full ABI encoding before infrastructure reads, IPFS publication or signing.
447
+ const preview = buildDeploymentParams(config, {
448
+ orgId: ethers_1.ethers.constants.HashZero, metadataHash: ethers_1.ethers.constants.HashZero,
449
+ registryAddr: ethers_1.ethers.constants.AddressZero, deployerAddress: params.deployerAddress,
450
+ deployerUsername: '', regDeadline: 0, regNonce: 0, regSignature: '0x',
451
+ });
452
+ new ethers_1.ethers.utils.Interface((0, contracts_1.getAbi)('OrgDeployerNew')).encodeFunctionData('deployFullOrg', [preview]);
453
+ if (config.paymaster?.funding && ethers_1.ethers.utils.parseEther(config.paymaster.funding).lt(0)) {
454
+ throw new errors_1.CliError('Paymaster funding must not be negative', exit_codes_1.EXIT.USAGE);
455
+ }
321
456
  if (!ctx.provider) {
322
457
  throw new errors_1.CliError('deployFullOrgIntent requires ctx.provider (UniversalAccountRegistry.nonces read)', exit_codes_1.EXIT.USAGE);
323
458
  }
@@ -332,6 +467,7 @@ async function deployFullOrgIntent(ctx, params) {
332
467
  throw new errors_1.CliError('Could not resolve OrgDeployer address', exit_codes_1.EXIT.INFRA);
333
468
  if (!registryAddr)
334
469
  throw new errors_1.CliError('Could not resolve UniversalAccountRegistry address', exit_codes_1.EXIT.INFRA);
470
+ requireAuthorityDeployerVersion(await (0, contracts_1.createReadContract)(orgDeployerAddr, 'OrgDeployerNew', ctx.provider).VERSION());
335
471
  // Generate orgId: keccak256(orgName.toLowerCase().replace(/\s+/g, '-'))
336
472
  const { orgId, normalizedName } = deriveOrgId(config.orgName);
337
473
  const deployerUsername = config.deployerUsername || normalizedName;
@@ -75,6 +75,7 @@ export interface ProposeProjectArgs {
75
75
  metadataHash?: string;
76
76
  /** Human PT cap (0/omitted = unlimited). */
77
77
  cap?: number | string;
78
+ managers?: string[];
78
79
  createHats?: ethers.BigNumberish[];
79
80
  claimHats?: ethers.BigNumberish[];
80
81
  reviewHats?: ethers.BigNumberish[];
@@ -88,8 +89,7 @@ export interface ProposeProjectArgs {
88
89
  /**
89
90
  * Port of `pop project propose` — src/commands/project/propose.ts.
90
91
  * HybridVoting.createProposal(`Create project: <name>`, descriptionHash,
91
- * duration, 2, [[createProject call], []], []). Managers, bountyTokens and
92
- * bountyCaps are forced empty (hat-based), matching the CLI struct.
92
+ * duration, 2, [[createProject call], []], []). Explicit address managers remain supported; authority TM_PERMS replaces the retired role-mask arrays.
93
93
  */
94
94
  export declare function buildProposeProject(a: ProposeProjectArgs): TxIntent;
95
95
  export interface CreateProjectParams {
@@ -130,6 +130,7 @@ export interface ProposeProjectParams {
130
130
  name: string;
131
131
  description?: string;
132
132
  cap?: number;
133
+ managers?: string[];
133
134
  /** Vote duration in minutes (CLI default 1440 = 24h). */
134
135
  duration?: number;
135
136
  createHats?: Array<string | number>;
@@ -33,18 +33,34 @@ const proposal_1 = require("../metadata/proposal");
33
33
  * governance-wrapped paths so the two cannot drift.
34
34
  */
35
35
  function encodeProjectStruct(a) {
36
- const cap = a.cap ? ethers_1.ethers.utils.parseUnits(a.cap.toString(), 18) : 0;
36
+ if ([a.createHats, a.claimHats, a.reviewHats, a.assignHats].some(rows => rows?.length))
37
+ throw new Error('Project permission arrays were removed. Configure MembershipAuthority TM_PERMS using projectId + 1 after creating the project.');
38
+ const title = (0, encoding_1.stringToBytes)(a.name);
39
+ if (title.length === 0 || title.length > 256)
40
+ throw new Error('Project title must contain 1–256 UTF-8 bytes.');
41
+ const cap = ethers_1.ethers.utils.parseUnits(String(a.cap ?? 0), 18);
42
+ const caps = (a.bountyCaps ?? []).map(c => ethers_1.ethers.BigNumber.from(c));
43
+ const maxPayout = ethers_1.ethers.BigNumber.from(10).pow(24);
44
+ const unlimited = ethers_1.ethers.BigNumber.from(2).pow(128).sub(1);
45
+ if ([cap, ...caps].some(value => value.lt(0) || (value.gt(maxPayout) && !value.eq(unlimited))))
46
+ throw new Error('Project and bounty caps must be at most 1e24 base units or the uint128 unlimited sentinel.');
47
+ if ((a.bountyTokens ?? []).length !== caps.length)
48
+ throw new Error('Bounty tokens and caps must have equal lengths.');
49
+ for (const address of [...(a.managers ?? []), ...(a.bountyTokens ?? [])]) {
50
+ if (!ethers_1.ethers.utils.isAddress(address) || address.toLowerCase() === ethers_1.ethers.constants.AddressZero)
51
+ throw new Error('Project managers and bounty tokens must be non-zero addresses.');
52
+ }
37
53
  return [
38
- (0, encoding_1.stringToBytes)(a.name),
54
+ title,
39
55
  a.metadataHash ? (0, encoding_1.ipfsCidToBytes32)(a.metadataHash) : ethers_1.ethers.constants.HashZero,
40
56
  cap,
41
57
  a.managers ?? [],
42
- (a.createHats ?? []).map((h) => ethers_1.ethers.BigNumber.from(h)),
43
- (a.claimHats ?? []).map((h) => ethers_1.ethers.BigNumber.from(h)),
44
- (a.reviewHats ?? []).map((h) => ethers_1.ethers.BigNumber.from(h)),
45
- (a.assignHats ?? []).map((h) => ethers_1.ethers.BigNumber.from(h)),
58
+ [],
59
+ [],
60
+ [],
61
+ [],
46
62
  a.bountyTokens ?? [],
47
- (a.bountyCaps ?? []).map((c) => ethers_1.ethers.BigNumber.from(c)),
63
+ caps,
48
64
  ];
49
65
  }
50
66
  /**
@@ -90,8 +106,7 @@ function buildCreateProjectCall(a) {
90
106
  /**
91
107
  * Port of `pop project propose` — src/commands/project/propose.ts.
92
108
  * HybridVoting.createProposal(`Create project: <name>`, descriptionHash,
93
- * duration, 2, [[createProject call], []], []). Managers, bountyTokens and
94
- * bountyCaps are forced empty (hat-based), matching the CLI struct.
109
+ * duration, 2, [[createProject call], []], []). Explicit address managers remain supported; authority TM_PERMS replaces the retired role-mask arrays.
95
110
  */
96
111
  function buildProposeProject(a) {
97
112
  const call = buildCreateProjectCall({
@@ -99,7 +114,7 @@ function buildProposeProject(a) {
99
114
  name: a.name,
100
115
  metadataHash: a.metadataHash,
101
116
  cap: a.cap,
102
- managers: [], // hat-based instead
117
+ managers: a.managers ?? [],
103
118
  createHats: a.createHats,
104
119
  claimHats: a.claimHats,
105
120
  reviewHats: a.reviewHats,
@@ -131,6 +146,7 @@ function buildProposeProject(a) {
131
146
  * delegates to buildCreateProject.
132
147
  */
133
148
  async function createProjectIntent(ctx, p) {
149
+ encodeProjectStruct({ ...p, taskManagerAddress: ethers_1.ethers.constants.AddressZero });
134
150
  const modules = await (0, resolve_1.resolveOrgModules)(ctx.client, p.org, ctx.chainId);
135
151
  const taskManagerAddress = (0, resolve_1.requireModule)(modules, 'taskManagerAddress');
136
152
  let metaCid;
@@ -179,6 +195,7 @@ async function deleteProjectIntent(ctx, p) {
179
195
  * wraps the createProject calldata in a HybridVoting proposal.
180
196
  */
181
197
  async function proposeProjectIntent(ctx, p) {
198
+ encodeProjectStruct({ ...p, taskManagerAddress: ethers_1.ethers.constants.AddressZero });
182
199
  const modules = await (0, resolve_1.resolveOrgModules)(ctx.client, p.org, ctx.chainId);
183
200
  const taskManagerAddress = (0, resolve_1.requireModule)(modules, 'taskManagerAddress');
184
201
  const hybridVotingAddress = modules.hybridVotingAddress;
@@ -204,6 +221,7 @@ async function proposeProjectIntent(ctx, p) {
204
221
  name: p.name,
205
222
  metadataHash: metaCid,
206
223
  cap: p.cap,
224
+ managers: p.managers,
207
225
  createHats: p.createHats,
208
226
  claimHats: p.claimHats,
209
227
  reviewHats: p.reviewHats,