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.
- package/CHANGELOG.md +192 -85
- package/README.md +103 -80
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +10 -0
- package/dist/config.js.map +1 -1
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/addresses.js +1 -1
- package/dist/lib/ipfs.d.ts.map +1 -1
- package/dist/lib/ipfs.js +2 -1
- package/dist/lib/ipfs.js.map +1 -1
- package/dist/lib/markdownToSlate.d.ts +39 -0
- package/dist/lib/markdownToSlate.d.ts.map +1 -0
- package/dist/lib/markdownToSlate.js +203 -0
- package/dist/lib/markdownToSlate.js.map +1 -0
- package/dist/lib/merkleTree.d.ts +38 -0
- package/dist/lib/merkleTree.d.ts.map +1 -0
- package/dist/lib/merkleTree.js +83 -0
- package/dist/lib/merkleTree.js.map +1 -0
- package/dist/lib/signer.d.ts +12 -0
- package/dist/lib/signer.d.ts.map +1 -0
- package/dist/lib/signer.js +31 -0
- package/dist/lib/signer.js.map +1 -0
- package/dist/lib/subgraph.d.ts +2 -2
- package/dist/lib/subgraph.d.ts.map +1 -1
- package/dist/lib/subgraph.js +14 -9
- package/dist/lib/subgraph.js.map +1 -1
- package/dist/tools/daoDeploy.d.ts.map +1 -1
- package/dist/tools/daoDeploy.js +313 -63
- package/dist/tools/daoDeploy.js.map +1 -1
- package/dist/tools/flow.d.ts +62 -0
- package/dist/tools/flow.d.ts.map +1 -0
- package/dist/tools/flow.js +479 -0
- package/dist/tools/flow.js.map +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +12 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/ipfs.d.ts.map +1 -1
- package/dist/tools/ipfs.js +94 -28
- package/dist/tools/ipfs.js.map +1 -1
- package/dist/tools/merkle.d.ts +4 -0
- package/dist/tools/merkle.d.ts.map +1 -0
- package/dist/tools/merkle.js +174 -0
- package/dist/tools/merkle.js.map +1 -0
- package/dist/tools/otc.d.ts +5 -0
- package/dist/tools/otc.d.ts.map +1 -0
- package/dist/tools/otc.js +481 -0
- package/dist/tools/otc.js.map +1 -0
- package/dist/tools/proposal.js +28 -10
- package/dist/tools/proposal.js.map +1 -1
- package/dist/tools/proposalBuild.d.ts.map +1 -1
- package/dist/tools/proposalBuild.js +28 -13
- package/dist/tools/proposalBuild.js.map +1 -1
- package/dist/tools/proposalBuildComplex.d.ts +222 -0
- package/dist/tools/proposalBuildComplex.d.ts.map +1 -1
- package/dist/tools/proposalBuildComplex.js +511 -138
- package/dist/tools/proposalBuildComplex.js.map +1 -1
- package/dist/tools/proposalBuildInternal.js +24 -11
- package/dist/tools/proposalBuildInternal.js.map +1 -1
- package/dist/tools/proposalBuildMore.js +42 -21
- package/dist/tools/proposalBuildMore.js.map +1 -1
- package/dist/tools/proposalBuildOffchain.d.ts.map +1 -1
- package/dist/tools/proposalBuildOffchain.js +8 -5
- package/dist/tools/proposalBuildOffchain.js.map +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +227 -0
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/subgraph.d.ts +4 -0
- package/dist/tools/subgraph.d.ts.map +1 -0
- package/dist/tools/subgraph.js +404 -0
- package/dist/tools/subgraph.js.map +1 -0
- package/dist/tools/txSend.d.ts +5 -0
- package/dist/tools/txSend.d.ts.map +1 -0
- package/dist/tools/txSend.js +87 -0
- package/dist/tools/txSend.js.map +1 -0
- package/dist/tools/voteBuild.d.ts.map +1 -1
- package/dist/tools/voteBuild.js +420 -0
- package/dist/tools/voteBuild.js.map +1 -1
- 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
|
-
|
|
19
|
-
|
|
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
|
|
37
|
-
"function mint(address to, uint256 multiplier)",
|
|
38
|
-
"function
|
|
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)`.
|
|
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
|
|
104
|
-
const
|
|
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 = [
|
|
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: "
|
|
167
|
+
proposalDescription: JSON.stringify(proposalDescription),
|
|
168
|
+
category: "tokenDistribution",
|
|
114
169
|
isMeta: false,
|
|
115
|
-
|
|
116
|
-
|
|
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
|
|
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.
|
|
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:
|
|
138
|
-
|
|
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
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
BigInt(
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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: "
|
|
472
|
+
proposalDescription: JSON.stringify(proposalDescription),
|
|
473
|
+
category: "tokenSale",
|
|
202
474
|
isMeta: false,
|
|
203
|
-
|
|
204
|
-
|
|
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: "
|
|
571
|
+
proposalDescription: JSON.stringify(proposalDescription),
|
|
572
|
+
category: "recoverTokenSale",
|
|
241
573
|
isMeta: false,
|
|
242
|
-
|
|
243
|
-
|
|
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
|
|
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 = [
|
|
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: "
|
|
634
|
+
proposalDescription: JSON.stringify(proposalDescription),
|
|
635
|
+
category: "createStakingTier",
|
|
292
636
|
isMeta: false,
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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: "
|
|
683
|
+
proposalDescription: JSON.stringify(proposalDescription),
|
|
684
|
+
category: "mathModel",
|
|
339
685
|
isMeta: false,
|
|
340
|
-
|
|
341
|
-
|
|
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: "
|
|
725
|
+
proposalDescription: JSON.stringify(proposalDescription),
|
|
726
|
+
category: "daoProfileModification",
|
|
379
727
|
isMeta: true,
|
|
380
|
-
|
|
381
|
-
|
|
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: "
|
|
787
|
+
proposalDescription: JSON.stringify(proposalDescription),
|
|
788
|
+
category: "blacklistManagement",
|
|
439
789
|
isMeta: false,
|
|
440
|
-
|
|
441
|
-
|
|
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: "
|
|
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", "
|
|
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=
|
|
466
|
-
uri: z.string().optional().describe("For mode=
|
|
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().
|
|
470
|
-
|
|
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 === "
|
|
844
|
+
else if (mode === "set_token_uri") {
|
|
493
845
|
if (!input.nftMultiplierContract || !isAddress(input.nftMultiplierContract))
|
|
494
|
-
return errorResult(`
|
|
846
|
+
return errorResult(`set_token_uri requires valid nftMultiplierContract`);
|
|
495
847
|
if (!input.tokenId)
|
|
496
|
-
return errorResult(`
|
|
848
|
+
return errorResult(`set_token_uri requires tokenId`);
|
|
497
849
|
if (input.uri === undefined)
|
|
498
|
-
return errorResult(`
|
|
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("
|
|
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(
|
|
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: "
|
|
898
|
+
proposalDescription: JSON.stringify(proposalDescription),
|
|
899
|
+
category: "rewardMultiplier",
|
|
533
900
|
isMeta: false,
|
|
534
|
-
|
|
535
|
-
|
|
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: "
|
|
960
|
+
proposalDescription: JSON.stringify(proposalDescription),
|
|
961
|
+
category: "applyToDao",
|
|
593
962
|
isMeta: false,
|
|
594
|
-
|
|
595
|
-
|
|
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: "
|
|
1053
|
+
proposalDescription: JSON.stringify(proposalDescription),
|
|
1054
|
+
category: "createProposalType",
|
|
684
1055
|
isMeta: false,
|
|
685
|
-
|
|
686
|
-
|
|
1056
|
+
changes: {
|
|
1057
|
+
proposedChanges: { settings, executors, newSettingId },
|
|
1058
|
+
currentChanges: {},
|
|
1059
|
+
},
|
|
687
1060
|
};
|
|
688
1061
|
return wrapperResult({
|
|
689
1062
|
metadata,
|