dexe-mcp 0.2.0 → 0.4.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 (80) hide show
  1. package/CHANGELOG.md +192 -85
  2. package/README.md +103 -80
  3. package/dist/config.d.ts +2 -0
  4. package/dist/config.d.ts.map +1 -1
  5. package/dist/config.js +10 -0
  6. package/dist/config.js.map +1 -1
  7. package/dist/index.js +16 -0
  8. package/dist/index.js.map +1 -1
  9. package/dist/lib/addresses.js +1 -1
  10. package/dist/lib/ipfs.d.ts.map +1 -1
  11. package/dist/lib/ipfs.js +2 -1
  12. package/dist/lib/ipfs.js.map +1 -1
  13. package/dist/lib/markdownToSlate.d.ts +39 -0
  14. package/dist/lib/markdownToSlate.d.ts.map +1 -0
  15. package/dist/lib/markdownToSlate.js +203 -0
  16. package/dist/lib/markdownToSlate.js.map +1 -0
  17. package/dist/lib/merkleTree.d.ts +38 -0
  18. package/dist/lib/merkleTree.d.ts.map +1 -0
  19. package/dist/lib/merkleTree.js +83 -0
  20. package/dist/lib/merkleTree.js.map +1 -0
  21. package/dist/lib/signer.d.ts +12 -0
  22. package/dist/lib/signer.d.ts.map +1 -0
  23. package/dist/lib/signer.js +31 -0
  24. package/dist/lib/signer.js.map +1 -0
  25. package/dist/lib/subgraph.d.ts +2 -2
  26. package/dist/lib/subgraph.d.ts.map +1 -1
  27. package/dist/lib/subgraph.js +14 -9
  28. package/dist/lib/subgraph.js.map +1 -1
  29. package/dist/tools/daoDeploy.d.ts.map +1 -1
  30. package/dist/tools/daoDeploy.js +313 -63
  31. package/dist/tools/daoDeploy.js.map +1 -1
  32. package/dist/tools/flow.d.ts +62 -0
  33. package/dist/tools/flow.d.ts.map +1 -0
  34. package/dist/tools/flow.js +479 -0
  35. package/dist/tools/flow.js.map +1 -0
  36. package/dist/tools/index.d.ts.map +1 -1
  37. package/dist/tools/index.js +12 -0
  38. package/dist/tools/index.js.map +1 -1
  39. package/dist/tools/ipfs.d.ts.map +1 -1
  40. package/dist/tools/ipfs.js +94 -28
  41. package/dist/tools/ipfs.js.map +1 -1
  42. package/dist/tools/merkle.d.ts +4 -0
  43. package/dist/tools/merkle.d.ts.map +1 -0
  44. package/dist/tools/merkle.js +174 -0
  45. package/dist/tools/merkle.js.map +1 -0
  46. package/dist/tools/otc.d.ts +5 -0
  47. package/dist/tools/otc.d.ts.map +1 -0
  48. package/dist/tools/otc.js +481 -0
  49. package/dist/tools/otc.js.map +1 -0
  50. package/dist/tools/proposal.js +28 -10
  51. package/dist/tools/proposal.js.map +1 -1
  52. package/dist/tools/proposalBuild.d.ts.map +1 -1
  53. package/dist/tools/proposalBuild.js +28 -13
  54. package/dist/tools/proposalBuild.js.map +1 -1
  55. package/dist/tools/proposalBuildComplex.d.ts +222 -0
  56. package/dist/tools/proposalBuildComplex.d.ts.map +1 -1
  57. package/dist/tools/proposalBuildComplex.js +511 -138
  58. package/dist/tools/proposalBuildComplex.js.map +1 -1
  59. package/dist/tools/proposalBuildInternal.js +24 -11
  60. package/dist/tools/proposalBuildInternal.js.map +1 -1
  61. package/dist/tools/proposalBuildMore.js +42 -21
  62. package/dist/tools/proposalBuildMore.js.map +1 -1
  63. package/dist/tools/proposalBuildOffchain.d.ts.map +1 -1
  64. package/dist/tools/proposalBuildOffchain.js +8 -5
  65. package/dist/tools/proposalBuildOffchain.js.map +1 -1
  66. package/dist/tools/read.d.ts.map +1 -1
  67. package/dist/tools/read.js +227 -0
  68. package/dist/tools/read.js.map +1 -1
  69. package/dist/tools/subgraph.d.ts +4 -0
  70. package/dist/tools/subgraph.d.ts.map +1 -0
  71. package/dist/tools/subgraph.js +404 -0
  72. package/dist/tools/subgraph.js.map +1 -0
  73. package/dist/tools/txSend.d.ts +5 -0
  74. package/dist/tools/txSend.d.ts.map +1 -0
  75. package/dist/tools/txSend.js +87 -0
  76. package/dist/tools/txSend.js.map +1 -0
  77. package/dist/tools/voteBuild.d.ts.map +1 -1
  78. package/dist/tools/voteBuild.js +420 -0
  79. package/dist/tools/voteBuild.js.map +1 -1
  80. package/package.json +83 -75
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
- import { Interface, isAddress, ZeroAddress } from "ethers";
2
+ import { AbiCoder, Interface, isAddress, ZeroAddress, getAddress } from "ethers";
3
+ import { buildAddressMerkleTree } from "../lib/merkleTree.js";
3
4
  /**
4
5
  * Phase 3c — 10 complex named wrappers. Same contract as 3a/3b:
5
6
  * every wrapper returns `{ metadata, actions: Action[] }`. Actions are fed
@@ -15,10 +16,51 @@ import { Interface, isAddress, ZeroAddress } from "ethers";
15
16
  const DISTRIBUTION_PROPOSAL_ABI = [
16
17
  "function execute(uint256 proposalId, address token, uint256 amount)",
17
18
  ];
18
- const TOKEN_SALE_PROPOSAL_ABI = [
19
- "function createTiers(tuple(tuple(string name, string description) metadata, uint256 totalTokenProvided, uint256 saleStartTime, uint256 saleEndTime, address saleTokenAddress, uint256 claimLockDuration, address[] purchaseTokenAddresses, uint256[] exchangeRates, uint256 minAllocationPerUser, uint256 maxAllocationPerUser, tuple(uint256 cliffPeriod, uint256 unlockStep, uint256 vestingDuration, uint256 vestingPercentage) vestingSettings, tuple(uint8 participationType, bytes data)[] participationDetails)[] tiers)",
19
+ // Canonical TokenSaleProposal signatures — verified against
20
+ // `dexe_get_methods TokenSaleProposal` (selectors createTiers=0x6a6effda,
21
+ // addToWhitelist=0xce6c2d91, recover=0xc59b695a) and the interface at
22
+ // `contracts/interfaces/gov/proposals/ITokenSaleProposal.sol`.
23
+ export const TOKEN_SALE_PROPOSAL_ABI = [
24
+ "function createTiers(tuple(tuple(string name, string description) metadata, uint256 totalTokenProvided, uint64 saleStartTime, uint64 saleEndTime, uint64 claimLockDuration, address saleTokenAddress, address[] purchaseTokenAddresses, uint256[] exchangeRates, uint256 minAllocationPerUser, uint256 maxAllocationPerUser, tuple(uint256 vestingPercentage, uint64 vestingDuration, uint64 cliffPeriod, uint64 unlockStep) vestingSettings, tuple(uint8 participationType, bytes data)[] participationDetails)[] tiers)",
25
+ "function addToWhitelist(tuple(uint256 tierId, address[] users, string uri)[] requests)",
20
26
  "function recover(uint256[] tierIds)",
21
27
  ];
28
+ const PARTICIPATION_TYPE_INDEX = {
29
+ DAOVotes: 0,
30
+ Whitelist: 1,
31
+ BABT: 2,
32
+ TokenLock: 3,
33
+ NftLock: 4,
34
+ MerkleWhitelist: 5,
35
+ };
36
+ const participationSchema = z.discriminatedUnion("type", [
37
+ z.object({ type: z.literal("DAOVotes"), requiredVotes: z.string() }),
38
+ z.object({
39
+ type: z.literal("Whitelist"),
40
+ users: z.array(z.string()).default([]),
41
+ uri: z.string().default(""),
42
+ }),
43
+ z.object({ type: z.literal("BABT") }),
44
+ z.object({
45
+ type: z.literal("TokenLock"),
46
+ token: z.string(),
47
+ amount: z.string(),
48
+ }),
49
+ z.object({
50
+ type: z.literal("NftLock"),
51
+ nft: z.string(),
52
+ amount: z.string(),
53
+ }),
54
+ z.object({
55
+ type: z.literal("MerkleWhitelist"),
56
+ users: z.array(z.string()).default([]),
57
+ uri: z.string().default(""),
58
+ root: z
59
+ .string()
60
+ .optional()
61
+ .describe("Optional pre-computed merkle root. If omitted but `users` is set, the tool computes it."),
62
+ }),
63
+ ]);
22
64
  const STAKING_PROPOSAL_ABI = [
23
65
  "function createStaking(address rewardToken, uint256 rewardAmount, uint256 startedAt, uint256 deadline, string metadata)",
24
66
  ];
@@ -31,11 +73,12 @@ const ERC20_GOV_ABI = [
31
73
  "function blacklist(address[] users, bool isBlacklisted)",
32
74
  "function transfer(address to, uint256 amount) returns (bool)",
33
75
  "function mint(address to, uint256 amount)",
76
+ "function approve(address spender, uint256 amount) returns (bool)",
34
77
  ];
35
78
  const ERC721_MULTIPLIER_ABI = [
36
- "function setURI(uint256 tokenId, string uri)",
37
- "function mint(address to, uint256 multiplier)",
38
- "function mint(address to, uint256 multiplier, uint256 rewardPeriod)",
79
+ "function setTokenURI(uint256 tokenId, string uri)",
80
+ "function mint(address to, uint256 multiplier, uint256 rewardPeriod, string metadataUrl)",
81
+ "function changeToken(uint256 tokenId, uint256 multiplier, uint256 rewardPeriod)",
39
82
  ];
40
83
  const GOV_SETTINGS_FULL_ABI = [
41
84
  "function addSettings(tuple(bool earlyCompletion, bool delegatedVotingAllowed, bool validatorsVote, uint64 duration, uint64 durationValidators, uint64 executionDelay, uint128 quorum, uint128 quorumValidators, uint256 minVotesForVoting, uint256 minVotesForCreating, tuple(address rewardToken, uint256 creationReward, uint256 executionReward, uint256 voteRewardsCoefficient) rewardsInfo, string executorDescription)[] settings)",
@@ -67,6 +110,8 @@ function wrapperResult(params) {
67
110
  export function registerProposalBuildComplexTools(server, _ctx) {
68
111
  registerTokenDistribution(server);
69
112
  registerTokenSale(server);
113
+ registerTokenSaleMulti(server);
114
+ registerTokenSaleWhitelist(server);
70
115
  registerTokenSaleRecover(server);
71
116
  registerCreateStakingTier(server);
72
117
  registerChangeMathModel(server);
@@ -80,7 +125,7 @@ export function registerProposalBuildComplexTools(server, _ctx) {
80
125
  function registerTokenDistribution(server) {
81
126
  server.registerTool("dexe_proposal_build_token_distribution", {
82
127
  title: "Wrapper: batch token distribution via DistributionProposal",
83
- description: "Builds a 'Token Distribution' external proposal. Encodes `DistributionProposal.execute(proposalId, token, amount)`. `proposalId` is the DAO's latest proposalId + 1 (distribution executes against the pending proposal number). Returns a single action; if the token is an ERC20, the agent may also need to prepend an ERC20.approve action — flagged in the nextStep hint.",
128
+ description: "Builds a 'Token Distribution' external proposal. Encodes `DistributionProposal.execute(proposalId, token, amount)`. For ERC20 tokens, automatically prepends an `ERC20.approve` action. For native tokens (isNative=true), sets the action value instead. `proposalId` is the DAO's latest proposalId + 1.",
84
129
  inputSchema: {
85
130
  distributionProposal: z
86
131
  .string()
@@ -90,36 +135,48 @@ function registerTokenDistribution(server) {
90
135
  .describe("Expected proposalId for this distribution (usually latestProposalId + 1)"),
91
136
  token: z.string(),
92
137
  amount: z.string(),
138
+ isNative: z.boolean().default(false).describe("True for native token (BNB/ETH) — sends value instead of approve"),
93
139
  proposalName: z.string().default("Token Distribution"),
94
140
  proposalDescription: z.string().default(""),
95
141
  },
96
142
  outputSchema: payloadOutputSchema(),
97
- }, async ({ distributionProposal, proposalId, token, amount, proposalName = "Token Distribution", proposalDescription = "", }) => {
143
+ }, async ({ distributionProposal, proposalId, token, amount, isNative = false, proposalName = "Token Distribution", proposalDescription = "", }) => {
98
144
  if (!isAddress(distributionProposal))
99
145
  return errorResult(`Invalid distributionProposal: ${distributionProposal}`);
100
146
  if (!isAddress(token))
101
147
  return errorResult(`Invalid token: ${token}`);
102
148
  try {
103
- const iface = new Interface(DISTRIBUTION_PROPOSAL_ABI);
104
- const data = iface.encodeFunctionData("execute", [
149
+ const distIface = new Interface(DISTRIBUTION_PROPOSAL_ABI);
150
+ const executeData = distIface.encodeFunctionData("execute", [
105
151
  BigInt(proposalId),
106
152
  token,
107
153
  BigInt(amount),
108
154
  ]);
109
- const actions = [{ executor: distributionProposal, value: "0", data }];
155
+ const actions = [];
156
+ if (isNative) {
157
+ actions.push({ executor: distributionProposal, value: amount, data: executeData });
158
+ }
159
+ else {
160
+ const erc20Iface = new Interface(ERC20_GOV_ABI);
161
+ const approveData = erc20Iface.encodeFunctionData("approve", [distributionProposal, BigInt(amount)]);
162
+ actions.push({ executor: token, value: "0", data: approveData });
163
+ actions.push({ executor: distributionProposal, value: "0", data: executeData });
164
+ }
110
165
  const metadata = {
111
166
  proposalName,
112
- proposalDescription,
113
- category: "Token Distribution",
167
+ proposalDescription: JSON.stringify(proposalDescription),
168
+ category: "tokenDistribution",
114
169
  isMeta: false,
115
- proposedChanges: { tokenAddress: token, tokenAmount: amount, proposalId },
116
- currentChanges: {},
170
+ changes: {
171
+ proposedChanges: { tokenAddress: token, tokenAmount: amount, proposalId },
172
+ currentChanges: {},
173
+ },
117
174
  };
118
175
  return wrapperResult({
119
176
  metadata,
120
177
  actions,
121
178
  title: `Token Distribution → ${amount} of ${token} via proposal #${proposalId}`,
122
- detail: `Target: DistributionProposal(${distributionProposal}).execute\nERC20 approve may need to precede this action if token is non-native.`,
179
+ detail: `Target: DistributionProposal(${distributionProposal}).execute (${actions.length} actions)`,
123
180
  });
124
181
  }
125
182
  catch (err) {
@@ -128,86 +185,361 @@ function registerTokenDistribution(server) {
128
185
  });
129
186
  }
130
187
  // ---------- 2. token_sale ----------
188
+ // Vesting settings shape — keys match the contract's VestingSettings struct
189
+ // order (vestingPercentage, vestingDuration, cliffPeriod, unlockStep).
190
+ const vestingSchema = z
191
+ .object({
192
+ vestingPercentage: z.string().default("0"),
193
+ vestingDuration: z.string().default("0"),
194
+ cliffPeriod: z.string().default("0"),
195
+ unlockStep: z.string().default("0"),
196
+ })
197
+ .default({
198
+ vestingPercentage: "0",
199
+ vestingDuration: "0",
200
+ cliffPeriod: "0",
201
+ unlockStep: "0",
202
+ });
203
+ export const tierSchema = z.object({
204
+ name: z.string(),
205
+ description: z.string().default(""),
206
+ totalTokenProvided: z.string(),
207
+ saleStartTime: z.string().describe("Unix seconds"),
208
+ saleEndTime: z.string().describe("Unix seconds"),
209
+ claimLockDuration: z.string().default("0"),
210
+ saleTokenAddress: z.string(),
211
+ purchaseTokenAddresses: z.array(z.string()).min(1),
212
+ exchangeRates: z.array(z.string()).min(1),
213
+ minAllocationPerUser: z.string().default("0"),
214
+ maxAllocationPerUser: z.string().default("0"),
215
+ vestingSettings: vestingSchema,
216
+ participation: z
217
+ .array(participationSchema)
218
+ .default([])
219
+ .describe("Participation requirements (joined with AND on-chain). Leave empty for an open tier."),
220
+ });
221
+ const dataCoder = AbiCoder.defaultAbiCoder();
222
+ function encodeParticipationData(spec) {
223
+ const typeIdx = PARTICIPATION_TYPE_INDEX[spec.type];
224
+ switch (spec.type) {
225
+ case "DAOVotes": {
226
+ const data = dataCoder.encode(["uint256"], [BigInt(spec.requiredVotes)]);
227
+ return { detail: { type: typeIdx, data } };
228
+ }
229
+ case "Whitelist": {
230
+ // Plain whitelist: NFT-mint mode. Empty data; users are added via
231
+ // `addToWhitelist` in a follow-up action.
232
+ const users = (spec.users ?? []).map((u) => {
233
+ if (!isAddress(u))
234
+ throw new Error(`Invalid whitelist user: ${u}`);
235
+ return getAddress(u);
236
+ });
237
+ return {
238
+ detail: { type: typeIdx, data: "0x" },
239
+ whitelistUsers: users,
240
+ whitelistUri: spec.uri ?? "",
241
+ };
242
+ }
243
+ case "BABT": {
244
+ return { detail: { type: typeIdx, data: "0x" } };
245
+ }
246
+ case "TokenLock": {
247
+ if (!isAddress(spec.token))
248
+ throw new Error(`Invalid TokenLock token: ${spec.token}`);
249
+ const data = dataCoder.encode(["address", "uint256"], [getAddress(spec.token), BigInt(spec.amount)]);
250
+ return { detail: { type: typeIdx, data } };
251
+ }
252
+ case "NftLock": {
253
+ if (!isAddress(spec.nft))
254
+ throw new Error(`Invalid NftLock nft: ${spec.nft}`);
255
+ const data = dataCoder.encode(["address", "uint256"], [getAddress(spec.nft), BigInt(spec.amount)]);
256
+ return { detail: { type: typeIdx, data } };
257
+ }
258
+ case "MerkleWhitelist": {
259
+ let root = spec.root;
260
+ let users;
261
+ if (!root) {
262
+ if (!spec.users || spec.users.length === 0) {
263
+ throw new Error("MerkleWhitelist needs either `root` or non-empty `users`.");
264
+ }
265
+ users = spec.users.map((u) => {
266
+ if (!isAddress(u))
267
+ throw new Error(`Invalid MerkleWhitelist user: ${u}`);
268
+ return getAddress(u);
269
+ });
270
+ root = buildAddressMerkleTree(users).root;
271
+ }
272
+ const data = dataCoder.encode(["bytes32", "string"], [root, spec.uri ?? ""]);
273
+ return {
274
+ detail: { type: typeIdx, data },
275
+ derived: { root, users: users ?? [] },
276
+ };
277
+ }
278
+ }
279
+ }
280
+ export function buildTierTuple(tier) {
281
+ if (!isAddress(tier.saleTokenAddress)) {
282
+ throw new Error(`Invalid saleTokenAddress for tier "${tier.name}".`);
283
+ }
284
+ if (tier.purchaseTokenAddresses.length !== tier.exchangeRates.length) {
285
+ throw new Error(`Tier "${tier.name}": purchaseTokenAddresses and exchangeRates must be parallel arrays.`);
286
+ }
287
+ for (const pt of tier.purchaseTokenAddresses) {
288
+ if (!isAddress(pt))
289
+ throw new Error(`Tier "${tier.name}": invalid purchase token ${pt}.`);
290
+ }
291
+ const participationDetails = [];
292
+ let whitelistUsers = [];
293
+ let whitelistUri = "";
294
+ const derivedRoots = [];
295
+ for (const spec of tier.participation ?? []) {
296
+ const encoded = encodeParticipationData(spec);
297
+ participationDetails.push(encoded.detail);
298
+ if (encoded.whitelistUsers && encoded.whitelistUsers.length > 0) {
299
+ whitelistUsers = whitelistUsers.concat(encoded.whitelistUsers);
300
+ whitelistUri = encoded.whitelistUri ?? whitelistUri;
301
+ }
302
+ if (encoded.derived)
303
+ derivedRoots.push(encoded.derived);
304
+ }
305
+ const tuple = [
306
+ [tier.name, tier.description],
307
+ BigInt(tier.totalTokenProvided),
308
+ BigInt(tier.saleStartTime),
309
+ BigInt(tier.saleEndTime),
310
+ BigInt(tier.claimLockDuration),
311
+ getAddress(tier.saleTokenAddress),
312
+ tier.purchaseTokenAddresses.map((p) => getAddress(p)),
313
+ tier.exchangeRates.map((r) => BigInt(r)),
314
+ BigInt(tier.minAllocationPerUser),
315
+ BigInt(tier.maxAllocationPerUser),
316
+ [
317
+ BigInt(tier.vestingSettings.vestingPercentage),
318
+ BigInt(tier.vestingSettings.vestingDuration),
319
+ BigInt(tier.vestingSettings.cliffPeriod),
320
+ BigInt(tier.vestingSettings.unlockStep),
321
+ ],
322
+ participationDetails.map((d) => [d.type, d.data]),
323
+ ];
324
+ return { tuple, whitelistUsers, whitelistUri, derivedRoots };
325
+ }
326
+ export function buildSaleApprovals(tiers, tokenSaleProposal) {
327
+ // Sum total token provided per sale token (matches frontend's saleTokensMap
328
+ // reducer in `useGovPoolCreateTokenSaleProposal.ts`).
329
+ const totals = new Map();
330
+ for (const tier of tiers) {
331
+ const key = getAddress(tier.saleTokenAddress);
332
+ totals.set(key, (totals.get(key) ?? 0n) + BigInt(tier.totalTokenProvided));
333
+ }
334
+ const erc20Iface = new Interface(ERC20_GOV_ABI);
335
+ const actions = [];
336
+ for (const [token, amount] of totals.entries()) {
337
+ actions.push({
338
+ executor: token,
339
+ value: "0",
340
+ data: erc20Iface.encodeFunctionData("approve", [tokenSaleProposal, amount]),
341
+ });
342
+ }
343
+ return actions;
344
+ }
345
+ /**
346
+ * Pure builder for a multi-tier Token Sale proposal envelope. Used by both
347
+ * the `dexe_proposal_build_token_sale_multi` registrar and the OTC composite
348
+ * tools in `src/tools/otc.ts`.
349
+ */
350
+ export function buildTokenSaleMultiActions(input) {
351
+ const { tokenSaleProposal, tiers, latestTierId = "0", proposalName = "Token Sale", proposalDescription = "", } = input;
352
+ if (!isAddress(tokenSaleProposal)) {
353
+ throw new Error(`Invalid tokenSaleProposal: ${tokenSaleProposal}`);
354
+ }
355
+ const iface = new Interface(TOKEN_SALE_PROPOSAL_ABI);
356
+ const built = tiers.map((t) => buildTierTuple(t));
357
+ const whitelistRequests = [];
358
+ const baseTierId = BigInt(latestTierId);
359
+ built.forEach((w, i) => {
360
+ if (w.whitelistUsers.length > 0) {
361
+ whitelistRequests.push({
362
+ tierId: baseTierId + 1n + BigInt(i),
363
+ users: w.whitelistUsers,
364
+ uri: w.whitelistUri,
365
+ });
366
+ }
367
+ });
368
+ const tierTuples = built.map((b) => b.tuple);
369
+ const createData = iface.encodeFunctionData("createTiers", [tierTuples]);
370
+ const actions = [];
371
+ actions.push(...buildSaleApprovals(tiers, tokenSaleProposal));
372
+ actions.push({ executor: tokenSaleProposal, value: "0", data: createData });
373
+ if (whitelistRequests.length > 0) {
374
+ const wlData = iface.encodeFunctionData("addToWhitelist", [
375
+ whitelistRequests.map((r) => [r.tierId, r.users, r.uri]),
376
+ ]);
377
+ actions.push({ executor: tokenSaleProposal, value: "0", data: wlData });
378
+ }
379
+ const derivedMerkleRoots = built.flatMap((b) => b.derivedRoots);
380
+ const metadata = {
381
+ proposalName,
382
+ proposalDescription: JSON.stringify(proposalDescription),
383
+ category: "tokenSale",
384
+ isMeta: false,
385
+ changes: {
386
+ proposedChanges: { tiers, derivedMerkleRoots },
387
+ currentChanges: {},
388
+ },
389
+ };
390
+ return {
391
+ metadata,
392
+ actions,
393
+ derivedMerkleRoots,
394
+ whitelistRequests: whitelistRequests.map((r) => ({
395
+ tierId: r.tierId.toString(),
396
+ users: r.users,
397
+ uri: r.uri,
398
+ })),
399
+ tierNames: tiers.map((t) => t.name).join(", "),
400
+ };
401
+ }
402
+ function registerTokenSaleMulti(server) {
403
+ server.registerTool("dexe_proposal_build_token_sale_multi", {
404
+ title: "Build a multi-tier Token Sale proposal (createTiers + optional addToWhitelist)",
405
+ description: "Wraps `TokenSaleProposal.createTiers([...])` for one or more tiers. Each tier may declare zero or more participation requirements (DAOVotes, Whitelist, BABT, TokenLock, NftLock, MerkleWhitelist) — the data payload is encoded per-type to match `TokenSaleProposalCreate.sol`. ERC20 approves are summed and deduped per sale token. For tiers using plain `Whitelist`, the matching `addToWhitelist` action is appended automatically when users are supplied.",
406
+ inputSchema: {
407
+ tokenSaleProposal: z.string().describe("TokenSaleProposal contract address"),
408
+ tiers: z.array(tierSchema).min(1),
409
+ latestTierId: z
410
+ .string()
411
+ .default("0")
412
+ .describe("Current `latestTierId()` on TokenSaleProposal. Defaults to 0 — bump when extending an existing sale so addToWhitelist tier ids are correct."),
413
+ proposalName: z.string().default("Token Sale"),
414
+ proposalDescription: z.string().default(""),
415
+ },
416
+ outputSchema: payloadOutputSchema(),
417
+ }, async (input) => {
418
+ try {
419
+ const built = buildTokenSaleMultiActions(input);
420
+ return wrapperResult({
421
+ metadata: built.metadata,
422
+ actions: built.actions,
423
+ title: `Token Sale tiers → ${built.tierNames}`,
424
+ detail: `Target: TokenSaleProposal(${input.tokenSaleProposal}).createTiers (${input.tiers.length} tier${input.tiers.length === 1 ? "" : "s"}${built.whitelistRequests.length > 0
425
+ ? ` + addToWhitelist(${built.whitelistRequests.length})`
426
+ : ""})`,
427
+ });
428
+ }
429
+ catch (err) {
430
+ return errorResult(err instanceof Error ? err.message : String(err));
431
+ }
432
+ });
433
+ }
131
434
  function registerTokenSale(server) {
435
+ // Back-compat shim around the multi-tier builder. Same single-tier API as
436
+ // before, plus an optional `participation` field. Delegates encoding to
437
+ // `buildTierTuple` so calldata stays canonical.
132
438
  server.registerTool("dexe_proposal_build_token_sale", {
133
439
  title: "Wrapper: launch a token-sale tier via TokenSaleProposal.createTiers",
134
- description: "Builds a Token Sale proposal with a single tier. Covers the common case (basic tier, optional plain whitelist, no merkle participation). For advanced merkle-whitelist tiers, encode `participationDetails` externally and use `dexe_proposal_build_custom_abi` instead.",
440
+ description: "Builds a Token Sale proposal with a single tier. Forwards to `dexe_proposal_build_token_sale_multi` internally. For multi-tier sales or merkle whitelists, call `_multi` directly.",
135
441
  inputSchema: {
136
442
  tokenSaleProposal: z.string().describe("TokenSaleProposal contract address"),
137
- tier: z.object({
138
- name: z.string(),
139
- description: z.string().default(""),
140
- totalTokenProvided: z.string(),
141
- saleStartTime: z.string().describe("Unix seconds"),
142
- saleEndTime: z.string().describe("Unix seconds"),
143
- saleTokenAddress: z.string(),
144
- claimLockDuration: z.string().default("0"),
145
- purchaseTokenAddresses: z.array(z.string()).min(1),
146
- exchangeRates: z.array(z.string()).min(1),
147
- minAllocationPerUser: z.string().default("0"),
148
- maxAllocationPerUser: z.string().default("0"),
149
- vestingSettings: z
150
- .object({
151
- cliffPeriod: z.string().default("0"),
152
- unlockStep: z.string().default("0"),
153
- vestingDuration: z.string().default("0"),
154
- vestingPercentage: z.string().default("0"),
155
- })
156
- .default({
157
- cliffPeriod: "0",
158
- unlockStep: "0",
159
- vestingDuration: "0",
160
- vestingPercentage: "0",
161
- }),
162
- }),
443
+ tier: tierSchema,
444
+ latestTierId: z.string().default("0"),
163
445
  proposalName: z.string().default("Token Sale"),
164
446
  proposalDescription: z.string().default(""),
165
447
  },
166
448
  outputSchema: payloadOutputSchema(),
167
- }, async ({ tokenSaleProposal, tier, proposalName = "Token Sale", proposalDescription = "" }) => {
168
- if (!isAddress(tokenSaleProposal))
449
+ }, async ({ tokenSaleProposal, tier, latestTierId = "0", proposalName = "Token Sale", proposalDescription = "", }) => {
450
+ if (!isAddress(tokenSaleProposal)) {
169
451
  return errorResult(`Invalid tokenSaleProposal: ${tokenSaleProposal}`);
170
- if (!isAddress(tier.saleTokenAddress))
171
- return errorResult(`Invalid saleTokenAddress`);
172
- if (tier.purchaseTokenAddresses.length !== tier.exchangeRates.length) {
173
- return errorResult("purchaseTokenAddresses and exchangeRates must be parallel arrays");
174
452
  }
175
453
  try {
176
454
  const iface = new Interface(TOKEN_SALE_PROPOSAL_ABI);
177
- const tierTuple = [
178
- [tier.name, tier.description],
179
- BigInt(tier.totalTokenProvided),
180
- BigInt(tier.saleStartTime),
181
- BigInt(tier.saleEndTime),
182
- tier.saleTokenAddress,
183
- BigInt(tier.claimLockDuration),
184
- tier.purchaseTokenAddresses,
185
- tier.exchangeRates.map((r) => BigInt(r)),
186
- BigInt(tier.minAllocationPerUser),
187
- BigInt(tier.maxAllocationPerUser),
188
- [
189
- BigInt(tier.vestingSettings.cliffPeriod),
190
- BigInt(tier.vestingSettings.unlockStep),
191
- BigInt(tier.vestingSettings.vestingDuration),
192
- BigInt(tier.vestingSettings.vestingPercentage),
193
- ],
194
- [], // participationDetails — empty for the common case
195
- ];
196
- const data = iface.encodeFunctionData("createTiers", [[tierTuple]]);
197
- const actions = [{ executor: tokenSaleProposal, value: "0", data }];
455
+ const built = buildTierTuple(tier);
456
+ const actions = [];
457
+ actions.push(...buildSaleApprovals([tier], tokenSaleProposal));
458
+ actions.push({
459
+ executor: tokenSaleProposal,
460
+ value: "0",
461
+ data: iface.encodeFunctionData("createTiers", [[built.tuple]]),
462
+ });
463
+ if (built.whitelistUsers.length > 0) {
464
+ const baseTierId = BigInt(latestTierId);
465
+ const wlData = iface.encodeFunctionData("addToWhitelist", [
466
+ [[baseTierId + 1n, built.whitelistUsers, built.whitelistUri]],
467
+ ]);
468
+ actions.push({ executor: tokenSaleProposal, value: "0", data: wlData });
469
+ }
198
470
  const metadata = {
199
471
  proposalName,
200
- proposalDescription,
201
- category: "Token Sale",
472
+ proposalDescription: JSON.stringify(proposalDescription),
473
+ category: "tokenSale",
202
474
  isMeta: false,
203
- proposedChanges: { tier },
204
- currentChanges: {},
475
+ changes: {
476
+ proposedChanges: { tier, derivedMerkleRoots: built.derivedRoots },
477
+ currentChanges: {},
478
+ },
205
479
  };
206
480
  return wrapperResult({
207
481
  metadata,
208
482
  actions,
209
483
  title: `Token Sale tier → ${tier.name}`,
210
- detail: `Target: TokenSaleProposal(${tokenSaleProposal}).createTiers (1 tier)`,
484
+ detail: `Target: TokenSaleProposal(${tokenSaleProposal}).createTiers (1 tier${built.whitelistUsers.length > 0 ? " + addToWhitelist" : ""})`,
485
+ });
486
+ }
487
+ catch (err) {
488
+ return errorResult(err instanceof Error ? err.message : String(err));
489
+ }
490
+ });
491
+ }
492
+ function registerTokenSaleWhitelist(server) {
493
+ server.registerTool("dexe_proposal_build_token_sale_whitelist", {
494
+ title: "Build an addToWhitelist proposal for existing token-sale tiers",
495
+ description: "Builds an external proposal calling `TokenSaleProposal.addToWhitelist([{tierId, users, uri}, ...])`. Use this to extend the whitelist of a tier that's already live (plain `Whitelist` participation type only — merkle tiers are gated by their root, not this list).",
496
+ inputSchema: {
497
+ tokenSaleProposal: z.string(),
498
+ requests: z
499
+ .array(z.object({
500
+ tierId: z.string(),
501
+ users: z.array(z.string()).min(1),
502
+ uri: z.string().default(""),
503
+ }))
504
+ .min(1),
505
+ proposalName: z.string().default("Whitelist Token Sale Tier"),
506
+ proposalDescription: z.string().default(""),
507
+ },
508
+ outputSchema: payloadOutputSchema(),
509
+ }, async ({ tokenSaleProposal, requests, proposalName = "Whitelist Token Sale Tier", proposalDescription = "", }) => {
510
+ if (!isAddress(tokenSaleProposal)) {
511
+ return errorResult(`Invalid tokenSaleProposal: ${tokenSaleProposal}`);
512
+ }
513
+ try {
514
+ const normalised = requests.map((r) => {
515
+ for (const u of r.users) {
516
+ if (!isAddress(u))
517
+ throw new Error(`Invalid whitelist user: ${u}`);
518
+ }
519
+ return [
520
+ BigInt(r.tierId),
521
+ r.users.map((u) => getAddress(u)),
522
+ r.uri ?? "",
523
+ ];
524
+ });
525
+ const iface = new Interface(TOKEN_SALE_PROPOSAL_ABI);
526
+ const data = iface.encodeFunctionData("addToWhitelist", [normalised]);
527
+ const actions = [{ executor: tokenSaleProposal, value: "0", data }];
528
+ const metadata = {
529
+ proposalName,
530
+ proposalDescription: JSON.stringify(proposalDescription),
531
+ category: "tokenSale",
532
+ isMeta: false,
533
+ changes: {
534
+ proposedChanges: { requests },
535
+ currentChanges: {},
536
+ },
537
+ };
538
+ return wrapperResult({
539
+ metadata,
540
+ actions,
541
+ title: `addToWhitelist (${requests.length} request${requests.length === 1 ? "" : "s"})`,
542
+ detail: `Target: TokenSaleProposal(${tokenSaleProposal}).addToWhitelist`,
211
543
  });
212
544
  }
213
545
  catch (err) {
@@ -236,11 +568,13 @@ function registerTokenSaleRecover(server) {
236
568
  const actions = [{ executor: tokenSaleProposal, value: "0", data }];
237
569
  const metadata = {
238
570
  proposalName,
239
- proposalDescription,
240
- category: "Recover Token Sale",
571
+ proposalDescription: JSON.stringify(proposalDescription),
572
+ category: "recoverTokenSale",
241
573
  isMeta: false,
242
- proposedChanges: { tierIds },
243
- currentChanges: {},
574
+ changes: {
575
+ proposedChanges: { tierIds },
576
+ currentChanges: {},
577
+ },
244
578
  };
245
579
  return wrapperResult({
246
580
  metadata,
@@ -258,7 +592,7 @@ function registerTokenSaleRecover(server) {
258
592
  function registerCreateStakingTier(server) {
259
593
  server.registerTool("dexe_proposal_build_create_staking_tier", {
260
594
  title: "Wrapper: create a staking pool/tier via StakingProposal.createStaking",
261
- description: "Builds a 'Create Staking Tier' external proposal calling StakingProposal.createStaking(rewardToken, rewardAmount, startedAt, deadline, metadata).",
595
+ description: "Builds a 'Create Staking Tier' external proposal calling StakingProposal.createStaking(rewardToken, rewardAmount, startedAt, deadline, metadata). For ERC20 reward tokens, automatically prepends an ERC20.approve action. For native tokens (isNative=true), sets the action value instead.",
262
596
  inputSchema: {
263
597
  stakingProposal: z.string().describe("StakingProposal contract address"),
264
598
  rewardToken: z.string(),
@@ -266,38 +600,50 @@ function registerCreateStakingTier(server) {
266
600
  startedAt: z.string().describe("Unix seconds"),
267
601
  deadline: z.string().describe("Unix seconds"),
268
602
  stakingMetadataUrl: z.string().describe("ipfs://<cid> of staking-specific metadata"),
603
+ isNative: z.boolean().default(false).describe("True when reward token is native (BNB/ETH)"),
269
604
  proposalName: z.string().default("Create Staking"),
270
605
  proposalDescription: z.string().default(""),
271
606
  },
272
607
  outputSchema: payloadOutputSchema(),
273
- }, async ({ stakingProposal, rewardToken, rewardAmount, startedAt, deadline, stakingMetadataUrl, proposalName = "Create Staking", proposalDescription = "", }) => {
608
+ }, async ({ stakingProposal, rewardToken, rewardAmount, startedAt, deadline, stakingMetadataUrl, isNative = false, proposalName = "Create Staking", proposalDescription = "", }) => {
274
609
  if (!isAddress(stakingProposal))
275
610
  return errorResult(`Invalid stakingProposal: ${stakingProposal}`);
276
611
  if (!isAddress(rewardToken))
277
612
  return errorResult(`Invalid rewardToken: ${rewardToken}`);
278
613
  try {
279
614
  const iface = new Interface(STAKING_PROPOSAL_ABI);
280
- const data = iface.encodeFunctionData("createStaking", [
615
+ const createData = iface.encodeFunctionData("createStaking", [
281
616
  rewardToken,
282
617
  BigInt(rewardAmount),
283
618
  BigInt(startedAt),
284
619
  BigInt(deadline),
285
620
  stakingMetadataUrl,
286
621
  ]);
287
- const actions = [{ executor: stakingProposal, value: "0", data }];
622
+ const actions = [];
623
+ if (isNative) {
624
+ actions.push({ executor: stakingProposal, value: rewardAmount, data: createData });
625
+ }
626
+ else {
627
+ const erc20Iface = new Interface(ERC20_GOV_ABI);
628
+ const approveData = erc20Iface.encodeFunctionData("approve", [stakingProposal, BigInt(rewardAmount)]);
629
+ actions.push({ executor: rewardToken, value: "0", data: approveData });
630
+ actions.push({ executor: stakingProposal, value: "0", data: createData });
631
+ }
288
632
  const metadata = {
289
633
  proposalName,
290
- proposalDescription,
291
- category: "Create Staking",
634
+ proposalDescription: JSON.stringify(proposalDescription),
635
+ category: "createStakingTier",
292
636
  isMeta: false,
293
- proposedChanges: {
294
- rewardToken,
295
- rewardAmount,
296
- startedAt,
297
- deadline,
298
- metadata: stakingMetadataUrl,
637
+ changes: {
638
+ proposedChanges: {
639
+ rewardToken,
640
+ rewardAmount,
641
+ startedAt,
642
+ deadline,
643
+ metadata: stakingMetadataUrl,
644
+ },
645
+ currentChanges: {},
299
646
  },
300
- currentChanges: {},
301
647
  };
302
648
  return wrapperResult({
303
649
  metadata,
@@ -334,11 +680,13 @@ function registerChangeMathModel(server) {
334
680
  const actions = [{ executor: govPool, value: "0", data }];
335
681
  const metadata = {
336
682
  proposalName,
337
- proposalDescription,
338
- category: "Change Math Model",
683
+ proposalDescription: JSON.stringify(proposalDescription),
684
+ category: "mathModel",
339
685
  isMeta: false,
340
- proposedChanges: { newVotePower },
341
- currentChanges: {},
686
+ changes: {
687
+ proposedChanges: { newVotePower },
688
+ currentChanges: {},
689
+ },
342
690
  };
343
691
  return wrapperResult({
344
692
  metadata,
@@ -374,11 +722,13 @@ function registerModifyDaoProfile(server) {
374
722
  const actions = [{ executor: govPool, value: "0", data }];
375
723
  const metadata = {
376
724
  proposalName,
377
- proposalDescription,
378
- category: "Modify DAO Profile",
725
+ proposalDescription: JSON.stringify(proposalDescription),
726
+ category: "daoProfileModification",
379
727
  isMeta: true,
380
- proposedChanges: { descriptionUrl: newDescriptionURL },
381
- currentChanges: { descriptionUrl: previousDescriptionURL ?? null },
728
+ changes: {
729
+ proposedChanges: { descriptionUrl: newDescriptionURL },
730
+ currentChanges: { descriptionUrl: previousDescriptionURL ?? null },
731
+ },
382
732
  };
383
733
  return wrapperResult({
384
734
  metadata,
@@ -434,11 +784,13 @@ function registerBlacklistManagement(server) {
434
784
  }
435
785
  const metadata = {
436
786
  proposalName,
437
- proposalDescription,
438
- category: "Blacklist Management",
787
+ proposalDescription: JSON.stringify(proposalDescription),
788
+ category: "blacklistManagement",
439
789
  isMeta: false,
440
- proposedChanges: { addBlacklist: addAddresses, removeBlacklist: removeAddresses },
441
- currentChanges: {},
790
+ changes: {
791
+ proposedChanges: { addBlacklist: addAddresses, removeBlacklist: removeAddresses },
792
+ currentChanges: {},
793
+ },
442
794
  };
443
795
  return wrapperResult({
444
796
  metadata,
@@ -456,18 +808,18 @@ function registerBlacklistManagement(server) {
456
808
  function registerRewardMultiplier(server) {
457
809
  server.registerTool("dexe_proposal_build_reward_multiplier", {
458
810
  title: "Wrapper: manage the DAO's reward-multiplier NFT contract",
459
- description: "Three modes: 'set_address' (call GovPool.setNftMultiplierAddress — pass ZERO to disable), 'set_uri' (call ERC721Multiplier.setURI on a tokenId), 'mint' (call ERC721Multiplier.mint). For DeXe's global multiplier NFT, mint uses (to, multiplier, rewardPeriod); others use (to, multiplier). Set `hasRewardPeriod: true` for the 3-arg variant.",
811
+ description: "Four modes: 'set_address' (GovPool.setNftMultiplierAddress — ZERO to disable), 'set_token_uri' (ERC721Multiplier.setTokenURI on a tokenId), 'mint' (ERC721Multiplier.mint(to, multiplier, rewardPeriod, metadataUrl)), 'change_token' (ERC721Multiplier.changeToken(tokenId, multiplier, rewardPeriod) modify existing NFT).",
460
812
  inputSchema: {
461
- mode: z.enum(["set_address", "set_uri", "mint"]),
813
+ mode: z.enum(["set_address", "set_token_uri", "mint", "change_token"]),
462
814
  govPool: z.string().optional(),
463
815
  nftMultiplierContract: z.string().optional(),
464
816
  newMultiplierAddress: z.string().optional().describe("For mode=set_address"),
465
- tokenId: z.string().optional().describe("For mode=set_uri"),
466
- uri: z.string().optional().describe("For mode=set_uri"),
817
+ tokenId: z.string().optional().describe("For mode=set_token_uri or change_token"),
818
+ uri: z.string().optional().describe("For mode=set_token_uri"),
467
819
  to: z.string().optional().describe("For mode=mint"),
468
- multiplier: z.string().optional().describe("For mode=mint"),
469
- rewardPeriod: z.string().optional().describe("For mode=mint (DeXeERC721Multiplier only)"),
470
- hasRewardPeriod: z.boolean().default(false),
820
+ multiplier: z.string().optional().describe("For mode=mint or change_token"),
821
+ rewardPeriod: z.string().default("0").describe("For mode=mint or change_token"),
822
+ metadataUrl: z.string().default("").describe("For mode=mint — metadata URI string"),
471
823
  proposalName: z.string().default("Reward Multiplier"),
472
824
  proposalDescription: z.string().default(""),
473
825
  },
@@ -489,22 +841,40 @@ function registerRewardMultiplier(server) {
489
841
  data: iface.encodeFunctionData("setNftMultiplierAddress", [addr]),
490
842
  });
491
843
  }
492
- else if (mode === "set_uri") {
844
+ else if (mode === "set_token_uri") {
493
845
  if (!input.nftMultiplierContract || !isAddress(input.nftMultiplierContract))
494
- return errorResult(`set_uri requires valid nftMultiplierContract`);
846
+ return errorResult(`set_token_uri requires valid nftMultiplierContract`);
495
847
  if (!input.tokenId)
496
- return errorResult(`set_uri requires tokenId`);
848
+ return errorResult(`set_token_uri requires tokenId`);
497
849
  if (input.uri === undefined)
498
- return errorResult(`set_uri requires uri`);
850
+ return errorResult(`set_token_uri requires uri`);
499
851
  const iface = new Interface(ERC721_MULTIPLIER_ABI);
500
852
  actions.push({
501
853
  executor: input.nftMultiplierContract,
502
854
  value: "0",
503
- data: iface.encodeFunctionData("setURI", [BigInt(input.tokenId), input.uri]),
855
+ data: iface.encodeFunctionData("setTokenURI", [BigInt(input.tokenId), input.uri]),
856
+ });
857
+ }
858
+ else if (mode === "change_token") {
859
+ if (!input.nftMultiplierContract || !isAddress(input.nftMultiplierContract))
860
+ return errorResult(`change_token requires valid nftMultiplierContract`);
861
+ if (!input.tokenId)
862
+ return errorResult(`change_token requires tokenId`);
863
+ if (!input.multiplier)
864
+ return errorResult(`change_token requires multiplier`);
865
+ const iface = new Interface(ERC721_MULTIPLIER_ABI);
866
+ actions.push({
867
+ executor: input.nftMultiplierContract,
868
+ value: "0",
869
+ data: iface.encodeFunctionData("changeToken", [
870
+ BigInt(input.tokenId),
871
+ BigInt(input.multiplier),
872
+ BigInt(input.rewardPeriod ?? "0"),
873
+ ]),
504
874
  });
505
875
  }
506
876
  else {
507
- // mint
877
+ // mint — 4-arg: mint(address, uint256, uint256, string)
508
878
  if (!input.nftMultiplierContract || !isAddress(input.nftMultiplierContract))
509
879
  return errorResult(`mint requires valid nftMultiplierContract`);
510
880
  if (!input.to || !isAddress(input.to))
@@ -512,27 +882,26 @@ function registerRewardMultiplier(server) {
512
882
  if (!input.multiplier)
513
883
  return errorResult(`mint requires multiplier`);
514
884
  const iface = new Interface(ERC721_MULTIPLIER_ABI);
515
- const args = [input.to, BigInt(input.multiplier)];
516
- let sig = "mint(address,uint256)";
517
- if (input.hasRewardPeriod) {
518
- if (!input.rewardPeriod)
519
- return errorResult(`hasRewardPeriod requires rewardPeriod`);
520
- args.push(BigInt(input.rewardPeriod));
521
- sig = "mint(address,uint256,uint256)";
522
- }
523
885
  actions.push({
524
886
  executor: input.nftMultiplierContract,
525
887
  value: "0",
526
- data: iface.encodeFunctionData(sig, args),
888
+ data: iface.encodeFunctionData("mint", [
889
+ input.to,
890
+ BigInt(input.multiplier),
891
+ BigInt(input.rewardPeriod ?? "0"),
892
+ input.metadataUrl ?? "",
893
+ ]),
527
894
  });
528
895
  }
529
896
  const metadata = {
530
897
  proposalName,
531
- proposalDescription,
532
- category: "Reward Multiplier",
898
+ proposalDescription: JSON.stringify(proposalDescription),
899
+ category: "rewardMultiplier",
533
900
  isMeta: false,
534
- proposedChanges: { ...input, mode },
535
- currentChanges: {},
901
+ changes: {
902
+ proposedChanges: { ...input, mode },
903
+ currentChanges: {},
904
+ },
536
905
  };
537
906
  return wrapperResult({
538
907
  metadata,
@@ -588,11 +957,13 @@ function registerApplyToDao(server) {
588
957
  }
589
958
  const metadata = {
590
959
  proposalName,
591
- proposalDescription,
592
- category: "Apply to DAO",
960
+ proposalDescription: JSON.stringify(proposalDescription),
961
+ category: "applyToDao",
593
962
  isMeta: false,
594
- proposedChanges: { receiver, tokenAmount: amount, tokenAddress: token },
595
- currentChanges: { treasuryBalance },
963
+ changes: {
964
+ proposedChanges: { receiver, tokenAmount: amount, tokenAddress: token },
965
+ currentChanges: { treasuryBalance },
966
+ },
596
967
  };
597
968
  return wrapperResult({
598
969
  metadata,
@@ -679,11 +1050,13 @@ function registerNewProposalType(server) {
679
1050
  ];
680
1051
  const metadata = {
681
1052
  proposalName,
682
- proposalDescription,
683
- category: "New Proposal Type",
1053
+ proposalDescription: JSON.stringify(proposalDescription),
1054
+ category: "createProposalType",
684
1055
  isMeta: false,
685
- proposedChanges: { settings, executors, newSettingId },
686
- currentChanges: {},
1056
+ changes: {
1057
+ proposedChanges: { settings, executors, newSettingId },
1058
+ currentChanges: {},
1059
+ },
687
1060
  };
688
1061
  return wrapperResult({
689
1062
  metadata,