dexe-mcp 0.11.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +109 -0
- package/README.md +11 -6
- package/dist/cli/init.d.ts +6 -0
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +64 -2
- package/dist/cli/init.js.map +1 -1
- package/dist/config.d.ts +13 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +12 -0
- package/dist/config.js.map +1 -1
- package/dist/diag/checks.d.ts.map +1 -1
- package/dist/diag/checks.js +29 -0
- package/dist/diag/checks.js.map +1 -1
- package/dist/env/schema.d.ts +14 -0
- package/dist/env/schema.d.ts.map +1 -1
- package/dist/env/schema.js +14 -0
- package/dist/env/schema.js.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/preflight.d.ts +133 -0
- package/dist/lib/preflight.d.ts.map +1 -0
- package/dist/lib/preflight.js +239 -0
- package/dist/lib/preflight.js.map +1 -0
- package/dist/lib/proposalBuilders.d.ts +50 -0
- package/dist/lib/proposalBuilders.d.ts.map +1 -0
- package/dist/lib/proposalBuilders.js +311 -0
- package/dist/lib/proposalBuilders.js.map +1 -0
- package/dist/lib/stateStore.d.ts +63 -0
- package/dist/lib/stateStore.d.ts.map +1 -0
- package/dist/lib/stateStore.js +128 -0
- package/dist/lib/stateStore.js.map +1 -0
- package/dist/lib/time.d.ts +16 -0
- package/dist/lib/time.d.ts.map +1 -0
- package/dist/lib/time.js +24 -0
- package/dist/lib/time.js.map +1 -0
- package/dist/tools/daoCreate.d.ts +6 -0
- package/dist/tools/daoCreate.d.ts.map +1 -0
- package/dist/tools/daoCreate.js +155 -0
- package/dist/tools/daoCreate.js.map +1 -0
- package/dist/tools/daoDeploy.d.ts +394 -0
- package/dist/tools/daoDeploy.d.ts.map +1 -1
- package/dist/tools/daoDeploy.js +393 -422
- package/dist/tools/daoDeploy.js.map +1 -1
- package/dist/tools/doctor.d.ts.map +1 -1
- package/dist/tools/doctor.js +15 -0
- package/dist/tools/doctor.js.map +1 -1
- package/dist/tools/flow.d.ts +13 -2
- package/dist/tools/flow.d.ts.map +1 -1
- package/dist/tools/flow.js +97 -16
- package/dist/tools/flow.js.map +1 -1
- package/dist/tools/gate.d.ts +28 -0
- package/dist/tools/gate.d.ts.map +1 -0
- package/dist/tools/gate.js +295 -0
- package/dist/tools/gate.js.map +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +14 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/operationalContext.d.ts +6 -0
- package/dist/tools/operationalContext.d.ts.map +1 -0
- package/dist/tools/operationalContext.js +122 -0
- package/dist/tools/operationalContext.js.map +1 -0
- package/dist/tools/otc.d.ts.map +1 -1
- package/dist/tools/otc.js +3 -0
- package/dist/tools/otc.js.map +1 -1
- package/dist/tools/proposalBuildMore.d.ts +69 -0
- package/dist/tools/proposalBuildMore.d.ts.map +1 -1
- package/dist/tools/proposalBuildMore.js +3 -2
- package/dist/tools/proposalBuildMore.js.map +1 -1
- package/dist/tools/subgraph.d.ts.map +1 -1
- package/dist/tools/subgraph.js +3 -0
- package/dist/tools/subgraph.js.map +1 -1
- package/package.json +2 -1
- package/skills/dexe-create-dao/SKILL.md +99 -0
- package/skills/dexe-create-proposal/SKILL.md +84 -0
- package/skills/dexe-otc/SKILL.md +73 -0
- package/skills/dexe-setup/SKILL.md +108 -0
- package/skills/dexe-vote-execute/SKILL.md +64 -0
package/dist/tools/daoDeploy.js
CHANGED
|
@@ -161,6 +161,394 @@ function payloadOutputSchema() {
|
|
|
161
161
|
predictedGovPool: z.string().nullable(),
|
|
162
162
|
};
|
|
163
163
|
}
|
|
164
|
+
// ---------- shared deploy params schema (reused by dexe_dao_create) ----------
|
|
165
|
+
/**
|
|
166
|
+
* Full `deployGovPool` parameter object. `descriptionURL` and `name` are the
|
|
167
|
+
* two fields `dexe_dao_create` derives itself (from the uploaded DAO metadata
|
|
168
|
+
* + daoName), so it consumes `DeployParamsSchema.omit({ descriptionURL, name })`.
|
|
169
|
+
*/
|
|
170
|
+
export const DeployParamsSchema = z.object({
|
|
171
|
+
settingsParams: SettingsDeployParamsSchema,
|
|
172
|
+
validatorsParams: ValidatorsDeployParamsSchema.optional(),
|
|
173
|
+
userKeeperParams: UserKeeperDeployParamsSchema,
|
|
174
|
+
tokenParams: TokenParamsSchema,
|
|
175
|
+
votePowerParams: VotePowerDeployParamsSchema,
|
|
176
|
+
verifier: z.string().default("0x0000000000000000000000000000000000000000"),
|
|
177
|
+
onlyBABTHolders: z.boolean().default(false),
|
|
178
|
+
descriptionURL: z.string().describe("ipfs://<cid> of DAO metadata JSON"),
|
|
179
|
+
name: z.string().min(1),
|
|
180
|
+
});
|
|
181
|
+
/**
|
|
182
|
+
* Pure builder for the `PoolFactory.deployGovPool` tx. Extracted from the
|
|
183
|
+
* `dexe_dao_build_deploy` handler so the `dexe_dao_create` composite reuses the
|
|
184
|
+
* exact same predicted-address wiring, settings auto-expand, cap/minted guard,
|
|
185
|
+
* executorDescription IPFS upload, and encoding. Returns a discriminated result
|
|
186
|
+
* instead of tool-shaped content.
|
|
187
|
+
*/
|
|
188
|
+
export async function buildDeployGovPool(input, ctx, rpc) {
|
|
189
|
+
const { chainId, poolFactory, deployer, params } = input;
|
|
190
|
+
const fail = (error) => ({ ok: false, error });
|
|
191
|
+
const chain = resolveChain(ctx.config, chainId);
|
|
192
|
+
const isTokenCreation = params.tokenParams.name.length > 0;
|
|
193
|
+
const hasValidators = !!(params.validatorsParams && params.validatorsParams.validators.length > 0);
|
|
194
|
+
// ---------- auto-encode vote power initData ----------
|
|
195
|
+
let votePowerInitData;
|
|
196
|
+
try {
|
|
197
|
+
if (params.votePowerParams.voteType === "CUSTOM_VOTES") {
|
|
198
|
+
votePowerInitData = params.votePowerParams.initData ?? "0x";
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
votePowerInitData = encodeVotePowerInitData(params.votePowerParams.voteType, params.votePowerParams.polynomialCoefficients);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
catch (err) {
|
|
205
|
+
return fail(`Failed to encode vote power initData: ${err instanceof Error ? err.message : String(err)}`);
|
|
206
|
+
}
|
|
207
|
+
// ---------- default validatorsParams when omitted ----------
|
|
208
|
+
const VT_SUFFIX = "-VT";
|
|
209
|
+
const rawValidatorsParams = params.validatorsParams ?? {
|
|
210
|
+
name: "Validator Token",
|
|
211
|
+
symbol: "VT",
|
|
212
|
+
proposalSettings: {
|
|
213
|
+
duration: params.settingsParams.proposalSettings[0].duration,
|
|
214
|
+
executionDelay: params.settingsParams.proposalSettings[0].executionDelay,
|
|
215
|
+
quorum: params.settingsParams.proposalSettings[0].quorum,
|
|
216
|
+
},
|
|
217
|
+
validators: [],
|
|
218
|
+
balances: [],
|
|
219
|
+
};
|
|
220
|
+
const validatorsParams = {
|
|
221
|
+
...rawValidatorsParams,
|
|
222
|
+
name: rawValidatorsParams.name.endsWith(VT_SUFFIX) ? rawValidatorsParams.name : rawValidatorsParams.name + VT_SUFFIX,
|
|
223
|
+
symbol: rawValidatorsParams.symbol.endsWith(VT_SUFFIX) ? rawValidatorsParams.symbol : rawValidatorsParams.symbol + VT_SUFFIX,
|
|
224
|
+
};
|
|
225
|
+
// ---------- validate leaf addresses ----------
|
|
226
|
+
if (poolFactory && !isAddress(poolFactory))
|
|
227
|
+
return fail(`Invalid poolFactory: ${poolFactory}`);
|
|
228
|
+
if (!isAddress(deployer))
|
|
229
|
+
return fail(`Invalid deployer: ${deployer}`);
|
|
230
|
+
if (isTokenCreation && params.tokenParams.users.length === 0)
|
|
231
|
+
return fail("tokenParams.users must have at least one recipient when creating a new token");
|
|
232
|
+
if (!isAddress(params.verifier))
|
|
233
|
+
return fail(`Invalid verifier: ${params.verifier}`);
|
|
234
|
+
if (!isAddress(params.userKeeperParams.tokenAddress))
|
|
235
|
+
return fail(`Invalid userKeeperParams.tokenAddress`);
|
|
236
|
+
if (!isAddress(params.userKeeperParams.nftAddress))
|
|
237
|
+
return fail(`Invalid userKeeperParams.nftAddress`);
|
|
238
|
+
if (!isAddress(params.votePowerParams.presetAddress))
|
|
239
|
+
return fail(`Invalid votePowerParams.presetAddress`);
|
|
240
|
+
for (const v of validatorsParams.validators) {
|
|
241
|
+
if (!isAddress(v))
|
|
242
|
+
return fail(`Invalid validator: ${v}`);
|
|
243
|
+
}
|
|
244
|
+
if (validatorsParams.validators.length !== validatorsParams.balances.length) {
|
|
245
|
+
return fail("validatorsParams.validators and .balances must be the same length");
|
|
246
|
+
}
|
|
247
|
+
for (const u of params.tokenParams.users) {
|
|
248
|
+
if (!isAddress(u))
|
|
249
|
+
return fail(`Invalid tokenParams.users entry: ${u}`);
|
|
250
|
+
}
|
|
251
|
+
if (params.tokenParams.users.length !== params.tokenParams.amounts.length) {
|
|
252
|
+
return fail("tokenParams.users and .amounts must be the same length");
|
|
253
|
+
}
|
|
254
|
+
// Bug #28: ERC20Gov init reverts silently when cap == mintedTotal.
|
|
255
|
+
if (isTokenCreation) {
|
|
256
|
+
const capBn = BigInt(params.tokenParams.cap);
|
|
257
|
+
const mintedBn = BigInt(params.tokenParams.mintedTotal);
|
|
258
|
+
if (capBn > 0n && capBn <= mintedBn) {
|
|
259
|
+
return fail(`tokenParams.cap (${capBn}) must be strictly greater than tokenParams.mintedTotal (${mintedBn}). ERC20Gov requires headroom for future minting; pass cap=0 for uncapped, or cap > mintedTotal.`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// ---------- resolve PoolFactory address ----------
|
|
263
|
+
let factoryAddress = poolFactory;
|
|
264
|
+
if (!factoryAddress) {
|
|
265
|
+
try {
|
|
266
|
+
const pr = rpc.tryProvider(chain.chainId);
|
|
267
|
+
if ("error" in pr)
|
|
268
|
+
return fail(`${pr.error}\n${pr.remediation}`);
|
|
269
|
+
const provider = pr.ok;
|
|
270
|
+
const book = new AddressBook({
|
|
271
|
+
provider,
|
|
272
|
+
chainId: chain.chainId,
|
|
273
|
+
registryOverride: chain.registryOverride ?? ctx.config.registryOverride,
|
|
274
|
+
});
|
|
275
|
+
factoryAddress = await book.resolve(CONTRACT_NAMES.POOL_FACTORY);
|
|
276
|
+
}
|
|
277
|
+
catch (err) {
|
|
278
|
+
return fail(`poolFactory address needed: pass it explicitly, or configure DEXE_RPC_URL so the ContractsRegistry lookup works. (${err instanceof Error ? err.message : String(err)})`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// ---------- predict addresses (always required — wires executors) ----------
|
|
282
|
+
let predictedGovPool;
|
|
283
|
+
let predictedGovToken;
|
|
284
|
+
let predictedDistribution;
|
|
285
|
+
let predictedTokenSale;
|
|
286
|
+
try {
|
|
287
|
+
const pr = rpc.tryProvider(chain.chainId);
|
|
288
|
+
if ("error" in pr)
|
|
289
|
+
return fail(`${pr.error}\n${pr.remediation}`);
|
|
290
|
+
const provider = pr.ok;
|
|
291
|
+
const predictIface = new Interface([
|
|
292
|
+
"function predictGovAddresses(address deployer, string poolName) view returns (tuple(address govPool, address govTokenSale, address govToken, address distributionProposal, address expertNft, address nftMultiplier))",
|
|
293
|
+
]);
|
|
294
|
+
const { Contract } = await import("ethers");
|
|
295
|
+
const factory = new Contract(factoryAddress, predictIface, provider);
|
|
296
|
+
const res = await factory.getFunction("predictGovAddresses").staticCall(deployer, params.name);
|
|
297
|
+
predictedGovPool = res.govPool;
|
|
298
|
+
predictedGovToken = res.govToken;
|
|
299
|
+
predictedDistribution = res.distributionProposal;
|
|
300
|
+
predictedTokenSale = res.govTokenSale;
|
|
301
|
+
}
|
|
302
|
+
catch (err) {
|
|
303
|
+
return fail(`Failed to predict gov addresses: ${err instanceof Error ? err.message : String(err)}`);
|
|
304
|
+
}
|
|
305
|
+
const ZERO = "0x0000000000000000000000000000000000000000";
|
|
306
|
+
const effectiveTokenAddress = isTokenCreation && predictedGovToken ? predictedGovToken : params.userKeeperParams.tokenAddress;
|
|
307
|
+
// Bug #12: substitute predicted govToken as rewardToken when reward amounts set.
|
|
308
|
+
if (isTokenCreation && predictedGovToken) {
|
|
309
|
+
const hasRewardAmounts = (r) => BigInt(r.creationReward) > 0n || BigInt(r.executionReward) > 0n || BigInt(r.voteRewardsCoefficient) > 0n;
|
|
310
|
+
for (const s of params.settingsParams.proposalSettings) {
|
|
311
|
+
if ((s.rewardsInfo.rewardToken === ZERO || s.rewardsInfo.rewardToken === "") && hasRewardAmounts(s.rewardsInfo)) {
|
|
312
|
+
s.rewardsInfo.rewardToken = predictedGovToken;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
const effectiveExecutors = [...params.settingsParams.additionalProposalExecutors];
|
|
317
|
+
if (predictedDistribution && !effectiveExecutors.includes(predictedDistribution))
|
|
318
|
+
effectiveExecutors.push(predictedDistribution);
|
|
319
|
+
if (predictedTokenSale && !effectiveExecutors.includes(predictedTokenSale))
|
|
320
|
+
effectiveExecutors.push(predictedTokenSale);
|
|
321
|
+
if (effectiveTokenAddress === ZERO && params.userKeeperParams.nftAddress === ZERO) {
|
|
322
|
+
return fail("GovUK requires at least one governance asset: set userKeeperParams.tokenAddress (existing ERC20), userKeeperParams.nftAddress (existing ERC721), or provide tokenParams.name to create a new token");
|
|
323
|
+
}
|
|
324
|
+
// ---------- auto-expand proposal settings (1 → 5) ----------
|
|
325
|
+
let expandedSettings = params.settingsParams.proposalSettings;
|
|
326
|
+
if (expandedSettings.length === 1) {
|
|
327
|
+
const base = expandedSettings[0];
|
|
328
|
+
const baseWithValidatorFallback = {
|
|
329
|
+
...base,
|
|
330
|
+
validatorsVote: true,
|
|
331
|
+
durationValidators: hasValidators ? base.durationValidators : base.duration,
|
|
332
|
+
quorumValidators: hasValidators ? base.quorumValidators : base.quorum,
|
|
333
|
+
};
|
|
334
|
+
const dpSettings = { ...baseWithValidatorFallback, delegatedVotingAllowed: false, earlyCompletion: false };
|
|
335
|
+
expandedSettings = [
|
|
336
|
+
baseWithValidatorFallback,
|
|
337
|
+
baseWithValidatorFallback,
|
|
338
|
+
baseWithValidatorFallback,
|
|
339
|
+
dpSettings,
|
|
340
|
+
baseWithValidatorFallback,
|
|
341
|
+
];
|
|
342
|
+
}
|
|
343
|
+
else if (expandedSettings.length !== 5) {
|
|
344
|
+
return fail(`proposalSettings must be 1 (auto-expand to 5) or exactly 5. Got ${expandedSettings.length}.`);
|
|
345
|
+
}
|
|
346
|
+
// ---------- treasury-safety advisory: quorum floor ----------
|
|
347
|
+
let quorumWarning = "";
|
|
348
|
+
if (ctx.config.treasuryGuard !== "off") {
|
|
349
|
+
const floor = ctx.config.minSafeQuorumPct;
|
|
350
|
+
const showPct = (p) => (Number.isFinite(p) ? `${p}%` : "unparseable");
|
|
351
|
+
const lowQuorum = [];
|
|
352
|
+
expandedSettings.forEach((s, i) => {
|
|
353
|
+
const pct = quorumPctFromRaw(s.quorum);
|
|
354
|
+
if (judgeQuorum(pct, floor) !== "SAFE")
|
|
355
|
+
lowQuorum.push(`proposalSettings[${i}] quorum=${showPct(pct)}`);
|
|
356
|
+
});
|
|
357
|
+
const vpct = quorumPctFromRaw(validatorsParams.proposalSettings.quorum);
|
|
358
|
+
if (judgeQuorum(vpct, floor) !== "SAFE")
|
|
359
|
+
lowQuorum.push(`validatorsParams quorum=${showPct(vpct)}`);
|
|
360
|
+
if (lowQuorum.length > 0) {
|
|
361
|
+
quorumWarning =
|
|
362
|
+
`\n⚠️ Quorum below the ${floor}% safe floor (DEXE_MIN_SAFE_QUORUM_PCT): ${lowQuorum.join(", ")}. ` +
|
|
363
|
+
`Low quorum reduces the participation required to pass a proposal; for a DAO that will hold ` +
|
|
364
|
+
`treasury assets, set quorum ≥50% (51%+ recommended). The safe value is DAO-specific and must ` +
|
|
365
|
+
`be verified. [governance-safety advisory]`;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
// ---------- auto-upload executorDescription to IPFS ----------
|
|
369
|
+
let pinataWarning = "";
|
|
370
|
+
if (ctx.config.pinataJwt) {
|
|
371
|
+
const pinata = new PinataClient(ctx.config.pinataJwt);
|
|
372
|
+
const settingsToUpload = [
|
|
373
|
+
{ index: 0, label: "default" },
|
|
374
|
+
{ index: 3, label: "distributionProposal" },
|
|
375
|
+
];
|
|
376
|
+
for (const { index, label } of settingsToUpload) {
|
|
377
|
+
const s = expandedSettings[index];
|
|
378
|
+
if (!s.executorDescription || s.executorDescription === "") {
|
|
379
|
+
try {
|
|
380
|
+
const settingsJson = {
|
|
381
|
+
earlyCompletion: s.earlyCompletion,
|
|
382
|
+
delegatedVotingAllowed: s.delegatedVotingAllowed,
|
|
383
|
+
validatorsVote: s.validatorsVote,
|
|
384
|
+
duration: s.duration,
|
|
385
|
+
durationValidators: s.durationValidators,
|
|
386
|
+
quorum: s.quorum,
|
|
387
|
+
quorumValidators: s.quorumValidators,
|
|
388
|
+
minVotesForVoting: s.minVotesForVoting,
|
|
389
|
+
minVotesForCreating: s.minVotesForCreating,
|
|
390
|
+
executionDelay: s.executionDelay,
|
|
391
|
+
rewardsInfo: {
|
|
392
|
+
rewardToken: s.rewardsInfo.rewardToken,
|
|
393
|
+
creationReward: s.rewardsInfo.creationReward,
|
|
394
|
+
executionReward: s.rewardsInfo.executionReward,
|
|
395
|
+
voteRewardsCoefficient: s.rewardsInfo.voteRewardsCoefficient,
|
|
396
|
+
},
|
|
397
|
+
minVotesForReadProposalDiscussion: "0",
|
|
398
|
+
minVotesForCreatingComment: "1000000000000000000",
|
|
399
|
+
};
|
|
400
|
+
const res = await pinata.pinJson(settingsJson, { name: `dao-settings-${label}:${params.name.slice(0, 40)}` });
|
|
401
|
+
const cid = `ipfs://${res.cid}`;
|
|
402
|
+
if (index === 0) {
|
|
403
|
+
expandedSettings[0] = { ...expandedSettings[0], executorDescription: cid };
|
|
404
|
+
expandedSettings[1] = { ...expandedSettings[1], executorDescription: cid };
|
|
405
|
+
expandedSettings[2] = { ...expandedSettings[2], executorDescription: cid };
|
|
406
|
+
expandedSettings[4] = { ...expandedSettings[4], executorDescription: cid };
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
expandedSettings[3] = { ...expandedSettings[3], executorDescription: cid };
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
catch (err) {
|
|
413
|
+
pinataWarning += `\n⚠️ Failed to upload ${label} executorDescription: ${err instanceof Error ? err.message : String(err)}`;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
const missingDesc = expandedSettings.some((s) => !s.executorDescription || s.executorDescription === "");
|
|
420
|
+
if (missingDesc) {
|
|
421
|
+
pinataWarning =
|
|
422
|
+
"\n⚠️ DEXE_PINATA_JWT not configured — executorDescription fields are empty. " +
|
|
423
|
+
"DAOs created without executorDescription will display broken proposal settings in the frontend UI. " +
|
|
424
|
+
"Set DEXE_PINATA_JWT to enable auto-upload.";
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
// ---------- build the tuple arg ----------
|
|
428
|
+
const paramsTuple = [
|
|
429
|
+
[
|
|
430
|
+
expandedSettings.map((s) => [
|
|
431
|
+
s.earlyCompletion,
|
|
432
|
+
s.delegatedVotingAllowed,
|
|
433
|
+
s.validatorsVote,
|
|
434
|
+
BigInt(s.duration),
|
|
435
|
+
BigInt(s.durationValidators),
|
|
436
|
+
BigInt(s.executionDelay),
|
|
437
|
+
BigInt(s.quorum),
|
|
438
|
+
BigInt(s.quorumValidators),
|
|
439
|
+
BigInt(s.minVotesForVoting),
|
|
440
|
+
BigInt(s.minVotesForCreating),
|
|
441
|
+
[
|
|
442
|
+
s.rewardsInfo.rewardToken,
|
|
443
|
+
BigInt(s.rewardsInfo.creationReward),
|
|
444
|
+
BigInt(s.rewardsInfo.executionReward),
|
|
445
|
+
BigInt(s.rewardsInfo.voteRewardsCoefficient),
|
|
446
|
+
],
|
|
447
|
+
s.executorDescription,
|
|
448
|
+
]),
|
|
449
|
+
effectiveExecutors,
|
|
450
|
+
],
|
|
451
|
+
[
|
|
452
|
+
validatorsParams.name,
|
|
453
|
+
validatorsParams.symbol,
|
|
454
|
+
[
|
|
455
|
+
BigInt(validatorsParams.proposalSettings.duration),
|
|
456
|
+
BigInt(validatorsParams.proposalSettings.executionDelay),
|
|
457
|
+
BigInt(validatorsParams.proposalSettings.quorum),
|
|
458
|
+
],
|
|
459
|
+
validatorsParams.validators,
|
|
460
|
+
validatorsParams.balances.map((b) => BigInt(b)),
|
|
461
|
+
],
|
|
462
|
+
[
|
|
463
|
+
effectiveTokenAddress,
|
|
464
|
+
params.userKeeperParams.nftAddress,
|
|
465
|
+
BigInt(params.userKeeperParams.individualPower),
|
|
466
|
+
BigInt(params.userKeeperParams.nftsTotalSupply),
|
|
467
|
+
],
|
|
468
|
+
[
|
|
469
|
+
params.tokenParams.name,
|
|
470
|
+
params.tokenParams.symbol,
|
|
471
|
+
params.tokenParams.users,
|
|
472
|
+
BigInt(params.tokenParams.cap),
|
|
473
|
+
BigInt(params.tokenParams.mintedTotal),
|
|
474
|
+
params.tokenParams.amounts.map((a) => BigInt(a)),
|
|
475
|
+
],
|
|
476
|
+
[
|
|
477
|
+
VOTE_POWER_TYPES.indexOf(params.votePowerParams.voteType),
|
|
478
|
+
votePowerInitData,
|
|
479
|
+
params.votePowerParams.presetAddress,
|
|
480
|
+
],
|
|
481
|
+
params.verifier,
|
|
482
|
+
params.onlyBABTHolders,
|
|
483
|
+
params.descriptionURL.replace(/^ipfs:\/\//, ""),
|
|
484
|
+
params.name,
|
|
485
|
+
];
|
|
486
|
+
// ---------- get ABI (artifact > fallback) ----------
|
|
487
|
+
let iface;
|
|
488
|
+
let ifaceSource;
|
|
489
|
+
try {
|
|
490
|
+
const records = ctx.artifacts.get("PoolFactory");
|
|
491
|
+
if (records.length > 0) {
|
|
492
|
+
iface = new Interface(records[0].abi);
|
|
493
|
+
ifaceSource = "compiled artifact";
|
|
494
|
+
try {
|
|
495
|
+
iface.getFunction("deployGovPool");
|
|
496
|
+
}
|
|
497
|
+
catch {
|
|
498
|
+
iface = new Interface(FALLBACK_POOL_FACTORY_ABI);
|
|
499
|
+
ifaceSource = "fallback (compiled artifact missing deployGovPool)";
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
else {
|
|
503
|
+
iface = new Interface(FALLBACK_POOL_FACTORY_ABI);
|
|
504
|
+
ifaceSource = "fallback (artifacts not loaded — run dexe_compile for strict parity)";
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
catch (err) {
|
|
508
|
+
if (err instanceof ArtifactsMissingError) {
|
|
509
|
+
iface = new Interface(FALLBACK_POOL_FACTORY_ABI);
|
|
510
|
+
ifaceSource = "fallback (no artifacts — run dexe_compile for strict parity)";
|
|
511
|
+
}
|
|
512
|
+
else {
|
|
513
|
+
return fail(`Failed to load PoolFactory ABI: ${err instanceof Error ? err.message : String(err)}`);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
// ---------- encode ----------
|
|
517
|
+
let payload;
|
|
518
|
+
try {
|
|
519
|
+
payload = buildPayload({
|
|
520
|
+
to: factoryAddress,
|
|
521
|
+
iface,
|
|
522
|
+
method: "deployGovPool",
|
|
523
|
+
args: [paramsTuple],
|
|
524
|
+
chainId: chain.chainId,
|
|
525
|
+
contractLabel: "PoolFactory",
|
|
526
|
+
description: `PoolFactory.deployGovPool("${params.name}") via ${ifaceSource}`,
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
catch (err) {
|
|
530
|
+
return fail(`deployGovPool encoding failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
531
|
+
}
|
|
532
|
+
let note = ifaceSource.includes("fallback")
|
|
533
|
+
? "⚠️ Using fallback tuple ABI. For guaranteed parity, run dexe_compile to populate artifacts."
|
|
534
|
+
: "Encoded against compiled artifact — strict parity with deployed PoolFactory.";
|
|
535
|
+
if (pinataWarning)
|
|
536
|
+
note += pinataWarning;
|
|
537
|
+
if (quorumWarning)
|
|
538
|
+
note += quorumWarning;
|
|
539
|
+
return {
|
|
540
|
+
ok: true,
|
|
541
|
+
payload,
|
|
542
|
+
predictedGovPool,
|
|
543
|
+
note,
|
|
544
|
+
predicted: {
|
|
545
|
+
govPool: predictedGovPool,
|
|
546
|
+
govToken: predictedGovToken,
|
|
547
|
+
distributionProposal: predictedDistribution,
|
|
548
|
+
govTokenSale: predictedTokenSale,
|
|
549
|
+
},
|
|
550
|
+
};
|
|
551
|
+
}
|
|
164
552
|
// ---------- dexe_dao_build_deploy ----------
|
|
165
553
|
function registerBuildDeploy(server, ctx, rpc) {
|
|
166
554
|
server.registerTool("dexe_dao_build_deploy", {
|
|
@@ -202,431 +590,14 @@ function registerBuildDeploy(server, ctx, rpc) {
|
|
|
202
590
|
deployer: z
|
|
203
591
|
.string()
|
|
204
592
|
.describe("tx.origin that will send the deploy tx — required for address prediction"),
|
|
205
|
-
params:
|
|
206
|
-
settingsParams: SettingsDeployParamsSchema,
|
|
207
|
-
validatorsParams: ValidatorsDeployParamsSchema.optional(),
|
|
208
|
-
userKeeperParams: UserKeeperDeployParamsSchema,
|
|
209
|
-
tokenParams: TokenParamsSchema,
|
|
210
|
-
votePowerParams: VotePowerDeployParamsSchema,
|
|
211
|
-
verifier: z.string().default("0x0000000000000000000000000000000000000000"),
|
|
212
|
-
onlyBABTHolders: z.boolean().default(false),
|
|
213
|
-
descriptionURL: z.string().describe("ipfs://<cid> of DAO metadata JSON"),
|
|
214
|
-
name: z.string().min(1),
|
|
215
|
-
}),
|
|
593
|
+
params: DeployParamsSchema,
|
|
216
594
|
},
|
|
217
595
|
outputSchema: payloadOutputSchema(),
|
|
218
596
|
}, async ({ chainId, poolFactory, deployer, params }) => {
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
let votePowerInitData;
|
|
224
|
-
try {
|
|
225
|
-
if (params.votePowerParams.voteType === "CUSTOM_VOTES") {
|
|
226
|
-
// CUSTOM_VOTES: use caller-provided initData (contract skips .call for custom)
|
|
227
|
-
votePowerInitData = params.votePowerParams.initData ?? "0x";
|
|
228
|
-
}
|
|
229
|
-
else {
|
|
230
|
-
// LINEAR / POLYNOMIAL: auto-encode the correct initializer
|
|
231
|
-
votePowerInitData = encodeVotePowerInitData(params.votePowerParams.voteType, params.votePowerParams.polynomialCoefficients);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
catch (err) {
|
|
235
|
-
return errorResult(`Failed to encode vote power initData: ${err instanceof Error ? err.message : String(err)}`);
|
|
236
|
-
}
|
|
237
|
-
// ---------- default validatorsParams when omitted ----------
|
|
238
|
-
const VT_SUFFIX = "-VT";
|
|
239
|
-
const rawValidatorsParams = params.validatorsParams ?? {
|
|
240
|
-
name: "Validator Token",
|
|
241
|
-
symbol: "VT",
|
|
242
|
-
proposalSettings: {
|
|
243
|
-
duration: params.settingsParams.proposalSettings[0].duration,
|
|
244
|
-
executionDelay: params.settingsParams.proposalSettings[0].executionDelay,
|
|
245
|
-
quorum: params.settingsParams.proposalSettings[0].quorum,
|
|
246
|
-
},
|
|
247
|
-
validators: [],
|
|
248
|
-
balances: [],
|
|
249
|
-
};
|
|
250
|
-
// Bug #11: Frontend appends "-VT" suffix to validator token name and symbol.
|
|
251
|
-
// The form UI always stores name/symbol with the suffix already included.
|
|
252
|
-
// Replicate that here: if the suffix isn't already present, append it.
|
|
253
|
-
const validatorsParams = {
|
|
254
|
-
...rawValidatorsParams,
|
|
255
|
-
name: rawValidatorsParams.name.endsWith(VT_SUFFIX)
|
|
256
|
-
? rawValidatorsParams.name
|
|
257
|
-
: rawValidatorsParams.name + VT_SUFFIX,
|
|
258
|
-
symbol: rawValidatorsParams.symbol.endsWith(VT_SUFFIX)
|
|
259
|
-
? rawValidatorsParams.symbol
|
|
260
|
-
: rawValidatorsParams.symbol + VT_SUFFIX,
|
|
261
|
-
};
|
|
262
|
-
// ---------- validate leaf addresses ----------
|
|
263
|
-
if (poolFactory && !isAddress(poolFactory))
|
|
264
|
-
return errorResult(`Invalid poolFactory: ${poolFactory}`);
|
|
265
|
-
if (!isAddress(deployer))
|
|
266
|
-
return errorResult(`Invalid deployer: ${deployer}`);
|
|
267
|
-
if (isTokenCreation && params.tokenParams.users.length === 0)
|
|
268
|
-
return errorResult("tokenParams.users must have at least one recipient when creating a new token");
|
|
269
|
-
if (!isAddress(params.verifier))
|
|
270
|
-
return errorResult(`Invalid verifier: ${params.verifier}`);
|
|
271
|
-
if (!isAddress(params.userKeeperParams.tokenAddress))
|
|
272
|
-
return errorResult(`Invalid userKeeperParams.tokenAddress`);
|
|
273
|
-
if (!isAddress(params.userKeeperParams.nftAddress))
|
|
274
|
-
return errorResult(`Invalid userKeeperParams.nftAddress`);
|
|
275
|
-
if (!isAddress(params.votePowerParams.presetAddress))
|
|
276
|
-
return errorResult(`Invalid votePowerParams.presetAddress`);
|
|
277
|
-
for (const v of validatorsParams.validators) {
|
|
278
|
-
if (!isAddress(v))
|
|
279
|
-
return errorResult(`Invalid validator: ${v}`);
|
|
280
|
-
}
|
|
281
|
-
if (validatorsParams.validators.length !== validatorsParams.balances.length) {
|
|
282
|
-
return errorResult("validatorsParams.validators and .balances must be the same length");
|
|
283
|
-
}
|
|
284
|
-
for (const u of params.tokenParams.users) {
|
|
285
|
-
if (!isAddress(u))
|
|
286
|
-
return errorResult(`Invalid tokenParams.users entry: ${u}`);
|
|
287
|
-
}
|
|
288
|
-
if (params.tokenParams.users.length !== params.tokenParams.amounts.length) {
|
|
289
|
-
return errorResult("tokenParams.users and .amounts must be the same length");
|
|
290
|
-
}
|
|
291
|
-
// Bug #28: ERC20Gov init reverts inside _initGovPool with the generic
|
|
292
|
-
// "Address: low-level delegate call failed" when cap == mintedTotal.
|
|
293
|
-
// The token contract requires strict headroom (cap > minted) or cap=0
|
|
294
|
-
// (uncapped). Pre-flight reject so callers get a clear error instead of
|
|
295
|
-
// a 10M-gas revert hiding the real cause.
|
|
296
|
-
if (isTokenCreation) {
|
|
297
|
-
const capBn = BigInt(params.tokenParams.cap);
|
|
298
|
-
const mintedBn = BigInt(params.tokenParams.mintedTotal);
|
|
299
|
-
if (capBn > 0n && capBn <= mintedBn) {
|
|
300
|
-
return errorResult(`tokenParams.cap (${capBn}) must be strictly greater than tokenParams.mintedTotal (${mintedBn}). ERC20Gov requires headroom for future minting; pass cap=0 for uncapped, or cap > mintedTotal.`);
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
// ---------- resolve PoolFactory address ----------
|
|
304
|
-
let factoryAddress = poolFactory;
|
|
305
|
-
if (!factoryAddress) {
|
|
306
|
-
try {
|
|
307
|
-
const pr = rpc.tryProvider(chain.chainId);
|
|
308
|
-
if ("error" in pr)
|
|
309
|
-
return errorResult(`${pr.error}\n${pr.remediation}`);
|
|
310
|
-
const provider = pr.ok;
|
|
311
|
-
const book = new AddressBook({
|
|
312
|
-
provider,
|
|
313
|
-
chainId: chain.chainId,
|
|
314
|
-
registryOverride: chain.registryOverride ?? ctx.config.registryOverride,
|
|
315
|
-
});
|
|
316
|
-
factoryAddress = await book.resolve(CONTRACT_NAMES.POOL_FACTORY);
|
|
317
|
-
}
|
|
318
|
-
catch (err) {
|
|
319
|
-
return errorResult(`poolFactory address needed: pass it explicitly, or configure DEXE_RPC_URL so the ContractsRegistry lookup works. (${err instanceof Error ? err.message : String(err)})`);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
// ---------- predict addresses (always required — wires executors) ----------
|
|
323
|
-
let predictedGovPool;
|
|
324
|
-
let predictedGovToken;
|
|
325
|
-
let predictedDistribution;
|
|
326
|
-
let predictedTokenSale;
|
|
327
|
-
try {
|
|
328
|
-
const pr = rpc.tryProvider(chain.chainId);
|
|
329
|
-
if ("error" in pr)
|
|
330
|
-
return errorResult(`${pr.error}\n${pr.remediation}`);
|
|
331
|
-
const provider = pr.ok;
|
|
332
|
-
const predictIface = new Interface([
|
|
333
|
-
"function predictGovAddresses(address deployer, string poolName) view returns (tuple(address govPool, address govTokenSale, address govToken, address distributionProposal, address expertNft, address nftMultiplier))",
|
|
334
|
-
]);
|
|
335
|
-
const { Contract } = await import("ethers");
|
|
336
|
-
const factory = new Contract(factoryAddress, predictIface, provider);
|
|
337
|
-
const res = await factory.getFunction("predictGovAddresses").staticCall(deployer, params.name);
|
|
338
|
-
predictedGovPool = res.govPool;
|
|
339
|
-
predictedGovToken = res.govToken;
|
|
340
|
-
predictedDistribution = res.distributionProposal;
|
|
341
|
-
predictedTokenSale = res.govTokenSale;
|
|
342
|
-
}
|
|
343
|
-
catch (err) {
|
|
344
|
-
return errorResult(`Failed to predict gov addresses: ${err instanceof Error ? err.message : String(err)}`);
|
|
345
|
-
}
|
|
346
|
-
const ZERO = "0x0000000000000000000000000000000000000000";
|
|
347
|
-
// Wire predicted govToken into userKeeperParams when creating a new token
|
|
348
|
-
const effectiveTokenAddress = isTokenCreation && predictedGovToken
|
|
349
|
-
? predictedGovToken
|
|
350
|
-
: params.userKeeperParams.tokenAddress;
|
|
351
|
-
// Bug #12: When "use governance token for rewards" is checked, the frontend
|
|
352
|
-
// sets rewardToken to the predicted govToken address. When creating a new
|
|
353
|
-
// token, the address isn't known at input time, so callers pass ZERO_ADDR.
|
|
354
|
-
// Detect this case: rewardToken is zero BUT reward amounts are non-zero
|
|
355
|
-
// → substitute the predicted govToken address (matching frontend behavior).
|
|
356
|
-
if (isTokenCreation && predictedGovToken) {
|
|
357
|
-
const hasRewardAmounts = (r) => BigInt(r.creationReward) > 0n || BigInt(r.executionReward) > 0n || BigInt(r.voteRewardsCoefficient) > 0n;
|
|
358
|
-
for (const s of params.settingsParams.proposalSettings) {
|
|
359
|
-
if ((s.rewardsInfo.rewardToken === ZERO || s.rewardsInfo.rewardToken === "") &&
|
|
360
|
-
hasRewardAmounts(s.rewardsInfo)) {
|
|
361
|
-
s.rewardsInfo.rewardToken = predictedGovToken;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
// Always wire predicted distribution + tokenSale into additionalProposalExecutors
|
|
366
|
-
const effectiveExecutors = [...params.settingsParams.additionalProposalExecutors];
|
|
367
|
-
if (predictedDistribution && !effectiveExecutors.includes(predictedDistribution))
|
|
368
|
-
effectiveExecutors.push(predictedDistribution);
|
|
369
|
-
if (predictedTokenSale && !effectiveExecutors.includes(predictedTokenSale))
|
|
370
|
-
effectiveExecutors.push(predictedTokenSale);
|
|
371
|
-
// Validate: after wiring, at least one governance asset must be non-zero
|
|
372
|
-
if (effectiveTokenAddress === ZERO && params.userKeeperParams.nftAddress === ZERO) {
|
|
373
|
-
return errorResult("GovUK requires at least one governance asset: set userKeeperParams.tokenAddress (existing ERC20), userKeeperParams.nftAddress (existing ERC721), or provide tokenParams.name to create a new token");
|
|
374
|
-
}
|
|
375
|
-
// ---------- auto-expand proposal settings (1 → 5) ----------
|
|
376
|
-
let expandedSettings = params.settingsParams.proposalSettings;
|
|
377
|
-
if (expandedSettings.length === 1) {
|
|
378
|
-
const base = expandedSettings[0];
|
|
379
|
-
const baseWithValidatorFallback = {
|
|
380
|
-
...base,
|
|
381
|
-
validatorsVote: true,
|
|
382
|
-
durationValidators: hasValidators ? base.durationValidators : base.duration,
|
|
383
|
-
quorumValidators: hasValidators ? base.quorumValidators : base.quorum,
|
|
384
|
-
};
|
|
385
|
-
const dpSettings = {
|
|
386
|
-
...baseWithValidatorFallback,
|
|
387
|
-
delegatedVotingAllowed: false,
|
|
388
|
-
earlyCompletion: false,
|
|
389
|
-
};
|
|
390
|
-
expandedSettings = [
|
|
391
|
-
baseWithValidatorFallback, // [0] default
|
|
392
|
-
baseWithValidatorFallback, // [1] internal
|
|
393
|
-
baseWithValidatorFallback, // [2] validators
|
|
394
|
-
dpSettings, // [3] distributionProposal
|
|
395
|
-
baseWithValidatorFallback, // [4] tokenSale
|
|
396
|
-
];
|
|
397
|
-
}
|
|
398
|
-
else if (expandedSettings.length !== 5) {
|
|
399
|
-
return errorResult(`proposalSettings must be 1 (auto-expand to 5) or exactly 5. Got ${expandedSettings.length}.`);
|
|
400
|
-
}
|
|
401
|
-
// ---------- treasury-safety advisory: quorum floor (Layer 2) ----------
|
|
402
|
-
// Low quorum reduces the participation required to pass a proposal — a
|
|
403
|
-
// governance-safety risk for a DAO that will hold treasury assets. Flag it
|
|
404
|
-
// at birth so the operator sets an adequate quorum.
|
|
405
|
-
let quorumWarning = "";
|
|
406
|
-
if (ctx.config.treasuryGuard !== "off") {
|
|
407
|
-
const floor = ctx.config.minSafeQuorumPct;
|
|
408
|
-
const showPct = (p) => (Number.isFinite(p) ? `${p}%` : "unparseable");
|
|
409
|
-
const lowQuorum = [];
|
|
410
|
-
expandedSettings.forEach((s, i) => {
|
|
411
|
-
const pct = quorumPctFromRaw(s.quorum);
|
|
412
|
-
if (judgeQuorum(pct, floor) !== "SAFE") {
|
|
413
|
-
lowQuorum.push(`proposalSettings[${i}] quorum=${showPct(pct)}`);
|
|
414
|
-
}
|
|
415
|
-
});
|
|
416
|
-
const vpct = quorumPctFromRaw(validatorsParams.proposalSettings.quorum);
|
|
417
|
-
if (judgeQuorum(vpct, floor) !== "SAFE") {
|
|
418
|
-
lowQuorum.push(`validatorsParams quorum=${showPct(vpct)}`);
|
|
419
|
-
}
|
|
420
|
-
if (lowQuorum.length > 0) {
|
|
421
|
-
const detail = `Quorum below the ${floor}% safe floor (DEXE_MIN_SAFE_QUORUM_PCT): ${lowQuorum.join(", ")}. ` +
|
|
422
|
-
`Low quorum reduces the participation required to pass a proposal; for a DAO that will hold ` +
|
|
423
|
-
`treasury assets, set quorum ≥50% (51%+ recommended). The safe value is DAO-specific and must ` +
|
|
424
|
-
`be verified. [governance-safety advisory]`;
|
|
425
|
-
// Advisory only — never blocks the deploy. The durable control is an
|
|
426
|
-
// adequate on-chain quorum threshold configured per DAO.
|
|
427
|
-
quorumWarning = `\n⚠️ ${detail}`;
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
// ---------- auto-upload executorDescription to IPFS (when Pinata configured) ----------
|
|
431
|
-
// The frontend uploads each proposal settings object as JSON to IPFS and stores
|
|
432
|
-
// the CID as `executorDescription`. Without this, DAOs display broken metadata in
|
|
433
|
-
// the UI. We replicate the frontend's behavior: for each settings slot, if
|
|
434
|
-
// executorDescription is empty and Pinata is available, upload the settings JSON
|
|
435
|
-
// and set executorDescription = "ipfs://<cid>".
|
|
436
|
-
//
|
|
437
|
-
// The uploaded JSON shape matches the frontend exactly:
|
|
438
|
-
// { earlyCompletion, delegatedVotingAllowed, validatorsVote, duration, durationValidators,
|
|
439
|
-
// quorum, quorumValidators, minVotesForVoting, minVotesForCreating, executionDelay,
|
|
440
|
-
// rewardsInfo: { rewardToken, creationReward, executionReward, voteRewardsCoefficient } }
|
|
441
|
-
let pinataWarning = "";
|
|
442
|
-
if (ctx.config.pinataJwt) {
|
|
443
|
-
const pinata = new PinataClient(ctx.config.pinataJwt);
|
|
444
|
-
// We only auto-upload for default (index 0) and DP (index 3) settings,
|
|
445
|
-
// matching the frontend which shares the same CID across default/internal/validators/tokenSale
|
|
446
|
-
// and a separate CID for DP settings.
|
|
447
|
-
const settingsToUpload = [
|
|
448
|
-
{ index: 0, label: "default" },
|
|
449
|
-
{ index: 3, label: "distributionProposal" },
|
|
450
|
-
];
|
|
451
|
-
for (const { index, label } of settingsToUpload) {
|
|
452
|
-
const s = expandedSettings[index];
|
|
453
|
-
if (!s.executorDescription || s.executorDescription === "") {
|
|
454
|
-
try {
|
|
455
|
-
// Bug #13: Frontend includes minVotesForReadProposalDiscussion and
|
|
456
|
-
// minVotesForCreatingComment in the settings JSON uploaded to IPFS.
|
|
457
|
-
// Defaults from frontend: 0 (read) and 1e18 (comment = 1 token).
|
|
458
|
-
const settingsJson = {
|
|
459
|
-
earlyCompletion: s.earlyCompletion,
|
|
460
|
-
delegatedVotingAllowed: s.delegatedVotingAllowed,
|
|
461
|
-
validatorsVote: s.validatorsVote,
|
|
462
|
-
duration: s.duration,
|
|
463
|
-
durationValidators: s.durationValidators,
|
|
464
|
-
quorum: s.quorum,
|
|
465
|
-
quorumValidators: s.quorumValidators,
|
|
466
|
-
minVotesForVoting: s.minVotesForVoting,
|
|
467
|
-
minVotesForCreating: s.minVotesForCreating,
|
|
468
|
-
executionDelay: s.executionDelay,
|
|
469
|
-
rewardsInfo: {
|
|
470
|
-
rewardToken: s.rewardsInfo.rewardToken,
|
|
471
|
-
creationReward: s.rewardsInfo.creationReward,
|
|
472
|
-
executionReward: s.rewardsInfo.executionReward,
|
|
473
|
-
voteRewardsCoefficient: s.rewardsInfo.voteRewardsCoefficient,
|
|
474
|
-
},
|
|
475
|
-
minVotesForReadProposalDiscussion: "0",
|
|
476
|
-
minVotesForCreatingComment: "1000000000000000000",
|
|
477
|
-
};
|
|
478
|
-
const res = await pinata.pinJson(settingsJson, {
|
|
479
|
-
name: `dao-settings-${label}:${params.name.slice(0, 40)}`,
|
|
480
|
-
});
|
|
481
|
-
const cid = `ipfs://${res.cid}`;
|
|
482
|
-
// Apply the CID to the relevant slots (frontend shares CIDs across slots)
|
|
483
|
-
if (index === 0) {
|
|
484
|
-
// Default CID shared across: default(0), internal(1), validators(2), tokenSale(4)
|
|
485
|
-
expandedSettings[0] = { ...expandedSettings[0], executorDescription: cid };
|
|
486
|
-
expandedSettings[1] = { ...expandedSettings[1], executorDescription: cid };
|
|
487
|
-
expandedSettings[2] = { ...expandedSettings[2], executorDescription: cid };
|
|
488
|
-
expandedSettings[4] = { ...expandedSettings[4], executorDescription: cid };
|
|
489
|
-
}
|
|
490
|
-
else {
|
|
491
|
-
// DP settings get their own CID
|
|
492
|
-
expandedSettings[3] = { ...expandedSettings[3], executorDescription: cid };
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
catch (err) {
|
|
496
|
-
pinataWarning += `\n⚠️ Failed to upload ${label} executorDescription: ${err instanceof Error ? err.message : String(err)}`;
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
else {
|
|
502
|
-
// Check if any executorDescription is missing — warn about UI impact
|
|
503
|
-
const missingDesc = expandedSettings.some((s) => !s.executorDescription || s.executorDescription === "");
|
|
504
|
-
if (missingDesc) {
|
|
505
|
-
pinataWarning =
|
|
506
|
-
"\n⚠️ DEXE_PINATA_JWT not configured — executorDescription fields are empty. " +
|
|
507
|
-
"DAOs created without executorDescription will display broken proposal settings in the frontend UI. " +
|
|
508
|
-
"Set DEXE_PINATA_JWT to enable auto-upload.";
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
// ---------- build the tuple arg ----------
|
|
512
|
-
const paramsTuple = [
|
|
513
|
-
// settingsParams
|
|
514
|
-
[
|
|
515
|
-
expandedSettings.map((s) => [
|
|
516
|
-
s.earlyCompletion,
|
|
517
|
-
s.delegatedVotingAllowed,
|
|
518
|
-
s.validatorsVote,
|
|
519
|
-
BigInt(s.duration),
|
|
520
|
-
BigInt(s.durationValidators),
|
|
521
|
-
BigInt(s.executionDelay),
|
|
522
|
-
BigInt(s.quorum),
|
|
523
|
-
BigInt(s.quorumValidators),
|
|
524
|
-
BigInt(s.minVotesForVoting),
|
|
525
|
-
BigInt(s.minVotesForCreating),
|
|
526
|
-
[
|
|
527
|
-
s.rewardsInfo.rewardToken,
|
|
528
|
-
BigInt(s.rewardsInfo.creationReward),
|
|
529
|
-
BigInt(s.rewardsInfo.executionReward),
|
|
530
|
-
BigInt(s.rewardsInfo.voteRewardsCoefficient),
|
|
531
|
-
],
|
|
532
|
-
s.executorDescription,
|
|
533
|
-
]),
|
|
534
|
-
effectiveExecutors,
|
|
535
|
-
],
|
|
536
|
-
// validatorsParams
|
|
537
|
-
[
|
|
538
|
-
validatorsParams.name,
|
|
539
|
-
validatorsParams.symbol,
|
|
540
|
-
[
|
|
541
|
-
BigInt(validatorsParams.proposalSettings.duration),
|
|
542
|
-
BigInt(validatorsParams.proposalSettings.executionDelay),
|
|
543
|
-
BigInt(validatorsParams.proposalSettings.quorum),
|
|
544
|
-
],
|
|
545
|
-
validatorsParams.validators,
|
|
546
|
-
validatorsParams.balances.map((b) => BigInt(b)),
|
|
547
|
-
],
|
|
548
|
-
// userKeeperParams
|
|
549
|
-
[
|
|
550
|
-
effectiveTokenAddress,
|
|
551
|
-
params.userKeeperParams.nftAddress,
|
|
552
|
-
BigInt(params.userKeeperParams.individualPower),
|
|
553
|
-
BigInt(params.userKeeperParams.nftsTotalSupply),
|
|
554
|
-
],
|
|
555
|
-
// tokenParams
|
|
556
|
-
[
|
|
557
|
-
params.tokenParams.name,
|
|
558
|
-
params.tokenParams.symbol,
|
|
559
|
-
params.tokenParams.users,
|
|
560
|
-
BigInt(params.tokenParams.cap),
|
|
561
|
-
BigInt(params.tokenParams.mintedTotal),
|
|
562
|
-
params.tokenParams.amounts.map((a) => BigInt(a)),
|
|
563
|
-
],
|
|
564
|
-
// votePowerParams
|
|
565
|
-
[
|
|
566
|
-
VOTE_POWER_TYPES.indexOf(params.votePowerParams.voteType), // uint8 enum
|
|
567
|
-
votePowerInitData,
|
|
568
|
-
params.votePowerParams.presetAddress,
|
|
569
|
-
],
|
|
570
|
-
params.verifier,
|
|
571
|
-
params.onlyBABTHolders,
|
|
572
|
-
params.descriptionURL.replace(/^ipfs:\/\//, ""),
|
|
573
|
-
params.name,
|
|
574
|
-
];
|
|
575
|
-
// ---------- get ABI (artifact > fallback) ----------
|
|
576
|
-
let iface;
|
|
577
|
-
let ifaceSource;
|
|
578
|
-
try {
|
|
579
|
-
const records = ctx.artifacts.get("PoolFactory");
|
|
580
|
-
if (records.length > 0) {
|
|
581
|
-
iface = new Interface(records[0].abi);
|
|
582
|
-
ifaceSource = "compiled artifact";
|
|
583
|
-
// Sanity: ensure it has deployGovPool
|
|
584
|
-
try {
|
|
585
|
-
iface.getFunction("deployGovPool");
|
|
586
|
-
}
|
|
587
|
-
catch {
|
|
588
|
-
iface = new Interface(FALLBACK_POOL_FACTORY_ABI);
|
|
589
|
-
ifaceSource = "fallback (compiled artifact missing deployGovPool)";
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
else {
|
|
593
|
-
iface = new Interface(FALLBACK_POOL_FACTORY_ABI);
|
|
594
|
-
ifaceSource = "fallback (artifacts not loaded — run dexe_compile for strict parity)";
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
catch (err) {
|
|
598
|
-
if (err instanceof ArtifactsMissingError) {
|
|
599
|
-
iface = new Interface(FALLBACK_POOL_FACTORY_ABI);
|
|
600
|
-
ifaceSource = "fallback (no artifacts — run dexe_compile for strict parity)";
|
|
601
|
-
}
|
|
602
|
-
else {
|
|
603
|
-
return errorResult(`Failed to load PoolFactory ABI: ${err instanceof Error ? err.message : String(err)}`);
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
// ---------- encode ----------
|
|
607
|
-
let payload;
|
|
608
|
-
try {
|
|
609
|
-
payload = buildPayload({
|
|
610
|
-
to: factoryAddress,
|
|
611
|
-
iface,
|
|
612
|
-
method: "deployGovPool",
|
|
613
|
-
args: [paramsTuple],
|
|
614
|
-
chainId: chain.chainId,
|
|
615
|
-
contractLabel: "PoolFactory",
|
|
616
|
-
description: `PoolFactory.deployGovPool("${params.name}") via ${ifaceSource}`,
|
|
617
|
-
});
|
|
618
|
-
}
|
|
619
|
-
catch (err) {
|
|
620
|
-
return errorResult(`deployGovPool encoding failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
621
|
-
}
|
|
622
|
-
let note = ifaceSource.includes("fallback")
|
|
623
|
-
? "⚠️ Using fallback tuple ABI. For guaranteed parity, run dexe_compile to populate artifacts."
|
|
624
|
-
: "Encoded against compiled artifact — strict parity with deployed PoolFactory.";
|
|
625
|
-
if (pinataWarning)
|
|
626
|
-
note += pinataWarning;
|
|
627
|
-
if (quorumWarning)
|
|
628
|
-
note += quorumWarning;
|
|
629
|
-
return payloadResult(payload, { predictedGovPool, note });
|
|
597
|
+
const res = await buildDeployGovPool({ chainId, poolFactory, deployer, params }, ctx, rpc);
|
|
598
|
+
if (!res.ok)
|
|
599
|
+
return errorResult(res.error);
|
|
600
|
+
return payloadResult(res.payload, { predictedGovPool: res.predictedGovPool, note: res.note });
|
|
630
601
|
});
|
|
631
602
|
}
|
|
632
603
|
//# sourceMappingURL=daoDeploy.js.map
|