dexe-mcp 0.20.1 → 0.22.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 (111) hide show
  1. package/CHANGELOG.md +114 -0
  2. package/README.md +2 -1
  3. package/dexe-plugin/skills/dexe-create-dao/SKILL.md +8 -0
  4. package/dexe-plugin/skills/dexe-create-proposal/SKILL.md +47 -8
  5. package/dexe-plugin/skills/dexe-otc/SKILL.md +5 -0
  6. package/dexe-plugin/skills/dexe-vote-execute/SKILL.md +16 -3
  7. package/dist/config.d.ts +7 -0
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/config.js +49 -9
  10. package/dist/config.js.map +1 -1
  11. package/dist/env/schema.d.ts +27 -6
  12. package/dist/env/schema.d.ts.map +1 -1
  13. package/dist/env/schema.js +36 -6
  14. package/dist/env/schema.js.map +1 -1
  15. package/dist/index.js +35 -7
  16. package/dist/index.js.map +1 -1
  17. package/dist/lib/avatarUpload.d.ts +57 -0
  18. package/dist/lib/avatarUpload.d.ts.map +1 -0
  19. package/dist/lib/avatarUpload.js +95 -0
  20. package/dist/lib/avatarUpload.js.map +1 -0
  21. package/dist/lib/errors.d.ts +33 -0
  22. package/dist/lib/errors.d.ts.map +1 -0
  23. package/dist/lib/errors.js +65 -0
  24. package/dist/lib/errors.js.map +1 -0
  25. package/dist/lib/params.d.ts +8 -0
  26. package/dist/lib/params.d.ts.map +1 -0
  27. package/dist/lib/params.js +14 -0
  28. package/dist/lib/params.js.map +1 -0
  29. package/dist/lib/proposalBuilders.d.ts +30 -2
  30. package/dist/lib/proposalBuilders.d.ts.map +1 -1
  31. package/dist/lib/proposalBuilders.js +583 -17
  32. package/dist/lib/proposalBuilders.js.map +1 -1
  33. package/dist/lib/requireEnv.d.ts.map +1 -1
  34. package/dist/lib/requireEnv.js +7 -4
  35. package/dist/lib/requireEnv.js.map +1 -1
  36. package/dist/lib/signer.d.ts.map +1 -1
  37. package/dist/lib/signer.js +7 -2
  38. package/dist/lib/signer.js.map +1 -1
  39. package/dist/lib/txWait.d.ts +28 -0
  40. package/dist/lib/txWait.d.ts.map +1 -0
  41. package/dist/lib/txWait.js +63 -0
  42. package/dist/lib/txWait.js.map +1 -0
  43. package/dist/lib/units.d.ts +32 -0
  44. package/dist/lib/units.d.ts.map +1 -0
  45. package/dist/lib/units.js +64 -0
  46. package/dist/lib/units.js.map +1 -0
  47. package/dist/rpc.d.ts +29 -1
  48. package/dist/rpc.d.ts.map +1 -1
  49. package/dist/rpc.js +86 -22
  50. package/dist/rpc.js.map +1 -1
  51. package/dist/tools/dao.d.ts.map +1 -1
  52. package/dist/tools/dao.js +16 -12
  53. package/dist/tools/dao.js.map +1 -1
  54. package/dist/tools/daoCreate.d.ts.map +1 -1
  55. package/dist/tools/daoCreate.js +37 -7
  56. package/dist/tools/daoCreate.js.map +1 -1
  57. package/dist/tools/flow.d.ts +32 -1
  58. package/dist/tools/flow.d.ts.map +1 -1
  59. package/dist/tools/flow.js +405 -120
  60. package/dist/tools/flow.js.map +1 -1
  61. package/dist/tools/gate.js +2 -2
  62. package/dist/tools/gov.d.ts.map +1 -1
  63. package/dist/tools/gov.js +17 -4
  64. package/dist/tools/gov.js.map +1 -1
  65. package/dist/tools/inbox.d.ts.map +1 -1
  66. package/dist/tools/inbox.js +4 -2
  67. package/dist/tools/inbox.js.map +1 -1
  68. package/dist/tools/ipfs.d.ts.map +1 -1
  69. package/dist/tools/ipfs.js +43 -47
  70. package/dist/tools/ipfs.js.map +1 -1
  71. package/dist/tools/operationalContext.d.ts.map +1 -1
  72. package/dist/tools/operationalContext.js +39 -5
  73. package/dist/tools/operationalContext.js.map +1 -1
  74. package/dist/tools/otc.d.ts.map +1 -1
  75. package/dist/tools/otc.js +53 -13
  76. package/dist/tools/otc.js.map +1 -1
  77. package/dist/tools/predict.d.ts.map +1 -1
  78. package/dist/tools/predict.js +7 -4
  79. package/dist/tools/predict.js.map +1 -1
  80. package/dist/tools/proposal.d.ts.map +1 -1
  81. package/dist/tools/proposal.js +8 -4
  82. package/dist/tools/proposal.js.map +1 -1
  83. package/dist/tools/proposalBuild.d.ts +1 -0
  84. package/dist/tools/proposalBuild.d.ts.map +1 -1
  85. package/dist/tools/proposalBuild.js +2 -1
  86. package/dist/tools/proposalBuild.js.map +1 -1
  87. package/dist/tools/proposalBuildComplex.d.ts +7 -0
  88. package/dist/tools/proposalBuildComplex.d.ts.map +1 -1
  89. package/dist/tools/proposalBuildComplex.js +7 -7
  90. package/dist/tools/proposalBuildComplex.js.map +1 -1
  91. package/dist/tools/proposalBuildInternal.d.ts +21 -0
  92. package/dist/tools/proposalBuildInternal.d.ts.map +1 -1
  93. package/dist/tools/proposalBuildInternal.js +1 -1
  94. package/dist/tools/proposalBuildInternal.js.map +1 -1
  95. package/dist/tools/proposalBuildMore.d.ts +2 -0
  96. package/dist/tools/proposalBuildMore.d.ts.map +1 -1
  97. package/dist/tools/proposalBuildMore.js +2 -2
  98. package/dist/tools/proposalBuildMore.js.map +1 -1
  99. package/dist/tools/read.d.ts.map +1 -1
  100. package/dist/tools/read.js +25 -16
  101. package/dist/tools/read.js.map +1 -1
  102. package/dist/tools/subgraph.d.ts.map +1 -1
  103. package/dist/tools/subgraph.js +5 -3
  104. package/dist/tools/subgraph.js.map +1 -1
  105. package/dist/tools/txSend.d.ts.map +1 -1
  106. package/dist/tools/txSend.js +46 -6
  107. package/dist/tools/txSend.js.map +1 -1
  108. package/dist/tools/vote.d.ts.map +1 -1
  109. package/dist/tools/vote.js +7 -4
  110. package/dist/tools/vote.js.map +1 -1
  111. package/package.json +1 -1
@@ -15,11 +15,40 @@
15
15
  * shape actions + metadata for the flow to deposit/create/vote.
16
16
  */
17
17
  import { z } from "zod";
18
- import { Interface, isAddress, ZeroAddress } from "ethers";
18
+ import { Interface, isAddress, getAddress, ZeroAddress } from "ethers";
19
19
  import { checkBlacklist, blacklistError } from "./blacklist.js";
20
20
  import { findForbiddenSelector, dangerousSelectorError } from "./dangerousSelectors.js";
21
- import { ProposalSettingsSchema, toSettingsTuple, } from "../tools/proposalBuildMore.js";
22
- import { buildTokenSaleMultiActions, tierSchema, } from "../tools/proposalBuildComplex.js";
21
+ import { ProposalSettingsSchema, toSettingsTuple, GOV_VALIDATORS_ABI, GOV_POOL_TREASURY_ABI, } from "../tools/proposalBuildMore.js";
22
+ import { buildTokenSaleMultiActions, tierSchema, TOKEN_SALE_PROPOSAL_ABI, STAKING_PROPOSAL_ABI, GOV_POOL_EXT_ABI, ERC20_GOV_ABI as ERC20_GOV_FULL_ABI, ERC721_MULTIPLIER_ABI, ERC721_MULTIPLIER_PRECISION, UINT64_MAX, GOV_SETTINGS_FULL_ABI, } from "../tools/proposalBuildComplex.js";
23
+ import { VALIDATORS_EXEC_ABI } from "../tools/proposalBuildInternal.js";
24
+ import { parseUintString } from "./amount.js";
25
+ import { parseAmount } from "./units.js";
26
+ import { RpcProvider } from "../rpc.js";
27
+ import { multicall } from "./multicall.js";
28
+ const DECIMALS_IFACE = new Interface(["function decimals() view returns (uint8)"]);
29
+ /**
30
+ * A8 — amount resolution for the money-mover builders. Digits-only strings are
31
+ * raw smallest units (unchanged back-compat). A decimal string ("12.5") is
32
+ * human units: the token's real decimals are read on-chain and the value is
33
+ * scaled. Needs an RPC only for the human form.
34
+ */
35
+ async function resolveTokenAmount(amount, token, deps, opts) {
36
+ const s = amount.trim();
37
+ if (/^\d+$/.test(s))
38
+ return BigInt(s);
39
+ if (opts?.isNative)
40
+ return parseAmount(s, 18);
41
+ const pr = new RpcProvider(deps.ctx.config).tryProvider(deps.chainId);
42
+ if ("error" in pr) {
43
+ throw new Error(`Amount '${amount}' is in human units, which needs an RPC to read the token's decimals — and no RPC is ` +
44
+ `configured for chain ${deps.chainId}. Either pass the raw smallest-unit amount (digits only) or configure an RPC. ${pr.remediation}`);
45
+ }
46
+ const res = await multicall(pr.ok, [
47
+ { target: token, iface: DECIMALS_IFACE, method: "decimals", args: [], allowFailure: true },
48
+ ]);
49
+ const decimals = res[0].success ? Number(res[0].value) : 18;
50
+ return parseAmount(s, decimals);
51
+ }
23
52
  // ---------- ABI fragments (identical strings to the build tools) ----------
24
53
  const ERC20_TRANSFER_ABI = new Interface([
25
54
  "function transfer(address to, uint256 amount) returns (bool)",
@@ -47,19 +76,23 @@ const tokenTransferBuilder = {
47
76
  schema: z.object({
48
77
  token: z.string().default("").describe("ERC20 token contract (ignored when isNative=true)"),
49
78
  recipient: z.string().describe("Recipient address"),
50
- amount: z.string().describe("Amount in wei / smallest unit"),
79
+ amount: z
80
+ .string()
81
+ .describe("Amount: raw smallest units (digits-only) OR human units with a decimal point ('12.5', scaled by the token's real decimals)"),
51
82
  isNative: z.boolean().default(false).describe("True for native BNB/ETH transfer"),
52
83
  }),
53
- async build(raw, { ctx }) {
84
+ async build(raw, deps) {
85
+ const { ctx } = deps;
54
86
  const p = raw;
55
87
  if (!isAddress(p.recipient))
56
88
  throw new Error(`Invalid recipient: ${p.recipient}`);
57
89
  if (p.isNative) {
90
+ const amt = (await resolveTokenAmount(p.amount, "", deps, { isNative: true })).toString();
58
91
  return {
59
- actionsOnFor: [{ executor: p.recipient, value: p.amount, data: "0x" }],
92
+ actionsOnFor: [{ executor: p.recipient, value: amt, data: "0x" }],
60
93
  category: "tokenTransfer",
61
- metadataExtra: changes({ data: [{ tokenAmount: p.amount, receiverAddress: p.recipient }], tokenAddress: ZeroAddress }),
62
- summary: `Native transfer → ${p.recipient} (${p.amount} wei)`,
94
+ metadataExtra: changes({ data: [{ tokenAmount: amt, receiverAddress: p.recipient }], tokenAddress: ZeroAddress }),
95
+ summary: `Native transfer → ${p.recipient} (${amt} wei)`,
63
96
  };
64
97
  }
65
98
  if (!isAddress(p.token))
@@ -67,12 +100,13 @@ const tokenTransferBuilder = {
67
100
  const bl = await checkBlacklist(ctx.config, p.token, p.recipient);
68
101
  if (bl.status === "blacklisted")
69
102
  throw new Error(blacklistError(p.token, p.recipient));
70
- const data = ERC20_TRANSFER_ABI.encodeFunctionData("transfer", [p.recipient, BigInt(p.amount)]);
103
+ const amount = await resolveTokenAmount(p.amount, p.token, deps);
104
+ const data = ERC20_TRANSFER_ABI.encodeFunctionData("transfer", [p.recipient, amount]);
71
105
  return {
72
106
  actionsOnFor: [{ executor: p.token, value: "0", data }],
73
107
  category: "tokenTransfer",
74
- metadataExtra: changes({ data: [{ tokenAmount: p.amount, receiverAddress: p.recipient }], tokenAddress: p.token }),
75
- summary: `ERC20(${p.token}).transfer(${p.recipient}, ${p.amount})`,
108
+ metadataExtra: changes({ data: [{ tokenAmount: amount.toString(), receiverAddress: p.recipient }], tokenAddress: p.token }),
109
+ summary: `ERC20(${p.token}).transfer(${p.recipient}, ${amount})`,
76
110
  };
77
111
  },
78
112
  };
@@ -80,15 +114,20 @@ const withdrawTreasuryBuilder = {
80
114
  schema: z.object({
81
115
  receiver: z.string().describe("Treasury withdrawal recipient"),
82
116
  token: z.string().default("").describe("ERC20 token contract (omit for NFT-only)"),
83
- amount: z.string().default("0").describe("ERC20 amount in wei (omit/0 for NFT-only)"),
117
+ amount: z
118
+ .string()
119
+ .default("0")
120
+ .describe("ERC20 amount: raw smallest units (digits-only) or human units ('12.5'). Omit/0 for NFT-only."),
84
121
  nftAddress: z.string().default("").describe("ERC721 contract (omit for token-only)"),
85
122
  nftIds: z.array(z.string()).default([]).describe("NFT ids; one transferFrom per id"),
86
123
  }),
87
- async build(raw, { ctx, govPool }) {
124
+ async build(raw, deps) {
125
+ const { ctx, govPool } = deps;
88
126
  const p = raw;
89
127
  if (!isAddress(p.receiver))
90
128
  throw new Error(`Invalid receiver: ${p.receiver}`);
91
- const wantToken = p.token.length > 0 && BigInt(p.amount) > 0n;
129
+ const rawAmount = p.token.length > 0 && isAddress(p.token) ? await resolveTokenAmount(p.amount, p.token, deps) : BigInt(/^\d+$/.test(p.amount) ? p.amount : "0");
130
+ const wantToken = p.token.length > 0 && rawAmount > 0n;
92
131
  const wantNfts = p.nftAddress.length > 0 && p.nftIds.length > 0;
93
132
  if (!wantToken && !wantNfts) {
94
133
  throw new Error("Nothing to withdraw — supply `token` + non-zero `amount`, and/or `nftAddress` + `nftIds`.");
@@ -108,7 +147,7 @@ const withdrawTreasuryBuilder = {
108
147
  actionsOnFor.push({
109
148
  executor: p.token,
110
149
  value: "0",
111
- data: ERC20_TRANSFER_ABI.encodeFunctionData("transfer", [p.receiver, BigInt(p.amount)]),
150
+ data: ERC20_TRANSFER_ABI.encodeFunctionData("transfer", [p.receiver, rawAmount]),
112
151
  });
113
152
  }
114
153
  if (wantNfts) {
@@ -123,7 +162,13 @@ const withdrawTreasuryBuilder = {
123
162
  return {
124
163
  actionsOnFor,
125
164
  category: "withdrawDeposit",
126
- metadataExtra: changes({ receiver: p.receiver, token: p.token, amount: p.amount, nftAddress: p.nftAddress, nftIds: p.nftIds }),
165
+ metadataExtra: changes({
166
+ receiver: p.receiver,
167
+ token: p.token,
168
+ amount: rawAmount.toString(),
169
+ nftAddress: p.nftAddress,
170
+ nftIds: p.nftIds,
171
+ }),
127
172
  summary: `Withdraw treasury → ${p.receiver} (${actionsOnFor.length} action${actionsOnFor.length === 1 ? "" : "s"})`,
128
173
  };
129
174
  },
@@ -288,9 +333,407 @@ const customAbiBuilder = {
288
333
  };
289
334
  },
290
335
  };
336
+ // ---------- v0.22: the remaining external catalog types ----------
337
+ const manageValidatorsBuilder = {
338
+ schema: z.object({
339
+ govValidators: z.string().describe("GovValidators address (dexe_dao_info.helpers.validators)"),
340
+ changes: z.array(z.object({ user: z.string(), balance: z.string().describe("Wei; 0 to remove") })).min(1),
341
+ }),
342
+ async build(raw) {
343
+ const p = raw;
344
+ if (!isAddress(p.govValidators))
345
+ throw new Error(`Invalid govValidators: ${p.govValidators}`);
346
+ for (const c of p.changes) {
347
+ if (!isAddress(c.user))
348
+ throw new Error(`Invalid validator user: ${c.user}`);
349
+ }
350
+ const iface = new Interface(GOV_VALIDATORS_ABI);
351
+ const data = iface.encodeFunctionData("changeBalances", [
352
+ p.changes.map((c) => BigInt(c.balance)),
353
+ p.changes.map((c) => c.user),
354
+ ]);
355
+ return {
356
+ actionsOnFor: [{ executor: p.govValidators, value: "0", data }],
357
+ category: "changeValidators",
358
+ metadataExtra: changes({ validators: p.changes }),
359
+ summary: `Manage validators (${p.changes.length} changes)`,
360
+ };
361
+ },
362
+ };
363
+ const delegateToExpertBuilder = {
364
+ schema: z.object({
365
+ expert: z.string().describe("Expert address receiving the treasury delegation"),
366
+ amount: z.string().describe("Token amount in wei"),
367
+ nftIds: z.array(z.string()).default([]),
368
+ value: z.string().default("0").describe("Native coin value for payable path"),
369
+ }),
370
+ async build(raw, { govPool }) {
371
+ const p = raw;
372
+ if (!isAddress(p.expert))
373
+ throw new Error(`Invalid expert: ${p.expert}`);
374
+ const iface = new Interface(GOV_POOL_TREASURY_ABI);
375
+ const data = iface.encodeFunctionData("delegateTreasury", [
376
+ p.expert,
377
+ BigInt(p.amount),
378
+ p.nftIds.map((n) => BigInt(n)),
379
+ ]);
380
+ return {
381
+ actionsOnFor: [{ executor: govPool, value: p.value, data }],
382
+ category: "delegateTokensToExpert",
383
+ metadataExtra: changes({ expert: p.expert, amount: p.amount, nftIds: p.nftIds, value: p.value }),
384
+ summary: `Delegate treasury → ${p.expert} (${p.amount} wei, ${p.nftIds.length} NFTs)`,
385
+ };
386
+ },
387
+ };
388
+ const revokeFromExpertBuilder = {
389
+ schema: z.object({
390
+ expert: z.string(),
391
+ amount: z.string(),
392
+ nftIds: z.array(z.string()).default([]),
393
+ }),
394
+ async build(raw, { govPool }) {
395
+ const p = raw;
396
+ if (!isAddress(p.expert))
397
+ throw new Error(`Invalid expert: ${p.expert}`);
398
+ const iface = new Interface(GOV_POOL_TREASURY_ABI);
399
+ const data = iface.encodeFunctionData("undelegateTreasury", [
400
+ p.expert,
401
+ BigInt(p.amount),
402
+ p.nftIds.map((n) => BigInt(n)),
403
+ ]);
404
+ return {
405
+ actionsOnFor: [{ executor: govPool, value: "0", data }],
406
+ category: "revokeTokensFromExpert",
407
+ metadataExtra: changes({ expert: p.expert, amount: p.amount, nftIds: p.nftIds }),
408
+ summary: `Revoke treasury delegation ← ${p.expert}`,
409
+ };
410
+ },
411
+ };
412
+ const tokenSaleRecoverBuilder = {
413
+ schema: z.object({
414
+ tokenSaleProposal: z.string(),
415
+ tierIds: z.array(z.string()).min(1),
416
+ }),
417
+ async build(raw) {
418
+ const p = raw;
419
+ if (!isAddress(p.tokenSaleProposal))
420
+ throw new Error(`Invalid tokenSaleProposal: ${p.tokenSaleProposal}`);
421
+ const iface = new Interface(TOKEN_SALE_PROPOSAL_ABI);
422
+ const data = iface.encodeFunctionData("recover", [p.tierIds.map((n) => BigInt(n))]);
423
+ return {
424
+ actionsOnFor: [{ executor: p.tokenSaleProposal, value: "0", data }],
425
+ category: "recoverTokenSale",
426
+ metadataExtra: changes({ tierIds: p.tierIds }),
427
+ summary: `Recover unsold tokens from tiers [${p.tierIds.join(", ")}]`,
428
+ };
429
+ },
430
+ };
431
+ const tokenSaleWhitelistBuilder = {
432
+ schema: z.object({
433
+ tokenSaleProposal: z.string(),
434
+ requests: z
435
+ .array(z.object({ tierId: z.string(), users: z.array(z.string()).min(1), uri: z.string().default("") }))
436
+ .min(1),
437
+ }),
438
+ async build(raw) {
439
+ const p = raw;
440
+ if (!isAddress(p.tokenSaleProposal))
441
+ throw new Error(`Invalid tokenSaleProposal: ${p.tokenSaleProposal}`);
442
+ const normalised = p.requests.map((r) => {
443
+ for (const u of r.users) {
444
+ if (!isAddress(u))
445
+ throw new Error(`Invalid whitelist user: ${u}`);
446
+ }
447
+ return [BigInt(r.tierId), r.users.map((u) => getAddress(u)), r.uri ?? ""];
448
+ });
449
+ const iface = new Interface(TOKEN_SALE_PROPOSAL_ABI);
450
+ const data = iface.encodeFunctionData("addToWhitelist", [normalised]);
451
+ return {
452
+ actionsOnFor: [{ executor: p.tokenSaleProposal, value: "0", data }],
453
+ category: "tokenSale",
454
+ metadataExtra: changes({ requests: p.requests }),
455
+ summary: `addToWhitelist (${p.requests.length} request${p.requests.length === 1 ? "" : "s"})`,
456
+ };
457
+ },
458
+ };
459
+ const createStakingTierBuilder = {
460
+ schema: z.object({
461
+ stakingProposal: z.string().describe("StakingProposal contract address"),
462
+ rewardToken: z.string(),
463
+ rewardAmount: z.string(),
464
+ startedAt: z.string().describe("Unix seconds"),
465
+ deadline: z.string().describe("Unix seconds"),
466
+ stakingMetadataUrl: z.string().describe("ipfs://<cid> of staking-specific metadata"),
467
+ isNative: z.boolean().default(false),
468
+ }),
469
+ async build(raw) {
470
+ const p = raw;
471
+ if (!isAddress(p.stakingProposal))
472
+ throw new Error(`Invalid stakingProposal: ${p.stakingProposal}`);
473
+ if (!isAddress(p.rewardToken))
474
+ throw new Error(`Invalid rewardToken: ${p.rewardToken}`);
475
+ const iface = new Interface(STAKING_PROPOSAL_ABI);
476
+ const createData = iface.encodeFunctionData("createStaking", [
477
+ p.rewardToken, BigInt(p.rewardAmount), BigInt(p.startedAt), BigInt(p.deadline), p.stakingMetadataUrl,
478
+ ]);
479
+ const actionsOnFor = [];
480
+ if (p.isNative) {
481
+ actionsOnFor.push({ executor: p.stakingProposal, value: p.rewardAmount, data: createData });
482
+ }
483
+ else {
484
+ const erc20 = new Interface(ERC20_GOV_FULL_ABI);
485
+ actionsOnFor.push({
486
+ executor: p.rewardToken,
487
+ value: "0",
488
+ data: erc20.encodeFunctionData("approve", [p.stakingProposal, BigInt(p.rewardAmount)]),
489
+ });
490
+ actionsOnFor.push({ executor: p.stakingProposal, value: "0", data: createData });
491
+ }
492
+ return {
493
+ actionsOnFor,
494
+ category: "createStakingTier",
495
+ metadataExtra: changes({
496
+ rewardToken: p.rewardToken, rewardAmount: p.rewardAmount,
497
+ startedAt: p.startedAt, deadline: p.deadline, metadata: p.stakingMetadataUrl,
498
+ }),
499
+ summary: `Create staking → ${p.rewardAmount} of ${p.rewardToken}`,
500
+ };
501
+ },
502
+ };
503
+ const changeMathModelBuilder = {
504
+ schema: z.object({
505
+ newVotePower: z.string().describe("Deployed vote-power contract (LINEAR_POWER / POLYNOMIAL_POWER / custom)"),
506
+ }),
507
+ async build(raw, { govPool }) {
508
+ const p = raw;
509
+ if (!isAddress(p.newVotePower))
510
+ throw new Error(`Invalid newVotePower: ${p.newVotePower}`);
511
+ const iface = new Interface(GOV_POOL_EXT_ABI);
512
+ const data = iface.encodeFunctionData("changeVotePower", [p.newVotePower]);
513
+ return {
514
+ actionsOnFor: [{ executor: govPool, value: "0", data }],
515
+ category: "mathModel",
516
+ metadataExtra: changes({ newVotePower: p.newVotePower }),
517
+ summary: `Change vote power → ${p.newVotePower}`,
518
+ };
519
+ },
520
+ };
521
+ const blacklistBuilder = {
522
+ schema: z.object({
523
+ erc20Gov: z.string().describe("DAO ERC20Gov token contract"),
524
+ addAddresses: z.array(z.string()).default([]),
525
+ removeAddresses: z.array(z.string()).default([]),
526
+ }),
527
+ async build(raw) {
528
+ const p = raw;
529
+ if (!isAddress(p.erc20Gov))
530
+ throw new Error(`Invalid erc20Gov: ${p.erc20Gov}`);
531
+ for (const a of [...p.addAddresses, ...p.removeAddresses]) {
532
+ if (!isAddress(a))
533
+ throw new Error(`Invalid blacklist address: ${a}`);
534
+ }
535
+ if (p.addAddresses.length === 0 && p.removeAddresses.length === 0) {
536
+ throw new Error("Must supply at least one address to add or remove");
537
+ }
538
+ const iface = new Interface(ERC20_GOV_FULL_ABI);
539
+ const actionsOnFor = [];
540
+ if (p.addAddresses.length) {
541
+ actionsOnFor.push({
542
+ executor: p.erc20Gov, value: "0",
543
+ data: iface.encodeFunctionData("blacklist", [p.addAddresses, true]),
544
+ });
545
+ }
546
+ if (p.removeAddresses.length) {
547
+ actionsOnFor.push({
548
+ executor: p.erc20Gov, value: "0",
549
+ data: iface.encodeFunctionData("blacklist", [p.removeAddresses, false]),
550
+ });
551
+ }
552
+ return {
553
+ actionsOnFor,
554
+ category: "blacklistManagement",
555
+ metadataExtra: changes({ addBlacklist: p.addAddresses, removeBlacklist: p.removeAddresses }),
556
+ summary: `Blacklist: +${p.addAddresses.length} / -${p.removeAddresses.length}`,
557
+ };
558
+ },
559
+ };
560
+ const rewardMultiplierBuilder = {
561
+ schema: z.object({
562
+ mode: z.enum(["set_address", "set_token_uri", "mint", "change_token"]),
563
+ nftMultiplierContract: z.string().optional(),
564
+ newMultiplierAddress: z.string().optional().describe("For mode=set_address (omit/zero to disable)"),
565
+ tokenId: z.string().optional(),
566
+ uri: z.string().optional(),
567
+ to: z.string().optional().describe("For mode=mint"),
568
+ multiplier: z.string().optional().describe("Scaled by PRECISION=1e25 (1.5x => 15000000000000000000000000)"),
569
+ rewardPeriod: z.string().default("0").describe("Lock duration in SECONDS (uint64)"),
570
+ metadataUrl: z.string().default(""),
571
+ }),
572
+ async build(raw, { govPool }) {
573
+ const p = raw;
574
+ const actionsOnFor = [];
575
+ if (p.mode === "set_address") {
576
+ const addr = p.newMultiplierAddress ?? ZeroAddress;
577
+ if (!isAddress(addr))
578
+ throw new Error(`Invalid newMultiplierAddress: ${addr}`);
579
+ const iface = new Interface(GOV_POOL_EXT_ABI);
580
+ actionsOnFor.push({
581
+ executor: govPool, value: "0",
582
+ data: iface.encodeFunctionData("setNftMultiplierAddress", [addr]),
583
+ });
584
+ }
585
+ else {
586
+ if (!p.nftMultiplierContract || !isAddress(p.nftMultiplierContract)) {
587
+ throw new Error(`${p.mode} requires valid nftMultiplierContract`);
588
+ }
589
+ const iface = new Interface(ERC721_MULTIPLIER_ABI);
590
+ if (p.mode === "set_token_uri") {
591
+ if (!p.tokenId)
592
+ throw new Error("set_token_uri requires tokenId");
593
+ if (p.uri === undefined)
594
+ throw new Error("set_token_uri requires uri");
595
+ actionsOnFor.push({
596
+ executor: p.nftMultiplierContract, value: "0",
597
+ data: iface.encodeFunctionData("setTokenURI", [BigInt(p.tokenId), p.uri]),
598
+ });
599
+ }
600
+ else {
601
+ if (!p.multiplier)
602
+ throw new Error(`${p.mode} requires multiplier`);
603
+ const multiplierBn = BigInt(p.multiplier);
604
+ if (multiplierBn === 0n)
605
+ throw new Error(`${p.mode}: multiplier=0 is meaningless — pass 1.5e25 for 1.5x (PRECISION=1e25).`);
606
+ if (multiplierBn < ERC721_MULTIPLIER_PRECISION / 100n) {
607
+ throw new Error(`${p.mode}: multiplier ${multiplierBn} is suspiciously small — values are scaled by PRECISION=1e25 (1.5x => 1.5e25). Did you forget the scale?`);
608
+ }
609
+ const durationBn = BigInt(p.rewardPeriod ?? "0");
610
+ if (durationBn > UINT64_MAX)
611
+ throw new Error(`${p.mode}: rewardPeriod ${durationBn} > uint64 max ${UINT64_MAX}.`);
612
+ if (p.mode === "change_token") {
613
+ if (!p.tokenId)
614
+ throw new Error("change_token requires tokenId");
615
+ actionsOnFor.push({
616
+ executor: p.nftMultiplierContract, value: "0",
617
+ data: iface.encodeFunctionData("changeToken", [BigInt(p.tokenId), multiplierBn, durationBn]),
618
+ });
619
+ }
620
+ else {
621
+ if (!p.to || !isAddress(p.to))
622
+ throw new Error("mint requires valid to");
623
+ if (durationBn === 0n)
624
+ throw new Error("mint: rewardPeriod must be > 0 seconds (lock duration).");
625
+ actionsOnFor.push({
626
+ executor: p.nftMultiplierContract, value: "0",
627
+ data: iface.encodeFunctionData("mint", [p.to, multiplierBn, durationBn, p.metadataUrl ?? ""]),
628
+ });
629
+ }
630
+ }
631
+ }
632
+ return {
633
+ actionsOnFor,
634
+ category: "rewardMultiplier",
635
+ metadataExtra: changes({ ...p }),
636
+ summary: `Reward multiplier (${p.mode})`,
637
+ };
638
+ },
639
+ };
640
+ const applyToDaoBuilder = {
641
+ schema: z.object({
642
+ token: z.string().describe("The DAO token contract (ERC20 or ERC20Gov)"),
643
+ receiver: z.string(),
644
+ amount: z.string().describe("Total amount to grant: raw smallest units (digits-only) or human units ('12.5')"),
645
+ treasuryBalance: z
646
+ .string()
647
+ .default("0")
648
+ .describe("Current treasury balance of `token` in RAW smallest units (dexe_read_treasury). If >= amount a single transfer is used, else transfer + mint shortfall."),
649
+ }),
650
+ async build(raw, deps) {
651
+ const { ctx } = deps;
652
+ const p = raw;
653
+ if (!isAddress(p.token))
654
+ throw new Error(`Invalid token: ${p.token}`);
655
+ if (!isAddress(p.receiver))
656
+ throw new Error(`Invalid receiver: ${p.receiver}`);
657
+ const bl = await checkBlacklist(ctx.config, p.token, p.receiver);
658
+ if (bl.status === "blacklisted")
659
+ throw new Error(blacklistError(p.token, p.receiver));
660
+ const iface = new Interface(ERC20_GOV_FULL_ABI);
661
+ const actionsOnFor = [];
662
+ const total = await resolveTokenAmount(p.amount, p.token, deps);
663
+ const have = parseUintString(p.treasuryBalance, "treasuryBalance");
664
+ if (have >= total) {
665
+ actionsOnFor.push({ executor: p.token, value: "0", data: iface.encodeFunctionData("transfer", [p.receiver, total]) });
666
+ }
667
+ else {
668
+ // H-4: transfer only what the treasury holds, then mint the shortfall.
669
+ if (have > 0n) {
670
+ actionsOnFor.push({ executor: p.token, value: "0", data: iface.encodeFunctionData("transfer", [p.receiver, have]) });
671
+ }
672
+ actionsOnFor.push({ executor: p.token, value: "0", data: iface.encodeFunctionData("mint", [p.receiver, total - have]) });
673
+ }
674
+ return {
675
+ actionsOnFor,
676
+ category: "applyToDao",
677
+ metadataExtra: {
678
+ changes: {
679
+ proposedChanges: { receiver: p.receiver, tokenAmount: total.toString(), tokenAddress: p.token },
680
+ currentChanges: { treasuryBalance: p.treasuryBalance },
681
+ },
682
+ },
683
+ summary: `Apply to DAO: ${total} of ${p.token} → ${p.receiver}`,
684
+ };
685
+ },
686
+ };
687
+ /**
688
+ * Fixed-scope aliases so the catalog-style names (external.add_local_expert,
689
+ * external.remove_global_expert, …) work as proposalType values directly —
690
+ * the scope param disappears because the name already carries it.
691
+ */
692
+ function scopedExpertAlias(base, scope) {
693
+ const baseSchema = base.schema;
694
+ return {
695
+ schema: baseSchema.omit({ scope: true }),
696
+ build: (params, deps) => base.build({ ...params, scope }, deps),
697
+ };
698
+ }
699
+ const newProposalTypeBuilder = {
700
+ schema: z.object({
701
+ govSettings: z.string().describe("GovSettings address (dexe_dao_info.helpers.settings)"),
702
+ settings: ProposalSettingsSchema,
703
+ executors: z.array(z.string()).min(1),
704
+ newSettingId: z.string().describe("Id the new setting receives (= current getSettingsLength(); read via dexe_read_settings)"),
705
+ }),
706
+ async build(raw) {
707
+ const p = raw;
708
+ if (!isAddress(p.govSettings))
709
+ throw new Error(`Invalid govSettings: ${p.govSettings}`);
710
+ for (const e of p.executors) {
711
+ if (!isAddress(e))
712
+ throw new Error(`Invalid executor: ${e}`);
713
+ }
714
+ const iface = new Interface(GOV_SETTINGS_FULL_ABI);
715
+ const addData = iface.encodeFunctionData("addSettings", [[toSettingsTuple(p.settings)]]);
716
+ const changeData = iface.encodeFunctionData("changeExecutors", [
717
+ p.executors,
718
+ p.executors.map(() => BigInt(p.newSettingId)),
719
+ ]);
720
+ return {
721
+ actionsOnFor: [
722
+ { executor: p.govSettings, value: "0", data: addData },
723
+ { executor: p.govSettings, value: "0", data: changeData },
724
+ ],
725
+ category: "createProposalType",
726
+ metadataExtra: changes({ settings: p.settings, executors: p.executors, newSettingId: p.newSettingId }),
727
+ summary: `New proposal type (settingsId=${p.newSettingId}, ${p.executors.length} executors)`,
728
+ };
729
+ },
730
+ };
291
731
  /**
292
732
  * Registry keyed by the short `proposalType` accepted by `dexe_proposal_create`.
293
- * Extend this to wire another catalog type into the composite.
733
+ * Extend this to wire another catalog type into the composite. Aliases point at
734
+ * the same builder object (validators_allocation ≡ manage_validators;
735
+ * enable_staking ≡ new_proposal_type with StakingProposal among executors —
736
+ * matching the frontend, which reuses useGovPoolCreateProposalType).
294
737
  */
295
738
  export const PROPOSAL_BUILDERS = {
296
739
  token_transfer: tokenTransferBuilder,
@@ -301,11 +744,134 @@ export const PROPOSAL_BUILDERS = {
301
744
  token_distribution: tokenDistributionBuilder,
302
745
  token_sale: tokenSaleBuilder,
303
746
  custom_abi: customAbiBuilder,
747
+ // v0.22 — full catalog coverage
748
+ manage_validators: manageValidatorsBuilder,
749
+ validators_allocation: manageValidatorsBuilder,
750
+ delegate_to_expert: delegateToExpertBuilder,
751
+ revoke_from_expert: revokeFromExpertBuilder,
752
+ token_sale_recover: tokenSaleRecoverBuilder,
753
+ token_sale_whitelist: tokenSaleWhitelistBuilder,
754
+ create_staking_tier: createStakingTierBuilder,
755
+ change_math_model: changeMathModelBuilder,
756
+ blacklist: blacklistBuilder,
757
+ reward_multiplier: rewardMultiplierBuilder,
758
+ apply_to_dao: applyToDaoBuilder,
759
+ new_proposal_type: newProposalTypeBuilder,
760
+ enable_staking: newProposalTypeBuilder,
761
+ // catalog-style names accepted verbatim (no separate scope/name mapping needed)
762
+ delegate_tokens_to_expert: delegateToExpertBuilder,
763
+ revoke_tokens_from_expert: revokeFromExpertBuilder,
764
+ add_local_expert: scopedExpertAlias(addExpertBuilder, "local"),
765
+ add_global_expert: scopedExpertAlias(addExpertBuilder, "global"),
766
+ remove_local_expert: scopedExpertAlias(removeExpertBuilder, "local"),
767
+ remove_global_expert: scopedExpertAlias(removeExpertBuilder, "global"),
304
768
  };
769
+ const validatorsExecIface = () => new Interface(VALIDATORS_EXEC_ABI);
770
+ /**
771
+ * Internal proposals do NOT go through GovPool.createProposalAndVote — they are
772
+ * created on GovValidators (validators-only voting, no deposit). The flow layer
773
+ * routes these to a single createInternalProposal payload.
774
+ */
775
+ export const INTERNAL_PROPOSAL_BUILDERS = {
776
+ change_validator_balances: {
777
+ schema: z.object({
778
+ changes: z.array(z.object({ user: z.string(), balance: z.string().describe("Wei; 0 to remove") })).min(1),
779
+ }),
780
+ build(raw) {
781
+ const p = raw;
782
+ for (const c of p.changes) {
783
+ if (!isAddress(c.user))
784
+ throw new Error(`Invalid validator address: ${c.user}`);
785
+ }
786
+ const data = validatorsExecIface().encodeFunctionData("changeBalances", [
787
+ p.changes.map((c) => BigInt(c.balance)),
788
+ p.changes.map((c) => c.user),
789
+ ]);
790
+ return {
791
+ internalType: 0,
792
+ data,
793
+ category: "changeValidatorBalances",
794
+ metadataExtra: changes({ validators: p.changes }),
795
+ summary: `Change validator balances (${p.changes.length} changes)`,
796
+ };
797
+ },
798
+ },
799
+ change_validator_settings: {
800
+ schema: z.object({
801
+ duration: z.string().describe("Voting duration in seconds (uint64)"),
802
+ executionDelay: z.string().describe("Delay after success before execution, seconds (uint64)"),
803
+ quorum: z.string().describe("Quorum (uint128, 10^27 percentage scale)"),
804
+ }),
805
+ build(raw) {
806
+ const p = raw;
807
+ const data = validatorsExecIface().encodeFunctionData("changeSettings", [
808
+ BigInt(p.duration), BigInt(p.executionDelay), BigInt(p.quorum),
809
+ ]);
810
+ return {
811
+ internalType: 1,
812
+ data,
813
+ category: "changeValidatorSettings",
814
+ metadataExtra: changes({ duration: p.duration, executionDelay: p.executionDelay, quorum: p.quorum }),
815
+ summary: `Change validator settings (duration=${p.duration}s, delay=${p.executionDelay}s)`,
816
+ };
817
+ },
818
+ },
819
+ monthly_withdraw: {
820
+ schema: z.object({
821
+ withdrawals: z.array(z.object({ token: z.string(), amount: z.string() })).min(1),
822
+ destination: z.string(),
823
+ }),
824
+ build(raw) {
825
+ const p = raw;
826
+ if (!isAddress(p.destination))
827
+ throw new Error(`Invalid destination: ${p.destination}`);
828
+ for (const w of p.withdrawals) {
829
+ if (!isAddress(w.token))
830
+ throw new Error(`Invalid token: ${w.token}`);
831
+ }
832
+ const data = validatorsExecIface().encodeFunctionData("monthlyWithdraw", [
833
+ p.withdrawals.map((w) => w.token),
834
+ p.withdrawals.map((w) => BigInt(w.amount)),
835
+ p.destination,
836
+ ]);
837
+ return {
838
+ internalType: 2,
839
+ data,
840
+ category: "monthlyWithdraw",
841
+ metadataExtra: changes({ withdrawals: p.withdrawals, destination: p.destination }),
842
+ summary: `Monthly withdraw (${p.withdrawals.length} tokens → ${p.destination})`,
843
+ };
844
+ },
845
+ },
846
+ offchain_internal_proposal: {
847
+ schema: z.object({}),
848
+ build() {
849
+ return {
850
+ internalType: 3,
851
+ data: "0x",
852
+ category: "offchainInternalProposal",
853
+ metadataExtra: changes({}),
854
+ summary: "Off-chain internal proposal (validators attest the off-chain result)",
855
+ };
856
+ },
857
+ },
858
+ };
859
+ /**
860
+ * Off-chain proposal types live on the DeXe backend (api.dexe.io), not on any
861
+ * contract — `dexe_proposal_create` rejects them with the exact alternative
862
+ * flow instead of a dead-end.
863
+ */
864
+ export const OFFCHAIN_FLOW_TYPES = [
865
+ "offchain_single_option",
866
+ "offchain_multi_option",
867
+ "offchain_for_against",
868
+ ];
305
869
  /** proposalType values `dexe_proposal_create` accepts directly. */
306
870
  export const FLOW_PROPOSAL_TYPES = [
307
871
  "modify_dao_profile",
308
872
  "custom",
309
873
  ...Object.keys(PROPOSAL_BUILDERS),
874
+ ...Object.keys(INTERNAL_PROPOSAL_BUILDERS),
875
+ ...OFFCHAIN_FLOW_TYPES,
310
876
  ];
311
877
  //# sourceMappingURL=proposalBuilders.js.map