@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.
Files changed (220) hide show
  1. package/README.md +201 -0
  2. package/dist/abis/DirectDemocracyVotingNew.d.ts +749 -0
  3. package/dist/abis/DirectDemocracyVotingNew.js +961 -0
  4. package/dist/abis/ERC20.d.ts +90 -0
  5. package/dist/abis/ERC20.js +125 -0
  6. package/dist/abis/EducationHubNew.d.ts +590 -0
  7. package/dist/abis/EducationHubNew.js +762 -0
  8. package/dist/abis/EligibilityModuleNew.d.ts +1615 -0
  9. package/dist/abis/EligibilityModuleNew.js +2082 -0
  10. package/dist/abis/Executor.d.ts +619 -0
  11. package/dist/abis/Executor.js +796 -0
  12. package/dist/abis/HybridVotingNew.d.ts +898 -0
  13. package/dist/abis/HybridVotingNew.js +1149 -0
  14. package/dist/abis/ImplementationRegistry.d.ts +270 -0
  15. package/dist/abis/ImplementationRegistry.js +355 -0
  16. package/dist/abis/OrgDeployerNew.d.ts +1275 -0
  17. package/dist/abis/OrgDeployerNew.js +1630 -0
  18. package/dist/abis/OrgRegistry.d.ts +670 -0
  19. package/dist/abis/OrgRegistry.js +868 -0
  20. package/dist/abis/ParticipationToken.d.ts +1086 -0
  21. package/dist/abis/ParticipationToken.js +1415 -0
  22. package/dist/abis/PasskeyAccount.d.ts +788 -0
  23. package/dist/abis/PasskeyAccount.js +1019 -0
  24. package/dist/abis/PasskeyAccountFactory.d.ts +344 -0
  25. package/dist/abis/PasskeyAccountFactory.js +449 -0
  26. package/dist/abis/PaymasterHub.d.ts +1603 -0
  27. package/dist/abis/PaymasterHub.js +2048 -0
  28. package/dist/abis/PaymentManager.d.ts +520 -0
  29. package/dist/abis/PaymentManager.js +671 -0
  30. package/dist/abis/PoaManager.d.ts +344 -0
  31. package/dist/abis/PoaManager.js +449 -0
  32. package/dist/abis/QuickJoinNew.d.ts +855 -0
  33. package/dist/abis/QuickJoinNew.js +1098 -0
  34. package/dist/abis/TaskManagerNew.d.ts +1236 -0
  35. package/dist/abis/TaskManagerNew.js +1573 -0
  36. package/dist/abis/ToggleModule.d.ts +193 -0
  37. package/dist/abis/ToggleModule.js +255 -0
  38. package/dist/abis/UniversalAccountRegistry.d.ts +577 -0
  39. package/dist/abis/UniversalAccountRegistry.js +750 -0
  40. package/dist/abis/ZkEmailInvites.d.ts +823 -0
  41. package/dist/abis/ZkEmailInvites.js +1060 -0
  42. package/dist/abis/external/AaveGovernanceV2.d.ts +128 -0
  43. package/dist/abis/external/AaveGovernanceV2.js +177 -0
  44. package/dist/abis/external/AaveGovernanceV3.d.ts +160 -0
  45. package/dist/abis/external/AaveGovernanceV3.js +220 -0
  46. package/dist/abis/external/AragonVoting.d.ts +107 -0
  47. package/dist/abis/external/AragonVoting.js +150 -0
  48. package/dist/abis/external/CurveGaugeController.d.ts +95 -0
  49. package/dist/abis/external/CurveGaugeController.js +130 -0
  50. package/dist/abis/external/CurveVotingEscrow.d.ts +86 -0
  51. package/dist/abis/external/CurveVotingEscrow.js +116 -0
  52. package/dist/abis/external/GovernorAlpha.d.ts +172 -0
  53. package/dist/abis/external/GovernorAlpha.js +228 -0
  54. package/dist/abis/external/MakerDAOChief.d.ts +92 -0
  55. package/dist/abis/external/MakerDAOChief.js +129 -0
  56. package/dist/abis/external/OZGovernor.d.ts +227 -0
  57. package/dist/abis/external/OZGovernor.js +317 -0
  58. package/dist/abis/external/SolidlyVotingEscrow.d.ts +196 -0
  59. package/dist/abis/external/SolidlyVotingEscrow.js +260 -0
  60. package/dist/abis/index.d.ts +62 -0
  61. package/dist/abis/index.js +98 -0
  62. package/dist/chains.d.ts +150 -0
  63. package/dist/chains.js +320 -0
  64. package/dist/context.d.ts +48 -0
  65. package/dist/context.js +38 -0
  66. package/dist/contracts.d.ts +24 -0
  67. package/dist/contracts.js +41 -0
  68. package/dist/encoding.d.ts +58 -0
  69. package/dist/encoding.js +243 -0
  70. package/dist/env.d.ts +10 -0
  71. package/dist/env.js +4 -0
  72. package/dist/error-catalog.d.ts +41 -0
  73. package/dist/error-catalog.js +941 -0
  74. package/dist/errors.d.ts +27 -0
  75. package/dist/errors.js +60 -0
  76. package/dist/execute/ethers.d.ts +93 -0
  77. package/dist/execute/ethers.js +323 -0
  78. package/dist/execute/index.d.ts +7 -0
  79. package/dist/execute/index.js +23 -0
  80. package/dist/execute/sponsored.d.ts +68 -0
  81. package/dist/execute/sponsored.js +277 -0
  82. package/dist/exit-codes.d.ts +18 -0
  83. package/dist/exit-codes.js +21 -0
  84. package/dist/format.d.ts +21 -0
  85. package/dist/format.js +89 -0
  86. package/dist/graph/client.d.ts +298 -0
  87. package/dist/graph/client.js +702 -0
  88. package/dist/graph/documents/activity.d.ts +9 -0
  89. package/dist/graph/documents/activity.js +164 -0
  90. package/dist/graph/documents/beacons.d.ts +172 -0
  91. package/dist/graph/documents/beacons.js +345 -0
  92. package/dist/graph/documents/index.d.ts +21 -0
  93. package/dist/graph/documents/index.js +57 -0
  94. package/dist/graph/documents/infrastructure.d.ts +30 -0
  95. package/dist/graph/documents/infrastructure.js +34 -0
  96. package/dist/graph/documents/org.d.ts +15 -0
  97. package/dist/graph/documents/org.js +249 -0
  98. package/dist/graph/documents/paymaster.d.ts +95 -0
  99. package/dist/graph/documents/paymaster.js +101 -0
  100. package/dist/graph/documents/role.d.ts +6 -0
  101. package/dist/graph/documents/role.js +41 -0
  102. package/dist/graph/documents/roles.d.ts +50 -0
  103. package/dist/graph/documents/roles.js +112 -0
  104. package/dist/graph/documents/task.d.ts +84 -0
  105. package/dist/graph/documents/task.js +236 -0
  106. package/dist/graph/documents/token.d.ts +13 -0
  107. package/dist/graph/documents/token.js +126 -0
  108. package/dist/graph/documents/treasury.d.ts +39 -0
  109. package/dist/graph/documents/treasury.js +136 -0
  110. package/dist/graph/documents/user.d.ts +120 -0
  111. package/dist/graph/documents/user.js +302 -0
  112. package/dist/graph/documents/voting-classes.d.ts +69 -0
  113. package/dist/graph/documents/voting-classes.js +149 -0
  114. package/dist/graph/documents/voting.d.ts +28 -0
  115. package/dist/graph/documents/voting.js +163 -0
  116. package/dist/graph/documents/vouch.d.ts +79 -0
  117. package/dist/graph/documents/vouch.js +171 -0
  118. package/dist/graph/documents/zkemail.d.ts +63 -0
  119. package/dist/graph/documents/zkemail.js +188 -0
  120. package/dist/graph/index.d.ts +2 -0
  121. package/dist/graph/index.js +41 -0
  122. package/dist/index.d.ts +49 -0
  123. package/dist/index.js +92 -0
  124. package/dist/ipfs.d.ts +39 -0
  125. package/dist/ipfs.js +214 -0
  126. package/dist/label-aliases.d.ts +31 -0
  127. package/dist/label-aliases.js +70 -0
  128. package/dist/metadata/education.d.ts +30 -0
  129. package/dist/metadata/education.js +31 -0
  130. package/dist/metadata/index.d.ts +12 -0
  131. package/dist/metadata/index.js +48 -0
  132. package/dist/metadata/org.d.ts +92 -0
  133. package/dist/metadata/org.js +93 -0
  134. package/dist/metadata/proposal.d.ts +44 -0
  135. package/dist/metadata/proposal.js +45 -0
  136. package/dist/metadata/role.d.ts +38 -0
  137. package/dist/metadata/role.js +35 -0
  138. package/dist/metadata/task.d.ts +108 -0
  139. package/dist/metadata/task.js +81 -0
  140. package/dist/metadata/token.d.ts +32 -0
  141. package/dist/metadata/token.js +36 -0
  142. package/dist/metadata/user.d.ts +63 -0
  143. package/dist/metadata/user.js +70 -0
  144. package/dist/multicall.d.ts +32 -0
  145. package/dist/multicall.js +96 -0
  146. package/dist/payout.d.ts +59 -0
  147. package/dist/payout.js +96 -0
  148. package/dist/perms.d.ts +47 -0
  149. package/dist/perms.js +120 -0
  150. package/dist/preflight.d.ts +72 -0
  151. package/dist/preflight.js +269 -0
  152. package/dist/reads/education.d.ts +79 -0
  153. package/dist/reads/education.js +122 -0
  154. package/dist/reads/eligibility.d.ts +377 -0
  155. package/dist/reads/eligibility.js +687 -0
  156. package/dist/reads/index.d.ts +16 -0
  157. package/dist/reads/index.js +55 -0
  158. package/dist/reads/org.d.ts +258 -0
  159. package/dist/reads/org.js +220 -0
  160. package/dist/reads/paymaster.d.ts +147 -0
  161. package/dist/reads/paymaster.js +235 -0
  162. package/dist/reads/project.d.ts +33 -0
  163. package/dist/reads/project.js +62 -0
  164. package/dist/reads/resolve.d.ts +34 -0
  165. package/dist/reads/resolve.js +64 -0
  166. package/dist/reads/task.d.ts +286 -0
  167. package/dist/reads/task.js +280 -0
  168. package/dist/reads/token.d.ts +94 -0
  169. package/dist/reads/token.js +75 -0
  170. package/dist/reads/treasury.d.ts +222 -0
  171. package/dist/reads/treasury.js +241 -0
  172. package/dist/reads/user.d.ts +229 -0
  173. package/dist/reads/user.js +155 -0
  174. package/dist/reads/vote.d.ts +296 -0
  175. package/dist/reads/vote.js +392 -0
  176. package/dist/reads/zkemail.d.ts +117 -0
  177. package/dist/reads/zkemail.js +217 -0
  178. package/dist/similarity.d.ts +36 -0
  179. package/dist/similarity.js +67 -0
  180. package/dist/sponsorship-config.d.ts +47 -0
  181. package/dist/sponsorship-config.js +41 -0
  182. package/dist/stats.d.ts +11 -0
  183. package/dist/stats.js +27 -0
  184. package/dist/task-lens.d.ts +113 -0
  185. package/dist/task-lens.js +210 -0
  186. package/dist/tx/education.d.ts +169 -0
  187. package/dist/tx/education.js +344 -0
  188. package/dist/tx/eligibility.d.ts +511 -0
  189. package/dist/tx/eligibility.js +818 -0
  190. package/dist/tx/governance.d.ts +85 -0
  191. package/dist/tx/governance.js +87 -0
  192. package/dist/tx/index.d.ts +17 -0
  193. package/dist/tx/index.js +56 -0
  194. package/dist/tx/intent.d.ts +67 -0
  195. package/dist/tx/intent.js +48 -0
  196. package/dist/tx/org.d.ts +321 -0
  197. package/dist/tx/org.js +544 -0
  198. package/dist/tx/paymaster.d.ts +140 -0
  199. package/dist/tx/paymaster.js +251 -0
  200. package/dist/tx/project.d.ts +146 -0
  201. package/dist/tx/project.js +217 -0
  202. package/dist/tx/task.d.ts +480 -0
  203. package/dist/tx/task.js +1149 -0
  204. package/dist/tx/token.d.ts +96 -0
  205. package/dist/tx/token.js +165 -0
  206. package/dist/tx/treasury.d.ts +474 -0
  207. package/dist/tx/treasury.js +911 -0
  208. package/dist/tx/user.d.ts +177 -0
  209. package/dist/tx/user.js +292 -0
  210. package/dist/tx/vote.d.ts +332 -0
  211. package/dist/tx/vote.js +762 -0
  212. package/dist/tx/zkemail.d.ts +90 -0
  213. package/dist/tx/zkemail.js +210 -0
  214. package/dist/validation.d.ts +8 -0
  215. package/dist/validation.js +39 -0
  216. package/dist/version.d.ts +142 -0
  217. package/dist/version.js +257 -0
  218. package/dist/zkemail.d.ts +161 -0
  219. package/dist/zkemail.js +299 -0
  220. package/package.json +130 -0
@@ -0,0 +1,344 @@
1
+ "use strict";
2
+ /**
3
+ * Education write builders.
4
+ *
5
+ * Ports:
6
+ * - `pop education create` — src/commands/education/create-module.ts
7
+ * - `pop education complete` — src/commands/education/complete.ts
8
+ * - `pop education update` — src/commands/education/update.ts
9
+ * - `pop education remove` — src/commands/education/remove.ts
10
+ *
11
+ * EducationHub facts (verified contracts origin/main src/EducationHub.sol):
12
+ * - createModule hashes the correct answer into the module PERMANENTLY —
13
+ * updateModule never touches answerHash, so changing quiz content cannot
14
+ * change which answer index passes (remove + recreate to change it).
15
+ * - updateModule is a FULL OVERWRITE of title + contentHash + payout.
16
+ * - removeModule is a permanent delete with no undo.
17
+ * - The chain stores only {answerHash, payout, exists}; module metadata is
18
+ * event-only, so preservation reads go through the subgraph/IPFS.
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.buildCreateModule = buildCreateModule;
22
+ exports.buildCompleteModule = buildCompleteModule;
23
+ exports.buildUpdateModule = buildUpdateModule;
24
+ exports.buildRemoveModule = buildRemoveModule;
25
+ exports.createModuleIntent = createModuleIntent;
26
+ exports.completeModuleIntent = completeModuleIntent;
27
+ exports.updateModuleIntent = updateModuleIntent;
28
+ exports.removeModuleIntent = removeModuleIntent;
29
+ const ethers_1 = require("ethers");
30
+ const contracts_1 = require("../contracts");
31
+ const encoding_1 = require("../encoding");
32
+ const ipfs_1 = require("../ipfs");
33
+ const errors_1 = require("../errors");
34
+ const exit_codes_1 = require("../exit-codes");
35
+ const resolve_1 = require("../reads/resolve");
36
+ const education_1 = require("../metadata/education");
37
+ const education_2 = require("../reads/education");
38
+ /**
39
+ * Port of `pop education create` — src/commands/education/create-module.ts.
40
+ * EducationHubNew.createModule(stringToBytes(name), contentHash,
41
+ * payoutWei(18d), correctAnswer uint8). Validates payout > 0 (the contract
42
+ * reverts InvalidPayout for 0) and correctAnswer 0-255, same messages as the
43
+ * CLI.
44
+ */
45
+ function buildCreateModule(a) {
46
+ const payoutNum = Number(a.payout);
47
+ if (!(payoutNum > 0) || !isFinite(payoutNum)) {
48
+ throw new errors_1.CliError(`--payout must be a positive number (the contract reverts InvalidPayout for 0), got: ${a.payout}`, exit_codes_1.EXIT.USAGE);
49
+ }
50
+ if (!Number.isInteger(a.correctAnswer) || a.correctAnswer < 0 || a.correctAnswer > 255) {
51
+ throw new errors_1.CliError(`--correct-answer must be an integer 0-255 (uint8), got: ${a.correctAnswer}`, exit_codes_1.EXIT.USAGE);
52
+ }
53
+ const titleBytes = (0, encoding_1.stringToBytes)(a.name);
54
+ const payoutWei = ethers_1.ethers.utils.parseUnits(a.payout.toString(), 18);
55
+ return {
56
+ to: a.educationHubAddress,
57
+ abi: (0, contracts_1.getAbi)('EducationHubNew'),
58
+ method: 'createModule',
59
+ args: [titleBytes, a.contentHash, payoutWei, a.correctAnswer],
60
+ meta: {
61
+ domain: 'education',
62
+ action: 'create-module',
63
+ orgId: a.orgId,
64
+ summary: {
65
+ name: a.name,
66
+ payout: `${a.payout} PT`,
67
+ correctAnswer: `index ${a.correctAnswer} (permanent)`,
68
+ },
69
+ },
70
+ };
71
+ }
72
+ /**
73
+ * Port of `pop education complete` — src/commands/education/complete.ts.
74
+ * EducationHubNew.completeModule(moduleId, answer uint8) — pays PT once per
75
+ * account (reverts AlreadyCompleted on a second attempt, ModuleUnknown for
76
+ * missing modules). Validates answer 0-255, same message as the CLI.
77
+ */
78
+ function buildCompleteModule(a) {
79
+ if (!Number.isInteger(a.answer) || a.answer < 0 || a.answer > 255) {
80
+ throw new errors_1.CliError(`--answer must be an integer 0-255 (uint8), got: ${a.answer}`, exit_codes_1.EXIT.USAGE);
81
+ }
82
+ const moduleId = (0, encoding_1.parseModuleId)(a.moduleId);
83
+ return {
84
+ to: a.educationHubAddress,
85
+ abi: (0, contracts_1.getAbi)('EducationHubNew'),
86
+ method: 'completeModule',
87
+ args: [moduleId, a.answer],
88
+ meta: {
89
+ domain: 'education',
90
+ action: 'complete',
91
+ orgId: a.orgId,
92
+ summary: { moduleId, answer: `index ${a.answer}` },
93
+ },
94
+ };
95
+ }
96
+ /**
97
+ * Port of `pop education update` — src/commands/education/update.ts.
98
+ * EducationHubNew.updateModule(id, stringToBytes(name), contentHash,
99
+ * payoutWei) — full overwrite of title + contentHash + payout; the answer
100
+ * hash is IMMUTABLE.
101
+ */
102
+ function buildUpdateModule(a) {
103
+ const moduleId = (0, encoding_1.parseModuleId)(a.moduleId);
104
+ return {
105
+ to: a.educationHubAddress,
106
+ abi: (0, contracts_1.getAbi)('EducationHubNew'),
107
+ method: 'updateModule',
108
+ args: [moduleId, (0, encoding_1.stringToBytes)(a.name), a.contentHash, a.payoutWei],
109
+ meta: {
110
+ domain: 'education',
111
+ action: 'update',
112
+ orgId: a.orgId,
113
+ summary: { moduleId, title: a.name },
114
+ },
115
+ };
116
+ }
117
+ /**
118
+ * Port of `pop education remove` — src/commands/education/remove.ts.
119
+ * EducationHubNew.removeModule(moduleId) — PERMANENT delete, no undo; past
120
+ * completions keep their minted PT but the module disappears for everyone.
121
+ */
122
+ function buildRemoveModule(a) {
123
+ const moduleId = (0, encoding_1.parseModuleId)(a.moduleId);
124
+ return {
125
+ to: a.educationHubAddress,
126
+ abi: (0, contracts_1.getAbi)('EducationHubNew'),
127
+ method: 'removeModule',
128
+ args: [moduleId],
129
+ meta: {
130
+ domain: 'education',
131
+ action: 'remove',
132
+ orgId: a.orgId,
133
+ summary: {
134
+ module: a.title ? `#${moduleId} — ${a.title}` : `#${moduleId}`,
135
+ warning: 'permanent removal — no undo; the module and its payout disappear for everyone',
136
+ },
137
+ },
138
+ };
139
+ }
140
+ /**
141
+ * Resolved builder for `pop education create` — src/commands/education/create-module.ts.
142
+ * Resolves the org's EducationHub, pins the module metadata
143
+ * ({name, description, link, quiz, answers} — key order load-bearing), and
144
+ * delegates to buildCreateModule.
145
+ */
146
+ async function createModuleIntent(ctx, params) {
147
+ const quiz = params.quiz || [];
148
+ const answers = params.answers || [];
149
+ if (quiz.length > 0 && answers.length > 0 && quiz.length !== answers.length) {
150
+ throw new errors_1.CliError(`Quiz has ${quiz.length} questions but ${answers.length} answer sets. They must match.`, exit_codes_1.EXIT.USAGE);
151
+ }
152
+ const modules = await (0, resolve_1.resolveOrgModules)(ctx.client, params.org, ctx.chainId);
153
+ const educationHubAddress = (0, resolve_1.requireModule)(modules, 'educationHubAddress');
154
+ const metadata = (0, education_1.buildEducationModuleMetadata)({
155
+ name: params.name,
156
+ description: params.description,
157
+ link: params.link,
158
+ quiz,
159
+ answers,
160
+ });
161
+ const cid = await (0, ipfs_1.pinJson)((0, education_1.serializeEducationModuleMetadata)(metadata), ctx.ipfs);
162
+ const contentHash = (0, encoding_1.ipfsCidToBytes32)(cid);
163
+ const intent = buildCreateModule({
164
+ educationHubAddress,
165
+ name: params.name,
166
+ contentHash,
167
+ payout: params.payout,
168
+ correctAnswer: params.correctAnswer,
169
+ orgId: modules.orgId,
170
+ });
171
+ intent.meta.ipfs = { cid, metadata };
172
+ return intent;
173
+ }
174
+ /**
175
+ * Resolved builder for `pop education complete` — src/commands/education/complete.ts.
176
+ * Resolves the org's EducationHub and delegates to buildCompleteModule. The
177
+ * CLI's pre-flight (module exists via getModule, not-already-completed via
178
+ * hasCompleted) is a host concern — see checkModuleExists/checkNotCompleted
179
+ * in the CLI's education helpers.
180
+ */
181
+ async function completeModuleIntent(ctx, params) {
182
+ const modules = await (0, resolve_1.resolveOrgModules)(ctx.client, params.org, ctx.chainId);
183
+ const educationHubAddress = (0, resolve_1.requireModule)(modules, 'educationHubAddress');
184
+ return buildCompleteModule({
185
+ educationHubAddress,
186
+ moduleId: params.module,
187
+ answer: params.answer,
188
+ orgId: modules.orgId,
189
+ });
190
+ }
191
+ /**
192
+ * Resolved builder for `pop education update` — src/commands/education/update.ts.
193
+ *
194
+ * READ-MERGE issued concurrently (Promise.allSettled): payout + existence
195
+ * from on-chain getModule — deliberately NOT the indexed payout, because a
196
+ * full overwrite writes the read-back value and a stale one would silently
197
+ * ROLL BACK a concurrent raise; the same call is the ModuleUnknown existence
198
+ * gate. Title/metadata come from FETCH_MODULES_FOR_UPDATE (the chain stores
199
+ * only {answerHash, payout, exists} — metadata is event-only) with an IPFS
200
+ * fallback. Metadata is re-pinned ONLY when its content actually changes —
201
+ * same key order as create ({name, description, link, quiz, answers}).
202
+ *
203
+ * Requires ctx.provider (the on-chain getModule read).
204
+ */
205
+ async function updateModuleIntent(ctx, params) {
206
+ // ── 0. Fail fast on input problems before any network work ─────────
207
+ const metadataChanging = params.name !== undefined
208
+ || params.description !== undefined
209
+ || params.link !== undefined
210
+ || params.quiz !== undefined
211
+ || params.answers !== undefined;
212
+ const payoutChanging = params.payout !== undefined;
213
+ if (!metadataChanging && !payoutChanging) {
214
+ throw new errors_1.CliError('Nothing to update — pass at least one field flag.', exit_codes_1.EXIT.USAGE, 'Available: --payout, --name, --description, --link, --quiz, --answers');
215
+ }
216
+ if (payoutChanging && (!(params.payout > 0) || !isFinite(params.payout))) {
217
+ throw new errors_1.CliError(`--payout must be a positive number (the contract reverts InvalidPayout for 0), got: ${params.payout}`, exit_codes_1.EXIT.USAGE);
218
+ }
219
+ if (!ctx.provider) {
220
+ throw new errors_1.CliError('updateModuleIntent requires ctx.provider (on-chain getModule read — see the payout note above)', exit_codes_1.EXIT.USAGE);
221
+ }
222
+ const modules = await (0, resolve_1.resolveOrgModules)(ctx.client, params.org, ctx.chainId);
223
+ const educationHubAddress = (0, resolve_1.requireModule)(modules, 'educationHubAddress');
224
+ const moduleId = (0, encoding_1.parseModuleId)(params.module);
225
+ // ── 1+2. READ: on-chain payout/existence AND the indexed metadata ──
226
+ const hub = (0, contracts_1.createReadContract)(educationHubAddress, 'EducationHubNew', ctx.provider);
227
+ const [chainRead, indexedRead] = await Promise.allSettled([
228
+ hub.getModule(moduleId),
229
+ ctx.client.query(education_2.FETCH_MODULES_FOR_UPDATE, { orgId: modules.orgId }, ctx.chainId),
230
+ ]);
231
+ if (chainRead.status === 'rejected') {
232
+ throw new errors_1.PreconditionError(`Could not read module ${moduleId} on-chain — it may not exist (getModule reverts ModuleUnknown).`, 'List modules with: pop education list');
233
+ }
234
+ const currentPayout = chainRead.value[0];
235
+ // Subgraph unavailable — handled below based on what the merge needs.
236
+ let subgraphModule = null;
237
+ if (indexedRead.status === 'fulfilled') {
238
+ const rows = indexedRead.value.organization?.educationHub?.modules || [];
239
+ subgraphModule = rows.find((m) => String(m.moduleId) === String(moduleId)) || null;
240
+ }
241
+ let ipfsMetadata = null;
242
+ const currentCid = subgraphModule?.contentHash ? (0, encoding_1.bytes32ToIpfsCid)(subgraphModule.contentHash) : null;
243
+ if (currentCid) {
244
+ try {
245
+ ipfsMetadata = await (0, ipfs_1.fetchJson)(currentCid, ctx.ipfs);
246
+ }
247
+ catch { /* IPFS lag — fall back to subgraph fields */ }
248
+ }
249
+ const currentName = ipfsMetadata?.name ?? subgraphModule?.title ?? undefined;
250
+ const currentDescription = ipfsMetadata?.description ?? subgraphModule?.metadata?.description ?? undefined;
251
+ const currentLink = ipfsMetadata?.link ?? subgraphModule?.metadata?.link ?? undefined;
252
+ const currentQuiz = ipfsMetadata?.quiz ?? subgraphModule?.metadata?.quiz ?? undefined;
253
+ let currentAnswers = ipfsMetadata?.answers;
254
+ if (currentAnswers === undefined && subgraphModule?.metadata?.answersJson) {
255
+ try {
256
+ currentAnswers = JSON.parse(subgraphModule.metadata.answersJson);
257
+ }
258
+ catch { /* unparseable — treated as unknown */ }
259
+ }
260
+ const currentContentHash = subgraphModule?.contentHash ?? undefined;
261
+ // updateModule overwrites title + contentHash even when only payout
262
+ // changed — so the current values must be recoverable.
263
+ if (!metadataChanging && (currentName === undefined || !currentContentHash)) {
264
+ throw new errors_1.CliError(`Module ${moduleId} metadata is not indexed yet (subgraph lag) — cannot preserve the current title/metadata through a full-overwrite update.`, exit_codes_1.EXIT.INFRA, 'Retry in a few seconds, or pass --name and --description explicitly.');
265
+ }
266
+ if (metadataChanging
267
+ && (params.name === undefined || params.description === undefined)
268
+ && (currentName === undefined || currentDescription === undefined)) {
269
+ throw new errors_1.CliError(`Module ${moduleId} metadata is not indexed yet (subgraph lag) — cannot merge a partial metadata edit.`, exit_codes_1.EXIT.INFRA, 'Retry in a few seconds, or pass BOTH --name and --description.');
270
+ }
271
+ // ── 3. MERGE: passed fields override, everything else preserved ────
272
+ const finalPayout = payoutChanging
273
+ ? ethers_1.ethers.utils.parseUnits(params.payout.toString(), 18)
274
+ : currentPayout;
275
+ const finalName = params.name ?? currentName ?? '';
276
+ const finalDescription = params.description ?? currentDescription ?? '';
277
+ const finalLink = params.link ?? currentLink ?? '';
278
+ const finalQuiz = params.quiz ?? currentQuiz ?? [];
279
+ const finalAnswers = params.answers ?? currentAnswers ?? [];
280
+ if (finalQuiz.length > 0 && finalAnswers.length > 0 && finalQuiz.length !== finalAnswers.length) {
281
+ throw new errors_1.CliError(`Quiz has ${finalQuiz.length} questions but ${finalAnswers.length} answer sets. They must match.`, exit_codes_1.EXIT.USAGE);
282
+ }
283
+ // Re-pin only when the metadata content actually changes.
284
+ let finalContentHash = currentContentHash
285
+ ? currentContentHash
286
+ : ethers_1.ethers.constants.HashZero;
287
+ let newCid;
288
+ let metadataDoc;
289
+ const metadataActuallyChanged = metadataChanging && (finalName !== currentName
290
+ || finalDescription !== (currentDescription ?? '')
291
+ || finalLink !== (currentLink ?? '')
292
+ || JSON.stringify(finalQuiz) !== JSON.stringify(currentQuiz ?? [])
293
+ || JSON.stringify(finalAnswers) !== JSON.stringify(currentAnswers ?? []));
294
+ if (metadataActuallyChanged) {
295
+ // NOTE (CLI warns here): when neither IPFS nor the subgraph could supply
296
+ // the current metadata, unspecified fields reset to defaults in the
297
+ // re-pinned JSON.
298
+ // Key order MUST match metadata/education.ts (frontend/subgraph parity).
299
+ metadataDoc = (0, education_1.buildEducationModuleMetadata)({
300
+ name: finalName,
301
+ description: finalDescription,
302
+ link: finalLink,
303
+ quiz: finalQuiz,
304
+ answers: finalAnswers,
305
+ });
306
+ newCid = await (0, ipfs_1.pinJson)((0, education_1.serializeEducationModuleMetadata)(metadataDoc), ctx.ipfs);
307
+ finalContentHash = (0, encoding_1.ipfsCidToBytes32)(newCid);
308
+ }
309
+ const intent = buildUpdateModule({
310
+ educationHubAddress,
311
+ moduleId,
312
+ name: finalName,
313
+ contentHash: finalContentHash,
314
+ payoutWei: finalPayout,
315
+ orgId: modules.orgId,
316
+ });
317
+ intent.meta.summary.metadataChanged = metadataActuallyChanged;
318
+ if (newCid)
319
+ intent.meta.ipfs = { cid: newCid, metadata: metadataDoc };
320
+ return intent;
321
+ }
322
+ /**
323
+ * Resolved builder for `pop education remove` — src/commands/education/remove.ts.
324
+ * Resolves the org's EducationHub, fetches a best-effort title for the
325
+ * confirmation summary (subgraph may lag — never blocks), and delegates to
326
+ * buildRemoveModule. DESTRUCTIVE: hosts should require explicit confirmation.
327
+ */
328
+ async function removeModuleIntent(ctx, params) {
329
+ const modules = await (0, resolve_1.resolveOrgModules)(ctx.client, params.org, ctx.chainId);
330
+ const educationHubAddress = (0, resolve_1.requireModule)(modules, 'educationHubAddress');
331
+ const moduleId = (0, encoding_1.parseModuleId)(params.module);
332
+ // Best-effort title for the confirmation summary (subgraph may lag).
333
+ let title;
334
+ try {
335
+ title = await (0, education_2.getModuleTitle)(ctx.client, modules.orgId, moduleId, ctx.chainId);
336
+ }
337
+ catch { /* confirm falls back to the bare ID */ }
338
+ return buildRemoveModule({
339
+ educationHubAddress,
340
+ moduleId,
341
+ orgId: modules.orgId,
342
+ title,
343
+ });
344
+ }