dexe-mcp 0.1.5 → 0.3.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/.mcp.example.json +12 -2
  2. package/CHANGELOG.md +84 -0
  3. package/FUTURE.md +26 -5
  4. package/README.md +105 -197
  5. package/dist/config.d.ts +12 -0
  6. package/dist/config.d.ts.map +1 -1
  7. package/dist/config.js +34 -1
  8. package/dist/config.js.map +1 -1
  9. package/dist/index.js +16 -0
  10. package/dist/index.js.map +1 -1
  11. package/dist/lib/addresses.d.ts +57 -0
  12. package/dist/lib/addresses.d.ts.map +1 -0
  13. package/dist/lib/addresses.js +83 -0
  14. package/dist/lib/addresses.js.map +1 -0
  15. package/dist/lib/calldata.d.ts +32 -0
  16. package/dist/lib/calldata.d.ts.map +1 -0
  17. package/dist/lib/calldata.js +28 -0
  18. package/dist/lib/calldata.js.map +1 -0
  19. package/dist/lib/govEnums.d.ts +12 -0
  20. package/dist/lib/govEnums.d.ts.map +1 -0
  21. package/dist/lib/govEnums.js +35 -0
  22. package/dist/lib/govEnums.js.map +1 -0
  23. package/dist/lib/ipfs.d.ts +60 -0
  24. package/dist/lib/ipfs.d.ts.map +1 -0
  25. package/dist/lib/ipfs.js +168 -0
  26. package/dist/lib/ipfs.js.map +1 -0
  27. package/dist/lib/markdownToSlate.d.ts +39 -0
  28. package/dist/lib/markdownToSlate.d.ts.map +1 -0
  29. package/dist/lib/markdownToSlate.js +203 -0
  30. package/dist/lib/markdownToSlate.js.map +1 -0
  31. package/dist/lib/multicall.d.ts +33 -0
  32. package/dist/lib/multicall.d.ts.map +1 -0
  33. package/dist/lib/multicall.js +46 -0
  34. package/dist/lib/multicall.js.map +1 -0
  35. package/dist/lib/proposalCatalog.d.ts +45 -0
  36. package/dist/lib/proposalCatalog.d.ts.map +1 -0
  37. package/dist/lib/proposalCatalog.js +416 -0
  38. package/dist/lib/proposalCatalog.js.map +1 -0
  39. package/dist/lib/signer.d.ts +12 -0
  40. package/dist/lib/signer.d.ts.map +1 -0
  41. package/dist/lib/signer.js +31 -0
  42. package/dist/lib/signer.js.map +1 -0
  43. package/dist/lib/subgraph.d.ts +15 -0
  44. package/dist/lib/subgraph.d.ts.map +1 -0
  45. package/dist/lib/subgraph.js +42 -0
  46. package/dist/lib/subgraph.js.map +1 -0
  47. package/dist/tools/dao.d.ts +4 -0
  48. package/dist/tools/dao.d.ts.map +1 -0
  49. package/dist/tools/dao.js +242 -0
  50. package/dist/tools/dao.js.map +1 -0
  51. package/dist/tools/daoDeploy.d.ts +4 -0
  52. package/dist/tools/daoDeploy.d.ts.map +1 -0
  53. package/dist/tools/daoDeploy.js +573 -0
  54. package/dist/tools/daoDeploy.js.map +1 -0
  55. package/dist/tools/flow.d.ts +5 -0
  56. package/dist/tools/flow.d.ts.map +1 -0
  57. package/dist/tools/flow.js +451 -0
  58. package/dist/tools/flow.js.map +1 -0
  59. package/dist/tools/index.d.ts.map +1 -1
  60. package/dist/tools/index.js +32 -0
  61. package/dist/tools/index.js.map +1 -1
  62. package/dist/tools/ipfs.d.ts +6 -0
  63. package/dist/tools/ipfs.d.ts.map +1 -0
  64. package/dist/tools/ipfs.js +376 -0
  65. package/dist/tools/ipfs.js.map +1 -0
  66. package/dist/tools/proposal.d.ts +4 -0
  67. package/dist/tools/proposal.d.ts.map +1 -0
  68. package/dist/tools/proposal.js +217 -0
  69. package/dist/tools/proposal.js.map +1 -0
  70. package/dist/tools/proposalBuild.d.ts +4 -0
  71. package/dist/tools/proposalBuild.d.ts.map +1 -0
  72. package/dist/tools/proposalBuild.js +372 -0
  73. package/dist/tools/proposalBuild.js.map +1 -0
  74. package/dist/tools/proposalBuildComplex.d.ts +4 -0
  75. package/dist/tools/proposalBuildComplex.d.ts.map +1 -0
  76. package/dist/tools/proposalBuildComplex.js +766 -0
  77. package/dist/tools/proposalBuildComplex.js.map +1 -0
  78. package/dist/tools/proposalBuildInternal.d.ts +4 -0
  79. package/dist/tools/proposalBuildInternal.d.ts.map +1 -0
  80. package/dist/tools/proposalBuildInternal.js +224 -0
  81. package/dist/tools/proposalBuildInternal.js.map +1 -0
  82. package/dist/tools/proposalBuildMore.d.ts +4 -0
  83. package/dist/tools/proposalBuildMore.d.ts.map +1 -0
  84. package/dist/tools/proposalBuildMore.js +457 -0
  85. package/dist/tools/proposalBuildMore.js.map +1 -0
  86. package/dist/tools/proposalBuildOffchain.d.ts +4 -0
  87. package/dist/tools/proposalBuildOffchain.d.ts.map +1 -0
  88. package/dist/tools/proposalBuildOffchain.js +389 -0
  89. package/dist/tools/proposalBuildOffchain.js.map +1 -0
  90. package/dist/tools/read.d.ts +4 -0
  91. package/dist/tools/read.d.ts.map +1 -0
  92. package/dist/tools/read.js +580 -0
  93. package/dist/tools/read.js.map +1 -0
  94. package/dist/tools/subgraph.d.ts +4 -0
  95. package/dist/tools/subgraph.d.ts.map +1 -0
  96. package/dist/tools/subgraph.js +404 -0
  97. package/dist/tools/subgraph.js.map +1 -0
  98. package/dist/tools/txSend.d.ts +5 -0
  99. package/dist/tools/txSend.d.ts.map +1 -0
  100. package/dist/tools/txSend.js +87 -0
  101. package/dist/tools/txSend.js.map +1 -0
  102. package/dist/tools/vote.d.ts +4 -0
  103. package/dist/tools/vote.d.ts.map +1 -0
  104. package/dist/tools/vote.js +196 -0
  105. package/dist/tools/vote.js.map +1 -0
  106. package/dist/tools/voteBuild.d.ts +4 -0
  107. package/dist/tools/voteBuild.d.ts.map +1 -0
  108. package/dist/tools/voteBuild.js +983 -0
  109. package/dist/tools/voteBuild.js.map +1 -0
  110. package/package.json +18 -5
  111. package/PLAN.md +0 -132
@@ -0,0 +1,983 @@
1
+ import { z } from "zod";
2
+ import { Interface, isAddress } from "ethers";
3
+ import { buildPayload } from "../lib/calldata.js";
4
+ /**
5
+ * Phase 4 — user-facing write calldata builders.
6
+ *
7
+ * Every tool returns `{ payload: TxPayload }` — a signable single tx. For
8
+ * atomic combos (deposit+delegate, execute+claim), use
9
+ * `dexe_vote_build_multicall` which wraps inner calldata into
10
+ * GovPool.multicall(bytes[] calls).
11
+ *
12
+ * Arg-order gotchas (verified against frontend hooks, 2026-04-15):
13
+ * GovPool.vote(proposalId, isVoteFor, amount, nftIds) ← (pid, bool, amt, nfts)
14
+ * GovValidators.voteInternalProposal(proposalId, amount, isVoteFor) ← (pid, amt, bool) — different!
15
+ * GovValidators.voteExternalProposal(proposalId, amount, isVoteFor)
16
+ * GovPool.claimRewards(proposalIds[], user)
17
+ * GovPool.claimMicropoolRewards(proposalIds[], delegator, delegatee)
18
+ *
19
+ * User-level delegate/undelegate live on GovPool (not GovUserKeeper).
20
+ * Treasury-level delegation is a proposal (see Phase 3b).
21
+ */
22
+ const GOV_POOL_WRITE_ABI = [
23
+ "function deposit(uint256 amount, uint256[] nftIds) payable",
24
+ "function withdraw(address receiver, uint256 amount, uint256[] nftIds)",
25
+ "function delegate(address delegatee, uint256 amount, uint256[] nftIds)",
26
+ "function undelegate(address delegatee, uint256 amount, uint256[] nftIds)",
27
+ "function vote(uint256 proposalId, bool isVoteFor, uint256 voteAmount, uint256[] voteNftIds)",
28
+ "function cancelVote(uint256 proposalId)",
29
+ "function moveProposalToValidators(uint256 proposalId)",
30
+ "function execute(uint256 proposalId)",
31
+ "function claimRewards(uint256[] proposalIds, address user)",
32
+ "function claimMicropoolRewards(uint256[] proposalIds, address delegator, address delegatee)",
33
+ "function multicall(bytes[] calls) returns (bytes[] results)",
34
+ ];
35
+ const GOV_VALIDATORS_WRITE_ABI = [
36
+ "function voteInternalProposal(uint256 proposalId, uint256 amount, bool isVoteFor)",
37
+ "function voteExternalProposal(uint256 proposalId, uint256 amount, bool isVoteFor)",
38
+ "function cancelVoteInternalProposal(uint256 proposalId)",
39
+ "function cancelVoteExternalProposal(uint256 proposalId)",
40
+ ];
41
+ const ERC20_WRITE_ABI = ["function approve(address spender, uint256 amount) returns (bool)"];
42
+ const TOKEN_SALE_WRITE_ABI = [
43
+ "function buy(uint256 tierId, address tokenToBuyWith, uint256 amount, bytes32[] proof) payable",
44
+ "function claim(uint256[] tierIds)",
45
+ "function vestingWithdraw(uint256[] tierIds)",
46
+ ];
47
+ const DISTRIBUTION_WRITE_ABI = [
48
+ "function claim(address voter, uint256[] proposalIds)",
49
+ ];
50
+ const NFT_MULTIPLIER_WRITE_ABI = [
51
+ "function lock(uint256 tokenId)",
52
+ "function unlock()",
53
+ ];
54
+ const USER_REGISTRY_WRITE_ABI = [
55
+ "function agreeToPrivacyPolicy(bytes signature)",
56
+ "function changeProfileAndAgreeToPrivacyPolicy(string profileURL, bytes signature)",
57
+ "function documentHash() view returns (bytes32)",
58
+ "function agreed(address user) view returns (bool)",
59
+ ];
60
+ const STAKING_WRITE_ABI = [
61
+ "function claim(uint256 id)",
62
+ "function claimAll()",
63
+ "function reclaim(uint256 id)",
64
+ ];
65
+ const USER_KEEPER_ABI = [
66
+ "function stakeTokens(uint256 tierId, uint256 amount)",
67
+ ];
68
+ function errorResult(message) {
69
+ return { content: [{ type: "text", text: message }], isError: true };
70
+ }
71
+ function payloadResult(payload) {
72
+ return {
73
+ content: [
74
+ {
75
+ type: "text",
76
+ text: `${payload.description}\n to : ${payload.to}\n value: ${payload.value}\n data : ${payload.data.slice(0, 66)}…`,
77
+ },
78
+ ],
79
+ structuredContent: { payload: { ...payload } },
80
+ };
81
+ }
82
+ function payloadOutputSchema() {
83
+ return {
84
+ payload: z.object({
85
+ to: z.string(),
86
+ data: z.string(),
87
+ value: z.string(),
88
+ chainId: z.number(),
89
+ description: z.string(),
90
+ }),
91
+ };
92
+ }
93
+ // ---------- register ----------
94
+ export function registerVoteBuildTools(server, ctx) {
95
+ registerErc20Approve(server, ctx);
96
+ registerDeposit(server, ctx);
97
+ registerWithdraw(server, ctx);
98
+ registerDelegate(server, ctx);
99
+ registerUndelegate(server, ctx);
100
+ registerVote(server, ctx);
101
+ registerCancelVote(server, ctx);
102
+ registerValidatorVote(server, ctx);
103
+ registerValidatorCancelVote(server, ctx);
104
+ registerMoveToValidators(server, ctx);
105
+ registerExecute(server, ctx);
106
+ registerClaimRewards(server, ctx);
107
+ registerClaimMicropoolRewards(server, ctx);
108
+ // NFT multiplier
109
+ registerNftMultiplierLock(server, ctx);
110
+ registerNftMultiplierUnlock(server, ctx);
111
+ // Phase A — token sale + distribution participation
112
+ registerTokenSaleBuy(server, ctx);
113
+ registerTokenSaleClaim(server, ctx);
114
+ registerTokenSaleVestingWithdraw(server, ctx);
115
+ registerDistributionClaim(server, ctx);
116
+ // Phase B — staking participation
117
+ registerStakingStake(server, ctx);
118
+ registerStakingClaim(server, ctx);
119
+ registerStakingClaimAll(server, ctx);
120
+ registerStakingReclaim(server, ctx);
121
+ // Privacy policy
122
+ registerPrivacyPolicySign(server, ctx);
123
+ registerPrivacyPolicyAgree(server, ctx);
124
+ registerMulticall(server, ctx);
125
+ }
126
+ // ---------- ERC20 approve ----------
127
+ function registerErc20Approve(server, ctx) {
128
+ server.registerTool("dexe_vote_build_erc20_approve", {
129
+ title: "Build ERC20.approve(spender, amount) calldata",
130
+ description: "Prepares an ERC20 approval tx. Prepend this before `dexe_vote_build_deposit` when staking an ERC20 token — DAO treasury uses GovPool as the spender. For native-coin staking (BNB/ETH) no approve is needed; just pass `value` on deposit.",
131
+ inputSchema: {
132
+ token: z.string(),
133
+ spender: z.string().describe("Typically the GovPool address"),
134
+ amount: z.string().describe("Wei amount; use max uint256 to grant unlimited"),
135
+ },
136
+ outputSchema: payloadOutputSchema(),
137
+ }, async ({ token, spender, amount }) => {
138
+ if (!isAddress(token))
139
+ return errorResult(`Invalid token: ${token}`);
140
+ if (!isAddress(spender))
141
+ return errorResult(`Invalid spender: ${spender}`);
142
+ try {
143
+ const iface = new Interface(ERC20_WRITE_ABI);
144
+ const payload = buildPayload({
145
+ to: token,
146
+ iface,
147
+ method: "approve",
148
+ args: [spender, BigInt(amount)],
149
+ chainId: ctx.config.chainId,
150
+ contractLabel: "ERC20",
151
+ description: `ERC20(${token}).approve(${spender}, ${amount})`,
152
+ });
153
+ return payloadResult(payload);
154
+ }
155
+ catch (err) {
156
+ return errorResult(err instanceof Error ? err.message : String(err));
157
+ }
158
+ });
159
+ }
160
+ // ---------- deposit ----------
161
+ function registerDeposit(server, ctx) {
162
+ server.registerTool("dexe_vote_build_deposit", {
163
+ title: "Stake tokens/NFTs into a DAO to gain voting power",
164
+ description: "Builds `GovPool.deposit(amount, nftIds)`. **payable** — for native-coin staking, pass `value` (wei). For ERC20 staking, pass `value=0` and ensure an ERC20 approve is already submitted.",
165
+ inputSchema: {
166
+ govPool: z.string(),
167
+ amount: z.string().describe("Token amount in wei"),
168
+ nftIds: z.array(z.string()).default([]),
169
+ value: z
170
+ .string()
171
+ .default("0")
172
+ .describe("Native coin (wei) for native-staking DAOs; 0 for ERC20"),
173
+ },
174
+ outputSchema: payloadOutputSchema(),
175
+ }, async ({ govPool, amount, nftIds = [], value = "0" }) => {
176
+ if (!isAddress(govPool))
177
+ return errorResult(`Invalid govPool: ${govPool}`);
178
+ try {
179
+ const iface = new Interface(GOV_POOL_WRITE_ABI);
180
+ const payload = buildPayload({
181
+ to: govPool,
182
+ iface,
183
+ method: "deposit",
184
+ args: [BigInt(amount), nftIds.map((n) => BigInt(n))],
185
+ value,
186
+ chainId: ctx.config.chainId,
187
+ contractLabel: "GovPool",
188
+ description: `GovPool.deposit(${amount} wei, ${nftIds.length} NFTs)${value !== "0" ? ` + ${value} native` : ""}`,
189
+ });
190
+ return payloadResult(payload);
191
+ }
192
+ catch (err) {
193
+ return errorResult(err instanceof Error ? err.message : String(err));
194
+ }
195
+ });
196
+ }
197
+ // ---------- withdraw ----------
198
+ function registerWithdraw(server, ctx) {
199
+ server.registerTool("dexe_vote_build_withdraw", {
200
+ title: "Unstake tokens/NFTs from a DAO",
201
+ description: "Builds `GovPool.withdraw(receiver, amount, nftIds)`.",
202
+ inputSchema: {
203
+ govPool: z.string(),
204
+ receiver: z.string(),
205
+ amount: z.string(),
206
+ nftIds: z.array(z.string()).default([]),
207
+ },
208
+ outputSchema: payloadOutputSchema(),
209
+ }, async ({ govPool, receiver, amount, nftIds = [] }) => {
210
+ if (!isAddress(govPool))
211
+ return errorResult(`Invalid govPool: ${govPool}`);
212
+ if (!isAddress(receiver))
213
+ return errorResult(`Invalid receiver: ${receiver}`);
214
+ try {
215
+ const iface = new Interface(GOV_POOL_WRITE_ABI);
216
+ const payload = buildPayload({
217
+ to: govPool,
218
+ iface,
219
+ method: "withdraw",
220
+ args: [receiver, BigInt(amount), nftIds.map((n) => BigInt(n))],
221
+ chainId: ctx.config.chainId,
222
+ contractLabel: "GovPool",
223
+ description: `GovPool.withdraw → ${receiver} (${amount} wei, ${nftIds.length} NFTs)`,
224
+ });
225
+ return payloadResult(payload);
226
+ }
227
+ catch (err) {
228
+ return errorResult(err instanceof Error ? err.message : String(err));
229
+ }
230
+ });
231
+ }
232
+ // ---------- delegate (user-level) ----------
233
+ function registerDelegate(server, ctx) {
234
+ server.registerTool("dexe_vote_build_delegate", {
235
+ title: "Delegate YOUR staked voting power to a delegatee",
236
+ description: "Builds `GovPool.delegate(delegatee, amount, nftIds)`. This is the user-level delegation; for DAO treasury delegation use the `dexe_proposal_build_delegate_to_expert` wrapper instead.",
237
+ inputSchema: {
238
+ govPool: z.string(),
239
+ delegatee: z.string(),
240
+ amount: z.string(),
241
+ nftIds: z.array(z.string()).default([]),
242
+ },
243
+ outputSchema: payloadOutputSchema(),
244
+ }, async ({ govPool, delegatee, amount, nftIds = [] }) => {
245
+ if (!isAddress(govPool))
246
+ return errorResult(`Invalid govPool: ${govPool}`);
247
+ if (!isAddress(delegatee))
248
+ return errorResult(`Invalid delegatee: ${delegatee}`);
249
+ try {
250
+ const iface = new Interface(GOV_POOL_WRITE_ABI);
251
+ const payload = buildPayload({
252
+ to: govPool,
253
+ iface,
254
+ method: "delegate",
255
+ args: [delegatee, BigInt(amount), nftIds.map((n) => BigInt(n))],
256
+ chainId: ctx.config.chainId,
257
+ contractLabel: "GovPool",
258
+ description: `GovPool.delegate → ${delegatee} (${amount} wei, ${nftIds.length} NFTs)`,
259
+ });
260
+ return payloadResult(payload);
261
+ }
262
+ catch (err) {
263
+ return errorResult(err instanceof Error ? err.message : String(err));
264
+ }
265
+ });
266
+ }
267
+ // ---------- undelegate ----------
268
+ function registerUndelegate(server, ctx) {
269
+ server.registerTool("dexe_vote_build_undelegate", {
270
+ title: "Undelegate voting power from a delegatee",
271
+ description: "Builds `GovPool.undelegate(delegatee, amount, nftIds)`.",
272
+ inputSchema: {
273
+ govPool: z.string(),
274
+ delegatee: z.string(),
275
+ amount: z.string(),
276
+ nftIds: z.array(z.string()).default([]),
277
+ },
278
+ outputSchema: payloadOutputSchema(),
279
+ }, async ({ govPool, delegatee, amount, nftIds = [] }) => {
280
+ if (!isAddress(govPool))
281
+ return errorResult(`Invalid govPool: ${govPool}`);
282
+ if (!isAddress(delegatee))
283
+ return errorResult(`Invalid delegatee: ${delegatee}`);
284
+ try {
285
+ const iface = new Interface(GOV_POOL_WRITE_ABI);
286
+ const payload = buildPayload({
287
+ to: govPool,
288
+ iface,
289
+ method: "undelegate",
290
+ args: [delegatee, BigInt(amount), nftIds.map((n) => BigInt(n))],
291
+ chainId: ctx.config.chainId,
292
+ contractLabel: "GovPool",
293
+ description: `GovPool.undelegate ← ${delegatee} (${amount} wei, ${nftIds.length} NFTs)`,
294
+ });
295
+ return payloadResult(payload);
296
+ }
297
+ catch (err) {
298
+ return errorResult(err instanceof Error ? err.message : String(err));
299
+ }
300
+ });
301
+ }
302
+ // ---------- vote ----------
303
+ function registerVote(server, ctx) {
304
+ server.registerTool("dexe_vote_build_vote", {
305
+ title: "Vote on an external proposal",
306
+ description: "Builds `GovPool.vote(proposalId, isVoteFor, amount, nftIds)`. Arg order: (proposalId, isVoteFor, amount, nftIds). Must have staked/delegated voting power beforehand.",
307
+ inputSchema: {
308
+ govPool: z.string(),
309
+ proposalId: z.string(),
310
+ isVoteFor: z.boolean(),
311
+ amount: z.string(),
312
+ nftIds: z.array(z.string()).default([]),
313
+ },
314
+ outputSchema: payloadOutputSchema(),
315
+ }, async ({ govPool, proposalId, isVoteFor, amount, nftIds = [] }) => {
316
+ if (!isAddress(govPool))
317
+ return errorResult(`Invalid govPool: ${govPool}`);
318
+ try {
319
+ const iface = new Interface(GOV_POOL_WRITE_ABI);
320
+ const payload = buildPayload({
321
+ to: govPool,
322
+ iface,
323
+ method: "vote",
324
+ args: [BigInt(proposalId), isVoteFor, BigInt(amount), nftIds.map((n) => BigInt(n))],
325
+ chainId: ctx.config.chainId,
326
+ contractLabel: "GovPool",
327
+ description: `GovPool.vote(#${proposalId}, ${isVoteFor ? "FOR" : "AGAINST"}, ${amount} wei, ${nftIds.length} NFTs)`,
328
+ });
329
+ return payloadResult(payload);
330
+ }
331
+ catch (err) {
332
+ return errorResult(err instanceof Error ? err.message : String(err));
333
+ }
334
+ });
335
+ }
336
+ // ---------- cancel vote ----------
337
+ function registerCancelVote(server, ctx) {
338
+ server.registerTool("dexe_vote_build_cancel_vote", {
339
+ title: "Cancel your vote on an external proposal",
340
+ description: "Builds `GovPool.cancelVote(proposalId)`.",
341
+ inputSchema: {
342
+ govPool: z.string(),
343
+ proposalId: z.string(),
344
+ },
345
+ outputSchema: payloadOutputSchema(),
346
+ }, async ({ govPool, proposalId }) => {
347
+ if (!isAddress(govPool))
348
+ return errorResult(`Invalid govPool: ${govPool}`);
349
+ try {
350
+ const iface = new Interface(GOV_POOL_WRITE_ABI);
351
+ const payload = buildPayload({
352
+ to: govPool,
353
+ iface,
354
+ method: "cancelVote",
355
+ args: [BigInt(proposalId)],
356
+ chainId: ctx.config.chainId,
357
+ contractLabel: "GovPool",
358
+ description: `GovPool.cancelVote(#${proposalId})`,
359
+ });
360
+ return payloadResult(payload);
361
+ }
362
+ catch (err) {
363
+ return errorResult(err instanceof Error ? err.message : String(err));
364
+ }
365
+ });
366
+ }
367
+ // ---------- validator vote ----------
368
+ function registerValidatorVote(server, ctx) {
369
+ server.registerTool("dexe_vote_build_validator_vote", {
370
+ title: "Validator vote on internal or external proposal",
371
+ description: "Builds `GovValidators.vote{Internal,External}Proposal(proposalId, amount, isVoteFor)`. **Arg order differs from GovPool.vote** — here amount comes before isVoteFor. Requires validator stake.",
372
+ inputSchema: {
373
+ govValidators: z.string(),
374
+ scope: z.enum(["internal", "external"]),
375
+ proposalId: z.string(),
376
+ amount: z.string(),
377
+ isVoteFor: z.boolean(),
378
+ },
379
+ outputSchema: payloadOutputSchema(),
380
+ }, async ({ govValidators, scope, proposalId, amount, isVoteFor }) => {
381
+ if (!isAddress(govValidators))
382
+ return errorResult(`Invalid govValidators: ${govValidators}`);
383
+ try {
384
+ const iface = new Interface(GOV_VALIDATORS_WRITE_ABI);
385
+ const method = scope === "internal" ? "voteInternalProposal" : "voteExternalProposal";
386
+ const payload = buildPayload({
387
+ to: govValidators,
388
+ iface,
389
+ method,
390
+ args: [BigInt(proposalId), BigInt(amount), isVoteFor],
391
+ chainId: ctx.config.chainId,
392
+ contractLabel: "GovValidators",
393
+ description: `GovValidators.${method}(#${proposalId}, ${amount}, ${isVoteFor ? "FOR" : "AGAINST"})`,
394
+ });
395
+ return payloadResult(payload);
396
+ }
397
+ catch (err) {
398
+ return errorResult(err instanceof Error ? err.message : String(err));
399
+ }
400
+ });
401
+ }
402
+ // ---------- validator cancel vote ----------
403
+ function registerValidatorCancelVote(server, ctx) {
404
+ server.registerTool("dexe_vote_build_validator_cancel_vote", {
405
+ title: "Validator: cancel your vote on internal/external proposal",
406
+ description: "Builds `GovValidators.cancelVote{Internal,External}Proposal(proposalId)`.",
407
+ inputSchema: {
408
+ govValidators: z.string(),
409
+ scope: z.enum(["internal", "external"]),
410
+ proposalId: z.string(),
411
+ },
412
+ outputSchema: payloadOutputSchema(),
413
+ }, async ({ govValidators, scope, proposalId }) => {
414
+ if (!isAddress(govValidators))
415
+ return errorResult(`Invalid govValidators: ${govValidators}`);
416
+ try {
417
+ const iface = new Interface(GOV_VALIDATORS_WRITE_ABI);
418
+ const method = scope === "internal" ? "cancelVoteInternalProposal" : "cancelVoteExternalProposal";
419
+ const payload = buildPayload({
420
+ to: govValidators,
421
+ iface,
422
+ method,
423
+ args: [BigInt(proposalId)],
424
+ chainId: ctx.config.chainId,
425
+ contractLabel: "GovValidators",
426
+ description: `GovValidators.${method}(#${proposalId})`,
427
+ });
428
+ return payloadResult(payload);
429
+ }
430
+ catch (err) {
431
+ return errorResult(err instanceof Error ? err.message : String(err));
432
+ }
433
+ });
434
+ }
435
+ // ---------- move to validators ----------
436
+ function registerMoveToValidators(server, ctx) {
437
+ server.registerTool("dexe_vote_build_move_to_validators", {
438
+ title: "Escalate a passing proposal to the validators tier",
439
+ description: "Builds `GovPool.moveProposalToValidators(proposalId)`.",
440
+ inputSchema: {
441
+ govPool: z.string(),
442
+ proposalId: z.string(),
443
+ },
444
+ outputSchema: payloadOutputSchema(),
445
+ }, async ({ govPool, proposalId }) => {
446
+ if (!isAddress(govPool))
447
+ return errorResult(`Invalid govPool: ${govPool}`);
448
+ try {
449
+ const iface = new Interface(GOV_POOL_WRITE_ABI);
450
+ const payload = buildPayload({
451
+ to: govPool,
452
+ iface,
453
+ method: "moveProposalToValidators",
454
+ args: [BigInt(proposalId)],
455
+ chainId: ctx.config.chainId,
456
+ contractLabel: "GovPool",
457
+ description: `GovPool.moveProposalToValidators(#${proposalId})`,
458
+ });
459
+ return payloadResult(payload);
460
+ }
461
+ catch (err) {
462
+ return errorResult(err instanceof Error ? err.message : String(err));
463
+ }
464
+ });
465
+ }
466
+ // ---------- execute ----------
467
+ function registerExecute(server, ctx) {
468
+ server.registerTool("dexe_vote_build_execute", {
469
+ title: "Execute a passed proposal",
470
+ description: "Builds `GovPool.execute(proposalId)`.",
471
+ inputSchema: {
472
+ govPool: z.string(),
473
+ proposalId: z.string(),
474
+ },
475
+ outputSchema: payloadOutputSchema(),
476
+ }, async ({ govPool, proposalId }) => {
477
+ if (!isAddress(govPool))
478
+ return errorResult(`Invalid govPool: ${govPool}`);
479
+ try {
480
+ const iface = new Interface(GOV_POOL_WRITE_ABI);
481
+ const payload = buildPayload({
482
+ to: govPool,
483
+ iface,
484
+ method: "execute",
485
+ args: [BigInt(proposalId)],
486
+ chainId: ctx.config.chainId,
487
+ contractLabel: "GovPool",
488
+ description: `GovPool.execute(#${proposalId})`,
489
+ });
490
+ return payloadResult(payload);
491
+ }
492
+ catch (err) {
493
+ return errorResult(err instanceof Error ? err.message : String(err));
494
+ }
495
+ });
496
+ }
497
+ // ---------- claim rewards ----------
498
+ function registerClaimRewards(server, ctx) {
499
+ server.registerTool("dexe_vote_build_claim_rewards", {
500
+ title: "Claim voter rewards for executed proposals",
501
+ description: "Builds `GovPool.claimRewards(proposalIds, user)`.",
502
+ inputSchema: {
503
+ govPool: z.string(),
504
+ proposalIds: z.array(z.string()).min(1),
505
+ user: z.string(),
506
+ },
507
+ outputSchema: payloadOutputSchema(),
508
+ }, async ({ govPool, proposalIds, user }) => {
509
+ if (!isAddress(govPool))
510
+ return errorResult(`Invalid govPool: ${govPool}`);
511
+ if (!isAddress(user))
512
+ return errorResult(`Invalid user: ${user}`);
513
+ try {
514
+ const iface = new Interface(GOV_POOL_WRITE_ABI);
515
+ const payload = buildPayload({
516
+ to: govPool,
517
+ iface,
518
+ method: "claimRewards",
519
+ args: [proposalIds.map((p) => BigInt(p)), user],
520
+ chainId: ctx.config.chainId,
521
+ contractLabel: "GovPool",
522
+ description: `GovPool.claimRewards([${proposalIds.join(",")}], ${user})`,
523
+ });
524
+ return payloadResult(payload);
525
+ }
526
+ catch (err) {
527
+ return errorResult(err instanceof Error ? err.message : String(err));
528
+ }
529
+ });
530
+ }
531
+ // ---------- claim micropool rewards ----------
532
+ function registerClaimMicropoolRewards(server, ctx) {
533
+ server.registerTool("dexe_vote_build_claim_micropool_rewards", {
534
+ title: "Claim micropool (delegated) rewards",
535
+ description: "Builds `GovPool.claimMicropoolRewards(proposalIds, delegator, delegatee)`. Called by the delegator to collect their share of rewards earned by their delegatee's votes.",
536
+ inputSchema: {
537
+ govPool: z.string(),
538
+ proposalIds: z.array(z.string()).min(1),
539
+ delegator: z.string(),
540
+ delegatee: z.string(),
541
+ },
542
+ outputSchema: payloadOutputSchema(),
543
+ }, async ({ govPool, proposalIds, delegator, delegatee }) => {
544
+ if (!isAddress(govPool))
545
+ return errorResult(`Invalid govPool: ${govPool}`);
546
+ if (!isAddress(delegator))
547
+ return errorResult(`Invalid delegator: ${delegator}`);
548
+ if (!isAddress(delegatee))
549
+ return errorResult(`Invalid delegatee: ${delegatee}`);
550
+ try {
551
+ const iface = new Interface(GOV_POOL_WRITE_ABI);
552
+ const payload = buildPayload({
553
+ to: govPool,
554
+ iface,
555
+ method: "claimMicropoolRewards",
556
+ args: [proposalIds.map((p) => BigInt(p)), delegator, delegatee],
557
+ chainId: ctx.config.chainId,
558
+ contractLabel: "GovPool",
559
+ description: `GovPool.claimMicropoolRewards([${proposalIds.join(",")}], ${delegator} → ${delegatee})`,
560
+ });
561
+ return payloadResult(payload);
562
+ }
563
+ catch (err) {
564
+ return errorResult(err instanceof Error ? err.message : String(err));
565
+ }
566
+ });
567
+ }
568
+ // ---------- NFT multiplier ----------
569
+ function registerNftMultiplierLock(server, ctx) {
570
+ server.registerTool("dexe_vote_build_nft_multiplier_lock", {
571
+ title: "Lock an NFT multiplier to boost voting power",
572
+ description: "Builds calldata for `ERC721Multiplier.lock(tokenId)`. Locks a reward-multiplier NFT to apply its bonus to the caller's voting power.",
573
+ inputSchema: {
574
+ nftMultiplier: z.string().describe("ERC721Multiplier contract address (from dexe_dao_info → nftMultiplier)"),
575
+ tokenId: z.string().describe("NFT token ID to lock"),
576
+ },
577
+ outputSchema: payloadOutputSchema(),
578
+ }, async ({ nftMultiplier, tokenId }) => {
579
+ if (!isAddress(nftMultiplier))
580
+ return errorResult(`Invalid nftMultiplier: ${nftMultiplier}`);
581
+ try {
582
+ const iface = new Interface(NFT_MULTIPLIER_WRITE_ABI);
583
+ const payload = buildPayload({
584
+ to: nftMultiplier,
585
+ iface,
586
+ method: "lock",
587
+ args: [BigInt(tokenId)],
588
+ chainId: ctx.config.chainId,
589
+ contractLabel: "ERC721Multiplier",
590
+ });
591
+ return payloadResult(payload);
592
+ }
593
+ catch (err) {
594
+ return errorResult(err instanceof Error ? err.message : String(err));
595
+ }
596
+ });
597
+ }
598
+ function registerNftMultiplierUnlock(server, ctx) {
599
+ server.registerTool("dexe_vote_build_nft_multiplier_unlock", {
600
+ title: "Unlock the NFT multiplier to reclaim it",
601
+ description: "Builds calldata for `ERC721Multiplier.unlock()`. Removes the locked reward-multiplier NFT, returning it to the caller and removing the voting power bonus.",
602
+ inputSchema: {
603
+ nftMultiplier: z.string().describe("ERC721Multiplier contract address (from dexe_dao_info → nftMultiplier)"),
604
+ },
605
+ outputSchema: payloadOutputSchema(),
606
+ }, async ({ nftMultiplier }) => {
607
+ if (!isAddress(nftMultiplier))
608
+ return errorResult(`Invalid nftMultiplier: ${nftMultiplier}`);
609
+ try {
610
+ const iface = new Interface(NFT_MULTIPLIER_WRITE_ABI);
611
+ const payload = buildPayload({
612
+ to: nftMultiplier,
613
+ iface,
614
+ method: "unlock",
615
+ args: [],
616
+ chainId: ctx.config.chainId,
617
+ contractLabel: "ERC721Multiplier",
618
+ });
619
+ return payloadResult(payload);
620
+ }
621
+ catch (err) {
622
+ return errorResult(err instanceof Error ? err.message : String(err));
623
+ }
624
+ });
625
+ }
626
+ // ---------- token sale participation ----------
627
+ function registerTokenSaleBuy(server, ctx) {
628
+ server.registerTool("dexe_vote_build_token_sale_buy", {
629
+ title: "Buy tokens from a token sale tier",
630
+ description: "Builds calldata for `TokenSaleProposal.buy(tierId, tokenToBuyWith, amount, proof)`. Pass `value` for native-coin purchases. Pass Merkle `proof` if the tier is whitelisted (empty array otherwise).",
631
+ inputSchema: {
632
+ tokenSaleProposal: z.string().describe("TokenSaleProposal contract address"),
633
+ tierId: z.string().describe("Tier ID to buy from"),
634
+ tokenToBuyWith: z.string().describe("Payment token address (zero address for native coin)"),
635
+ amount: z.string().describe("Payment amount in wei"),
636
+ proof: z.array(z.string()).default([]).describe("Merkle proof bytes32[] (empty if no whitelist)"),
637
+ value: z.string().default("0").describe("Native-coin value in wei (for native purchases)"),
638
+ },
639
+ outputSchema: payloadOutputSchema(),
640
+ }, async ({ tokenSaleProposal, tierId, tokenToBuyWith, amount, proof = [], value = "0" }) => {
641
+ if (!isAddress(tokenSaleProposal))
642
+ return errorResult(`Invalid tokenSaleProposal: ${tokenSaleProposal}`);
643
+ if (!isAddress(tokenToBuyWith))
644
+ return errorResult(`Invalid tokenToBuyWith: ${tokenToBuyWith}`);
645
+ try {
646
+ const iface = new Interface(TOKEN_SALE_WRITE_ABI);
647
+ const payload = buildPayload({
648
+ to: tokenSaleProposal,
649
+ iface,
650
+ method: "buy",
651
+ args: [BigInt(tierId), tokenToBuyWith, BigInt(amount), proof],
652
+ value: BigInt(value),
653
+ chainId: ctx.config.chainId,
654
+ contractLabel: "TokenSaleProposal",
655
+ });
656
+ return payloadResult(payload);
657
+ }
658
+ catch (err) {
659
+ return errorResult(err instanceof Error ? err.message : String(err));
660
+ }
661
+ });
662
+ }
663
+ function registerTokenSaleClaim(server, ctx) {
664
+ server.registerTool("dexe_vote_build_token_sale_claim", {
665
+ title: "Claim purchased tokens from token sale tiers",
666
+ description: "Builds calldata for `TokenSaleProposal.claim(tierIds)`. Call after the tier's claim lock duration has passed.",
667
+ inputSchema: {
668
+ tokenSaleProposal: z.string().describe("TokenSaleProposal contract address"),
669
+ tierIds: z.array(z.string()).min(1).describe("Tier IDs to claim from"),
670
+ },
671
+ outputSchema: payloadOutputSchema(),
672
+ }, async ({ tokenSaleProposal, tierIds }) => {
673
+ if (!isAddress(tokenSaleProposal))
674
+ return errorResult(`Invalid tokenSaleProposal: ${tokenSaleProposal}`);
675
+ try {
676
+ const iface = new Interface(TOKEN_SALE_WRITE_ABI);
677
+ const payload = buildPayload({
678
+ to: tokenSaleProposal,
679
+ iface,
680
+ method: "claim",
681
+ args: [tierIds.map((id) => BigInt(id))],
682
+ chainId: ctx.config.chainId,
683
+ contractLabel: "TokenSaleProposal",
684
+ });
685
+ return payloadResult(payload);
686
+ }
687
+ catch (err) {
688
+ return errorResult(err instanceof Error ? err.message : String(err));
689
+ }
690
+ });
691
+ }
692
+ function registerTokenSaleVestingWithdraw(server, ctx) {
693
+ server.registerTool("dexe_vote_build_token_sale_vesting_withdraw", {
694
+ title: "Withdraw vested tokens from token sale tiers",
695
+ description: "Builds calldata for `TokenSaleProposal.vestingWithdraw(tierIds)`. For tiers with vesting schedules — withdraws the currently unlocked portion.",
696
+ inputSchema: {
697
+ tokenSaleProposal: z.string().describe("TokenSaleProposal contract address"),
698
+ tierIds: z.array(z.string()).min(1).describe("Tier IDs to withdraw vested tokens from"),
699
+ },
700
+ outputSchema: payloadOutputSchema(),
701
+ }, async ({ tokenSaleProposal, tierIds }) => {
702
+ if (!isAddress(tokenSaleProposal))
703
+ return errorResult(`Invalid tokenSaleProposal: ${tokenSaleProposal}`);
704
+ try {
705
+ const iface = new Interface(TOKEN_SALE_WRITE_ABI);
706
+ const payload = buildPayload({
707
+ to: tokenSaleProposal,
708
+ iface,
709
+ method: "vestingWithdraw",
710
+ args: [tierIds.map((id) => BigInt(id))],
711
+ chainId: ctx.config.chainId,
712
+ contractLabel: "TokenSaleProposal",
713
+ });
714
+ return payloadResult(payload);
715
+ }
716
+ catch (err) {
717
+ return errorResult(err instanceof Error ? err.message : String(err));
718
+ }
719
+ });
720
+ }
721
+ // ---------- distribution participation ----------
722
+ function registerDistributionClaim(server, ctx) {
723
+ server.registerTool("dexe_vote_build_distribution_claim", {
724
+ title: "Claim share from distribution proposals",
725
+ description: "Builds calldata for `DistributionProposal.claim(voter, proposalIds)`. Claims the voter's proportional share from executed distribution proposals.",
726
+ inputSchema: {
727
+ distributionProposal: z.string().describe("DistributionProposal contract address"),
728
+ voter: z.string().describe("Address of the voter claiming their share"),
729
+ proposalIds: z.array(z.string()).min(1).describe("Proposal IDs to claim from"),
730
+ },
731
+ outputSchema: payloadOutputSchema(),
732
+ }, async ({ distributionProposal, voter, proposalIds }) => {
733
+ if (!isAddress(distributionProposal))
734
+ return errorResult(`Invalid distributionProposal: ${distributionProposal}`);
735
+ if (!isAddress(voter))
736
+ return errorResult(`Invalid voter: ${voter}`);
737
+ try {
738
+ const iface = new Interface(DISTRIBUTION_WRITE_ABI);
739
+ const payload = buildPayload({
740
+ to: distributionProposal,
741
+ iface,
742
+ method: "claim",
743
+ args: [voter, proposalIds.map((id) => BigInt(id))],
744
+ chainId: ctx.config.chainId,
745
+ contractLabel: "DistributionProposal",
746
+ });
747
+ return payloadResult(payload);
748
+ }
749
+ catch (err) {
750
+ return errorResult(err instanceof Error ? err.message : String(err));
751
+ }
752
+ });
753
+ }
754
+ // ---------- staking participation ----------
755
+ function registerStakingStake(server, ctx) {
756
+ server.registerTool("dexe_vote_build_staking_stake", {
757
+ title: "Stake tokens in a staking tier",
758
+ description: "Builds calldata for `GovUserKeeper.stakeTokens(tierId, amount)`. Deposits tokens into the specified staking tier to earn rewards. Target is the GovUserKeeper contract (not StakingProposal).",
759
+ inputSchema: {
760
+ userKeeper: z.string().describe("GovUserKeeper contract address"),
761
+ tierId: z.string().describe("Staking tier ID"),
762
+ amount: z.string().describe("Amount to stake in wei"),
763
+ },
764
+ outputSchema: payloadOutputSchema(),
765
+ }, async ({ userKeeper, tierId, amount }) => {
766
+ if (!isAddress(userKeeper))
767
+ return errorResult(`Invalid userKeeper: ${userKeeper}`);
768
+ try {
769
+ const iface = new Interface(USER_KEEPER_ABI);
770
+ const payload = buildPayload({
771
+ to: userKeeper,
772
+ iface,
773
+ method: "stakeTokens",
774
+ args: [BigInt(tierId), BigInt(amount)],
775
+ chainId: ctx.config.chainId,
776
+ contractLabel: "GovUserKeeper",
777
+ });
778
+ return payloadResult(payload);
779
+ }
780
+ catch (err) {
781
+ return errorResult(err instanceof Error ? err.message : String(err));
782
+ }
783
+ });
784
+ }
785
+ function registerStakingClaim(server, ctx) {
786
+ server.registerTool("dexe_vote_build_staking_claim", {
787
+ title: "Claim staking rewards from a tier",
788
+ description: "Builds calldata for `StakingProposal.claim(id)`. Claims accumulated rewards from the specified staking tier without unstaking.",
789
+ inputSchema: {
790
+ stakingProposal: z.string().describe("StakingProposal contract address"),
791
+ stakingId: z.string().describe("Staking tier ID to claim rewards from"),
792
+ },
793
+ outputSchema: payloadOutputSchema(),
794
+ }, async ({ stakingProposal, stakingId }) => {
795
+ if (!isAddress(stakingProposal))
796
+ return errorResult(`Invalid stakingProposal: ${stakingProposal}`);
797
+ try {
798
+ const iface = new Interface(STAKING_WRITE_ABI);
799
+ const payload = buildPayload({
800
+ to: stakingProposal,
801
+ iface,
802
+ method: "claim",
803
+ args: [BigInt(stakingId)],
804
+ chainId: ctx.config.chainId,
805
+ contractLabel: "StakingProposal",
806
+ });
807
+ return payloadResult(payload);
808
+ }
809
+ catch (err) {
810
+ return errorResult(err instanceof Error ? err.message : String(err));
811
+ }
812
+ });
813
+ }
814
+ function registerStakingClaimAll(server, ctx) {
815
+ server.registerTool("dexe_vote_build_staking_claim_all", {
816
+ title: "Claim all staking rewards across all tiers",
817
+ description: "Builds calldata for `StakingProposal.claimAll()`. Claims accumulated rewards from every active staking tier in one transaction.",
818
+ inputSchema: {
819
+ stakingProposal: z.string().describe("StakingProposal contract address"),
820
+ },
821
+ outputSchema: payloadOutputSchema(),
822
+ }, async ({ stakingProposal }) => {
823
+ if (!isAddress(stakingProposal))
824
+ return errorResult(`Invalid stakingProposal: ${stakingProposal}`);
825
+ try {
826
+ const iface = new Interface(STAKING_WRITE_ABI);
827
+ const payload = buildPayload({
828
+ to: stakingProposal,
829
+ iface,
830
+ method: "claimAll",
831
+ args: [],
832
+ chainId: ctx.config.chainId,
833
+ contractLabel: "StakingProposal",
834
+ });
835
+ return payloadResult(payload);
836
+ }
837
+ catch (err) {
838
+ return errorResult(err instanceof Error ? err.message : String(err));
839
+ }
840
+ });
841
+ }
842
+ function registerStakingReclaim(server, ctx) {
843
+ server.registerTool("dexe_vote_build_staking_reclaim", {
844
+ title: "Unstake (reclaim) tokens from a staking tier",
845
+ description: "Builds calldata for `StakingProposal.reclaim(id)`. Withdraws staked tokens and any pending rewards from the specified tier.",
846
+ inputSchema: {
847
+ stakingProposal: z.string().describe("StakingProposal contract address"),
848
+ stakingId: z.string().describe("Staking tier ID to unstake from"),
849
+ },
850
+ outputSchema: payloadOutputSchema(),
851
+ }, async ({ stakingProposal, stakingId }) => {
852
+ if (!isAddress(stakingProposal))
853
+ return errorResult(`Invalid stakingProposal: ${stakingProposal}`);
854
+ try {
855
+ const iface = new Interface(STAKING_WRITE_ABI);
856
+ const payload = buildPayload({
857
+ to: stakingProposal,
858
+ iface,
859
+ method: "reclaim",
860
+ args: [BigInt(stakingId)],
861
+ chainId: ctx.config.chainId,
862
+ contractLabel: "StakingProposal",
863
+ });
864
+ return payloadResult(payload);
865
+ }
866
+ catch (err) {
867
+ return errorResult(err instanceof Error ? err.message : String(err));
868
+ }
869
+ });
870
+ }
871
+ // ---------- privacy policy ----------
872
+ function registerPrivacyPolicySign(server, ctx) {
873
+ server.registerTool("dexe_vote_build_privacy_policy_sign", {
874
+ title: "Build EIP712 typed data for privacy policy agreement",
875
+ description: "Returns the EIP712 typed data that must be signed before calling `agreeToPrivacyPolicy`. The agent wallet signs this with `signTypedData`, then passes the signature to `dexe_vote_build_privacy_policy_agree`. One-time global action per user (not per-DAO).",
876
+ inputSchema: {
877
+ userRegistry: z.string().describe("UserRegistry contract address"),
878
+ documentHash: z.string().describe("Privacy policy document hash (bytes32). Read from UserRegistry.documentHash() or use DEXE_PRIVACY_POLICY_HASH env var."),
879
+ },
880
+ }, async ({ userRegistry, documentHash }) => {
881
+ if (!isAddress(userRegistry))
882
+ return errorResult(`Invalid userRegistry: ${userRegistry}`);
883
+ if (!documentHash.startsWith("0x") || documentHash.length !== 66) {
884
+ return errorResult(`documentHash must be a bytes32 hex string (0x + 64 hex chars), got: ${documentHash}`);
885
+ }
886
+ const typedData = {
887
+ domain: {
888
+ name: "USER_REGISTRY",
889
+ version: "1",
890
+ chainId: ctx.config.chainId,
891
+ verifyingContract: userRegistry,
892
+ },
893
+ types: {
894
+ Agreement: [{ name: "documentHash", type: "bytes32" }],
895
+ },
896
+ primaryType: "Agreement",
897
+ message: {
898
+ documentHash,
899
+ },
900
+ };
901
+ return {
902
+ content: [
903
+ {
904
+ type: "text",
905
+ text: `EIP712 typed data for privacy policy agreement on ${userRegistry} (chainId=${ctx.config.chainId}). Sign with wallet's signTypedData, then pass signature to dexe_vote_build_privacy_policy_agree.`,
906
+ },
907
+ ],
908
+ structuredContent: { typedData },
909
+ };
910
+ });
911
+ }
912
+ function registerPrivacyPolicyAgree(server, ctx) {
913
+ server.registerTool("dexe_vote_build_privacy_policy_agree", {
914
+ title: "Submit privacy policy agreement signature on-chain",
915
+ description: "Builds calldata for `UserRegistry.agreeToPrivacyPolicy(signature)`. Pass the EIP712 signature obtained from signing the typed data (from `dexe_vote_build_privacy_policy_sign`). Optionally combine with profile URL update via `profileURL` param.",
916
+ inputSchema: {
917
+ userRegistry: z.string().describe("UserRegistry contract address"),
918
+ signature: z.string().describe("EIP712 signature bytes (0x-prefixed hex)"),
919
+ profileURL: z.string().optional().describe("Optional IPFS URL for user profile — if set, calls changeProfileAndAgreeToPrivacyPolicy instead"),
920
+ },
921
+ outputSchema: payloadOutputSchema(),
922
+ }, async ({ userRegistry, signature, profileURL }) => {
923
+ if (!isAddress(userRegistry))
924
+ return errorResult(`Invalid userRegistry: ${userRegistry}`);
925
+ if (!signature.startsWith("0x"))
926
+ return errorResult(`Signature must be 0x-prefixed hex`);
927
+ try {
928
+ const iface = new Interface(USER_REGISTRY_WRITE_ABI);
929
+ const method = profileURL ? "changeProfileAndAgreeToPrivacyPolicy" : "agreeToPrivacyPolicy";
930
+ const args = profileURL ? [profileURL, signature] : [signature];
931
+ const payload = buildPayload({
932
+ to: userRegistry,
933
+ iface,
934
+ method,
935
+ args,
936
+ chainId: ctx.config.chainId,
937
+ contractLabel: "UserRegistry",
938
+ });
939
+ return payloadResult(payload);
940
+ }
941
+ catch (err) {
942
+ return errorResult(err instanceof Error ? err.message : String(err));
943
+ }
944
+ });
945
+ }
946
+ // ---------- multicall wrapper ----------
947
+ function registerMulticall(server, ctx) {
948
+ server.registerTool("dexe_vote_build_multicall", {
949
+ title: "Atomic multicall on GovPool (batch multiple writes in one tx)",
950
+ description: "Wraps N inner calldatas into `GovPool.multicall(calls)`. Pass the `data` fields from other build tools (e.g. deposit + delegate, execute + claim). Each inner call executes against GovPool itself — only use for GovPool methods.",
951
+ inputSchema: {
952
+ govPool: z.string(),
953
+ calls: z.array(z.string()).min(2).describe("Array of 0x-hex calldatas to batch"),
954
+ value: z.string().default("0").describe("Total native-coin value across the batch"),
955
+ },
956
+ outputSchema: payloadOutputSchema(),
957
+ }, async ({ govPool, calls, value = "0" }) => {
958
+ if (!isAddress(govPool))
959
+ return errorResult(`Invalid govPool: ${govPool}`);
960
+ for (const c of calls) {
961
+ if (!c.startsWith("0x"))
962
+ return errorResult(`call must be 0x-prefixed hex: ${c.slice(0, 16)}…`);
963
+ }
964
+ try {
965
+ const iface = new Interface(GOV_POOL_WRITE_ABI);
966
+ const payload = buildPayload({
967
+ to: govPool,
968
+ iface,
969
+ method: "multicall",
970
+ args: [calls],
971
+ value,
972
+ chainId: ctx.config.chainId,
973
+ contractLabel: "GovPool",
974
+ description: `GovPool.multicall(${calls.length} calls)`,
975
+ });
976
+ return payloadResult(payload);
977
+ }
978
+ catch (err) {
979
+ return errorResult(err instanceof Error ? err.message : String(err));
980
+ }
981
+ });
982
+ }
983
+ //# sourceMappingURL=voteBuild.js.map