@claritydao/midnight-agora-sdk 0.1.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/.prettierrc +1 -0
- package/LICENSE +0 -0
- package/README.md +1134 -0
- package/dist/browser-providers.d.ts +21 -0
- package/dist/browser-providers.d.ts.map +1 -0
- package/dist/browser-providers.js +147 -0
- package/dist/browser-providers.js.map +1 -0
- package/dist/common-types.d.ts +29 -0
- package/dist/common-types.d.ts.map +1 -0
- package/dist/common-types.js +2 -0
- package/dist/common-types.js.map +1 -0
- package/dist/errors.d.ts +67 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +108 -0
- package/dist/errors.js.map +1 -0
- package/dist/governor-api.d.ts +348 -0
- package/dist/governor-api.d.ts.map +1 -0
- package/dist/governor-api.js +716 -0
- package/dist/governor-api.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/manager.d.ts +87 -0
- package/dist/manager.d.ts.map +1 -0
- package/dist/manager.js +93 -0
- package/dist/manager.js.map +1 -0
- package/dist/types.d.ts +307 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +77 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/index.d.ts +34 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +57 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/validators.d.ts +52 -0
- package/dist/validators.d.ts.map +1 -0
- package/dist/validators.js +160 -0
- package/dist/validators.js.map +1 -0
- package/eslint.config.mjs +48 -0
- package/package.json +39 -0
- package/src/browser-providers.ts +274 -0
- package/src/common-types.ts +51 -0
- package/src/errors.ts +124 -0
- package/src/governor-api.ts +948 -0
- package/src/index.ts +51 -0
- package/src/manager.ts +203 -0
- package/src/types.ts +403 -0
- package/src/utils/index.ts +60 -0
- package/src/validators.ts +245 -0
- package/test/README.md +409 -0
- package/test/errors.test.ts +179 -0
- package/test/integration/governor-api.test.ts +370 -0
- package/test/mocks/providers.ts +130 -0
- package/test/types.test.ts +112 -0
- package/test/utils.test.ts +111 -0
- package/test/validators.test.ts +368 -0
- package/test/wasm-init.test.ts +132 -0
- package/tsconfig.json +18 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1,716 @@
|
|
|
1
|
+
import { GovernorPrivateStateId } from "./common-types";
|
|
2
|
+
import * as utils from "./utils/index";
|
|
3
|
+
import { deployContract, findDeployedContract } from "@midnight-ntwrk/midnight-js-contracts";
|
|
4
|
+
import { Contract, witnesses } from "@agora-dao-midnight/contract";
|
|
5
|
+
import { VoteChoice, ConfigPresets } from "./types";
|
|
6
|
+
import { validateDeploymentConfig, validateProposal, validateVoteChoice, validateAddress, validatePositiveBigInt } from "./validators";
|
|
7
|
+
import { InvalidParameterError } from "./errors";
|
|
8
|
+
/** @internal */
|
|
9
|
+
const governorContractInstance = new Contract(witnesses);
|
|
10
|
+
// Constants to replace magic numbers
|
|
11
|
+
/** @internal */
|
|
12
|
+
const MILLISECONDS_TO_SECONDS = 1000;
|
|
13
|
+
/** @internal */
|
|
14
|
+
const INITIAL_EFFECT_ID = BigInt(0);
|
|
15
|
+
/** @internal */
|
|
16
|
+
const DEFAULT_BIGINT_VALUE = BigInt(0);
|
|
17
|
+
/** @internal */
|
|
18
|
+
const getCurrentUnixTimestamp = () => BigInt(Math.floor(Date.now() / MILLISECONDS_TO_SECONDS));
|
|
19
|
+
/**
|
|
20
|
+
* Provides an implementation of {@link DeployedGovernorAPI} by adapting a deployed governor
|
|
21
|
+
* contract.
|
|
22
|
+
*/
|
|
23
|
+
export class GovernorAPI {
|
|
24
|
+
deployedContract;
|
|
25
|
+
providers;
|
|
26
|
+
logger;
|
|
27
|
+
/** @internal */
|
|
28
|
+
constructor(deployedContract, providers, logger) {
|
|
29
|
+
this.deployedContract = deployedContract;
|
|
30
|
+
this.providers = providers;
|
|
31
|
+
this.logger = logger;
|
|
32
|
+
this.deployedContractAddress =
|
|
33
|
+
deployedContract.deployTxData.public.contractAddress;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Gets the address of the current deployed contract.
|
|
37
|
+
*/
|
|
38
|
+
deployedContractAddress;
|
|
39
|
+
/**
|
|
40
|
+
* Creates a governance proposal.
|
|
41
|
+
*
|
|
42
|
+
* @param title The proposal title (max 256 characters).
|
|
43
|
+
* @param description The proposal description (max 10,000 characters).
|
|
44
|
+
* @param creator The creator's address (32 bytes).
|
|
45
|
+
* @param actions Array of proposal actions to execute if passed.
|
|
46
|
+
* @returns Transaction data and proposal ID.
|
|
47
|
+
*
|
|
48
|
+
* @throws {InvalidParameterError} If title, description, or creator are invalid.
|
|
49
|
+
* @throws {InsufficientStakeError} If creator doesn't have sufficient stake.
|
|
50
|
+
*
|
|
51
|
+
* @remarks
|
|
52
|
+
* The creator must have sufficient stake as defined by the governance config's proposalThreshold.
|
|
53
|
+
* This method will automatically retrieve the creator's current stake from the contract.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```typescript
|
|
57
|
+
* const actions: ProposalAction[] = [{
|
|
58
|
+
* typ: 0,
|
|
59
|
+
* token: tokenAddress,
|
|
60
|
+
* amount: BigInt(10000),
|
|
61
|
+
* to: recipientAddress,
|
|
62
|
+
* configKey: new Uint8Array(32),
|
|
63
|
+
* configValue: new Uint8Array(32),
|
|
64
|
+
* }];
|
|
65
|
+
*
|
|
66
|
+
* const { proposalId, txData } = await governorAPI.createProposal(
|
|
67
|
+
* 'Fund Community Initiative',
|
|
68
|
+
* 'Transfer 10,000 tokens to community wallet',
|
|
69
|
+
* creatorAddress,
|
|
70
|
+
* actions
|
|
71
|
+
* );
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
async createProposal(title, description, creator, actions) {
|
|
75
|
+
this.logger?.info(`Creating proposal: ${title}`);
|
|
76
|
+
this.logger?.info(`Description: ${description}`);
|
|
77
|
+
// Validate parameters
|
|
78
|
+
validateProposal(title, description, creator);
|
|
79
|
+
if (!Array.isArray(actions) || actions.length === 0) {
|
|
80
|
+
throw new InvalidParameterError("actions", "must be a non-empty array of ProposalAction");
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
// Get creator's current stake
|
|
84
|
+
const stakeInfo = await this.deployedContract.callTx.getStakeInfo(creator);
|
|
85
|
+
const stakeAmount = stakeInfo.private.result.stakedAmount;
|
|
86
|
+
this.logger?.info(`Creator stake amount: ${stakeAmount}`);
|
|
87
|
+
const stakeWitness = {
|
|
88
|
+
userId: creator,
|
|
89
|
+
amount: stakeAmount,
|
|
90
|
+
nonce: utils.randomBytes(32)
|
|
91
|
+
};
|
|
92
|
+
const currentTime = getCurrentUnixTimestamp();
|
|
93
|
+
this.logger?.info(`Current time: ${currentTime}`);
|
|
94
|
+
const createdProposal = await this.deployedContract.callTx.createProposalWithEffect(creator, title, description, actions, stakeWitness, currentTime, currentTime + 300n);
|
|
95
|
+
const proposalId = createdProposal.private.result;
|
|
96
|
+
this.logger?.info(`Proposal created with ID: ${proposalId}, txHash: ${createdProposal.public.txHash}`);
|
|
97
|
+
this.logger?.trace({
|
|
98
|
+
transactionAdded: {
|
|
99
|
+
circuit: "createProposalWithEffect",
|
|
100
|
+
txHash: createdProposal.public.txHash,
|
|
101
|
+
blockHeight: createdProposal.public.blockHeight
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
return {
|
|
105
|
+
txData: createdProposal.public,
|
|
106
|
+
proposalId: proposalId
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
this.logger?.error(`Error creating proposal: ${error}`);
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Casts a vote on a proposal.
|
|
116
|
+
*
|
|
117
|
+
* @param proposalId The proposal ID to vote on (must be > 0).
|
|
118
|
+
* @param voter The voter's address (32 bytes).
|
|
119
|
+
* @param choice The vote choice: 0 (For), 1 (Against), or 2 (Abstain). Use VoteChoice enum for type safety.
|
|
120
|
+
* @returns Transaction data.
|
|
121
|
+
*
|
|
122
|
+
* @throws {InvalidParameterError} If proposalId, voter, or choice are invalid.
|
|
123
|
+
* @throws {InsufficientStakeError} If voter doesn't have sufficient stake.
|
|
124
|
+
*
|
|
125
|
+
* @remarks
|
|
126
|
+
* The voter must have voting power (non-zero stake). This method will automatically retrieve
|
|
127
|
+
* the voter's current stake from the contract.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```typescript
|
|
131
|
+
* import { VoteChoice } from '@agora-dao-midnight/sdk';
|
|
132
|
+
*
|
|
133
|
+
* await governorAPI.castVote(
|
|
134
|
+
* proposalId,
|
|
135
|
+
* voterAddress,
|
|
136
|
+
* VoteChoice.For // Type-safe vote choice
|
|
137
|
+
* );
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
async castVote(proposalId, voter, choice) {
|
|
141
|
+
this.logger?.info(`Casting vote on proposal ${proposalId}`);
|
|
142
|
+
this.logger?.info(`Vote choice: ${choice === VoteChoice.For ? "For" : choice === VoteChoice.Against ? "Against" : "Abstain"}`);
|
|
143
|
+
// Validate parameters
|
|
144
|
+
validatePositiveBigInt(proposalId, "proposalId");
|
|
145
|
+
validateAddress(voter, "voter");
|
|
146
|
+
validateVoteChoice(choice);
|
|
147
|
+
// Get voter's current stake
|
|
148
|
+
const stakeInfo = await this.deployedContract.callTx.getStakeInfo(voter);
|
|
149
|
+
const stakeAmount = stakeInfo.private.result.stakedAmount;
|
|
150
|
+
this.logger?.info(`Voter stake amount: ${stakeAmount}`);
|
|
151
|
+
const stakeWitness = {
|
|
152
|
+
userId: voter,
|
|
153
|
+
amount: stakeAmount,
|
|
154
|
+
nonce: utils.randomBytes(32)
|
|
155
|
+
};
|
|
156
|
+
const tx = await this.deployedContract.callTx.castVoteWithValidation(proposalId, voter, choice, stakeWitness);
|
|
157
|
+
this.logger?.info(`Vote cast with txHash: ${tx.public.txHash}`);
|
|
158
|
+
this.logger?.trace({
|
|
159
|
+
transactionAdded: {
|
|
160
|
+
circuit: "castVoteWithValidation",
|
|
161
|
+
txHash: tx.public.txHash,
|
|
162
|
+
blockHeight: tx.public.blockHeight
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
return tx.public;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Finalizes a proposal after voting period ends.
|
|
169
|
+
*
|
|
170
|
+
* @param proposalId The proposal ID to finalize.
|
|
171
|
+
* @returns Transaction data.
|
|
172
|
+
*
|
|
173
|
+
* @remarks
|
|
174
|
+
* This should be called after the voting period has ended.
|
|
175
|
+
*/
|
|
176
|
+
async finalizeProposal(proposalId) {
|
|
177
|
+
this.logger?.info(`Finalizing proposal ${proposalId}`);
|
|
178
|
+
const proofOfGovernorApproval = {
|
|
179
|
+
proposalId: proposalId,
|
|
180
|
+
effectId: INITIAL_EFFECT_ID,
|
|
181
|
+
approvalTime: getCurrentUnixTimestamp(),
|
|
182
|
+
nonce: utils.randomBytes(32)
|
|
183
|
+
};
|
|
184
|
+
const tx = await this.deployedContract.callTx.finalizeProposalWithValidation(proposalId, proofOfGovernorApproval);
|
|
185
|
+
this.logger?.info(`Proposal finalized with txHash: ${tx.public.txHash}`);
|
|
186
|
+
return tx.public;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Queues a finalized proposal for execution.
|
|
190
|
+
*
|
|
191
|
+
* @param proposalId The proposal ID to queue.
|
|
192
|
+
* @returns Transaction data.
|
|
193
|
+
*
|
|
194
|
+
* @remarks
|
|
195
|
+
* This should be called after the proposal has been finalized.
|
|
196
|
+
*/
|
|
197
|
+
async queueProposal(proposalId) {
|
|
198
|
+
this.logger?.info(`Queueing proposal ${proposalId}`);
|
|
199
|
+
const proofOfGovernorApproval = {
|
|
200
|
+
proposalId: proposalId,
|
|
201
|
+
effectId: INITIAL_EFFECT_ID,
|
|
202
|
+
approvalTime: getCurrentUnixTimestamp(),
|
|
203
|
+
nonce: utils.randomBytes(32)
|
|
204
|
+
};
|
|
205
|
+
const tx = await this.deployedContract.callTx.queueProposalWithValidation(proposalId, proofOfGovernorApproval);
|
|
206
|
+
this.logger?.info(`Proposal queued with txHash: ${tx.public.txHash}`);
|
|
207
|
+
return tx.public;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Executes a queued proposal.
|
|
211
|
+
*
|
|
212
|
+
* @param proposalId The proposal ID to execute.
|
|
213
|
+
* @param effectId The effect ID to execute.
|
|
214
|
+
* @returns Transaction data.
|
|
215
|
+
*
|
|
216
|
+
* @remarks
|
|
217
|
+
* This should be called after the execution delay has passed.
|
|
218
|
+
*/
|
|
219
|
+
async executeProposal(proposalId, effectId) {
|
|
220
|
+
this.logger?.info(`Executing proposal ${proposalId}`);
|
|
221
|
+
this.logger?.info(`Effect ID: ${effectId}`);
|
|
222
|
+
const proofOfGovernorApproval = {
|
|
223
|
+
proposalId: proposalId,
|
|
224
|
+
effectId: effectId,
|
|
225
|
+
approvalTime: getCurrentUnixTimestamp(),
|
|
226
|
+
nonce: utils.randomBytes(32)
|
|
227
|
+
};
|
|
228
|
+
const tx = await this.deployedContract.callTx.executeQueuedProposal(proposalId, effectId, proofOfGovernorApproval);
|
|
229
|
+
this.logger?.info(`Proposal executed with txHash: ${tx.public.txHash}`);
|
|
230
|
+
return tx.public;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Registers a stake for a user.
|
|
234
|
+
*
|
|
235
|
+
* @param userId The user's address (32 bytes).
|
|
236
|
+
* @param amount The amount to stake.
|
|
237
|
+
* @returns Transaction data.
|
|
238
|
+
*/
|
|
239
|
+
async registerStake(userId, amount) {
|
|
240
|
+
this.logger?.info(`Registering stake for user`);
|
|
241
|
+
this.logger?.info(`Amount: ${amount}`);
|
|
242
|
+
const proofOfStake = {
|
|
243
|
+
userId: userId,
|
|
244
|
+
amount: amount,
|
|
245
|
+
nonce: utils.randomBytes(32)
|
|
246
|
+
};
|
|
247
|
+
const tx = await this.deployedContract.callTx.registerStakeWithValidation(userId, amount, proofOfStake);
|
|
248
|
+
this.logger?.info(`Stake registered with txHash: ${tx.public.txHash}`);
|
|
249
|
+
return tx.public;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Gets stake information for a user.
|
|
253
|
+
*
|
|
254
|
+
* @param userId The user's address (32 bytes).
|
|
255
|
+
* @returns Stake information.
|
|
256
|
+
*/
|
|
257
|
+
async getStakeInfo(userId) {
|
|
258
|
+
this.logger?.info(`Getting stake info for user`);
|
|
259
|
+
try {
|
|
260
|
+
const stakeInfo = await this.deployedContract.callTx.getStakeInfo(userId);
|
|
261
|
+
this.logger?.info({
|
|
262
|
+
message: "Stake info retrieved",
|
|
263
|
+
txHash: stakeInfo.public.txHash,
|
|
264
|
+
blockHeight: stakeInfo.public.blockHeight
|
|
265
|
+
});
|
|
266
|
+
return stakeInfo;
|
|
267
|
+
}
|
|
268
|
+
catch (error) {
|
|
269
|
+
this.logger?.error(`Error getting stake info: ${error}`);
|
|
270
|
+
throw error;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Gets total delegated power for a user.
|
|
275
|
+
*
|
|
276
|
+
* @param userId The user's address (32 bytes).
|
|
277
|
+
* @returns Total delegated power.
|
|
278
|
+
*/
|
|
279
|
+
async getTotalDelegatedPower(userId) {
|
|
280
|
+
this.logger?.info(`Getting total delegated power for user`);
|
|
281
|
+
try {
|
|
282
|
+
const result = await this.deployedContract.callTx.getTotalDelegatedPower(userId);
|
|
283
|
+
this.logger?.info({
|
|
284
|
+
message: "Total delegated power retrieved",
|
|
285
|
+
txHash: result.public.txHash,
|
|
286
|
+
blockHeight: result.public.blockHeight
|
|
287
|
+
});
|
|
288
|
+
return result;
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
this.logger?.error(`Error getting total delegated power: ${error}`);
|
|
292
|
+
throw error;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Gets stake record for a user.
|
|
297
|
+
*
|
|
298
|
+
* @param userId The user's address (32 bytes).
|
|
299
|
+
* @returns Stake record.
|
|
300
|
+
*/
|
|
301
|
+
async getStakeRecord(userId) {
|
|
302
|
+
this.logger?.info(`Getting stake record for user`);
|
|
303
|
+
try {
|
|
304
|
+
const result = await this.deployedContract.callTx.getStakeRecord(userId);
|
|
305
|
+
this.logger?.info({
|
|
306
|
+
message: "Stake record retrieved",
|
|
307
|
+
txHash: result.public.txHash,
|
|
308
|
+
blockHeight: result.public.blockHeight
|
|
309
|
+
});
|
|
310
|
+
return result;
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
this.logger?.error(`Error getting stake record: ${error}`);
|
|
314
|
+
throw error;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Checks if user has sufficient stake to create proposals.
|
|
319
|
+
*
|
|
320
|
+
* @param userId The user's address (32 bytes).
|
|
321
|
+
* @returns Result indicating if user meets threshold.
|
|
322
|
+
*/
|
|
323
|
+
async hasProposalThreshold(userId) {
|
|
324
|
+
try {
|
|
325
|
+
const result = await this.deployedContract.callTx.hasProposalThreshold(userId);
|
|
326
|
+
return result;
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
this.logger?.error(`Error checking proposal threshold: ${error}`);
|
|
330
|
+
throw error;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Checks if user has voting power.
|
|
335
|
+
*
|
|
336
|
+
* @param userId The user's address (32 bytes).
|
|
337
|
+
* @returns Result indicating if user has voting power.
|
|
338
|
+
*/
|
|
339
|
+
async hasVotingPower(userId) {
|
|
340
|
+
try {
|
|
341
|
+
const result = await this.deployedContract.callTx.hasVotingPower(userId);
|
|
342
|
+
return result;
|
|
343
|
+
}
|
|
344
|
+
catch (error) {
|
|
345
|
+
this.logger?.error(`Error checking voting power: ${error}`);
|
|
346
|
+
throw error;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Gets proposal details by ID.
|
|
351
|
+
*
|
|
352
|
+
* @param proposalId The proposal ID.
|
|
353
|
+
* @returns Proposal details.
|
|
354
|
+
*/
|
|
355
|
+
async getProposalDetails(proposalId) {
|
|
356
|
+
this.logger?.info(`Getting proposal details for ID: ${proposalId}`);
|
|
357
|
+
try {
|
|
358
|
+
const result = await this.deployedContract.callTx.getProposal(proposalId);
|
|
359
|
+
const loggableResult = {
|
|
360
|
+
message: "Proposal details",
|
|
361
|
+
id: result.private.result.id?.toString(),
|
|
362
|
+
status: result.private.result.status,
|
|
363
|
+
votingStartTime: result.private.result.votingStartTime?.toString(),
|
|
364
|
+
votingEndTime: result.private.result.votingEndTime?.toString(),
|
|
365
|
+
creator: result.private.result.creator,
|
|
366
|
+
votesFor: result.private.result.votesFor?.toString(),
|
|
367
|
+
votesAgainst: result.private.result.votesAgainst?.toString(),
|
|
368
|
+
effectId: result.private.result.effectId?.toString()
|
|
369
|
+
};
|
|
370
|
+
this.logger?.info(loggableResult);
|
|
371
|
+
return result.private.result;
|
|
372
|
+
}
|
|
373
|
+
catch (error) {
|
|
374
|
+
this.logger?.error(`Error getting proposal details: ${error}`);
|
|
375
|
+
throw error;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Gets effect details by ID.
|
|
380
|
+
*
|
|
381
|
+
* @param effectId The effect ID.
|
|
382
|
+
* @returns Effect details.
|
|
383
|
+
*/
|
|
384
|
+
async getEffectDetails(effectId) {
|
|
385
|
+
this.logger?.info(`Getting effect details for ID: ${effectId}`);
|
|
386
|
+
try {
|
|
387
|
+
const result = await this.deployedContract.callTx.getEffect(effectId);
|
|
388
|
+
const loggableResult = {
|
|
389
|
+
message: "Effect details",
|
|
390
|
+
id: result.private.result.id?.toString(),
|
|
391
|
+
proposalId: result.private.result.proposalId?.toString(),
|
|
392
|
+
status: result.private.result.status,
|
|
393
|
+
executedAt: result.private.result.executedAt?.toString(),
|
|
394
|
+
actionsCount: result.private.result.actions?.length || 0
|
|
395
|
+
};
|
|
396
|
+
this.logger?.info(loggableResult);
|
|
397
|
+
return result.private.result;
|
|
398
|
+
}
|
|
399
|
+
catch (error) {
|
|
400
|
+
this.logger?.error(`Error getting effect details: ${error}`);
|
|
401
|
+
throw error;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Gets the count of active proposals.
|
|
406
|
+
*
|
|
407
|
+
* @returns Number of active proposals.
|
|
408
|
+
*/
|
|
409
|
+
async getActiveProposalCount() {
|
|
410
|
+
this.logger?.info(`Getting active proposal count`);
|
|
411
|
+
try {
|
|
412
|
+
if (!this.deployedContract?.callTx) {
|
|
413
|
+
this.logger?.warn("Governor contract or callTx is undefined");
|
|
414
|
+
return DEFAULT_BIGINT_VALUE;
|
|
415
|
+
}
|
|
416
|
+
const result = await this.deployedContract.callTx.getActiveProposalCount();
|
|
417
|
+
this.logger?.info(`Active proposals: ${result.private.result}`);
|
|
418
|
+
return result.private.result;
|
|
419
|
+
}
|
|
420
|
+
catch (error) {
|
|
421
|
+
this.logger?.warn(`Could not get active proposal count: ${error}`);
|
|
422
|
+
return DEFAULT_BIGINT_VALUE;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Gets the count of queued proposals.
|
|
427
|
+
*
|
|
428
|
+
* @returns Number of queued proposals.
|
|
429
|
+
*/
|
|
430
|
+
async getQueuedProposalCount() {
|
|
431
|
+
this.logger?.info(`Getting queued proposal count`);
|
|
432
|
+
try {
|
|
433
|
+
const result = await this.deployedContract.callTx.getQueuedProposalCount();
|
|
434
|
+
this.logger?.info(`Queued proposals: ${result.private.result}`);
|
|
435
|
+
return result.private.result;
|
|
436
|
+
}
|
|
437
|
+
catch (error) {
|
|
438
|
+
this.logger?.warn(`Could not get queued proposal count: ${error}`);
|
|
439
|
+
return DEFAULT_BIGINT_VALUE;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Gets vote count for a proposal.
|
|
444
|
+
*
|
|
445
|
+
* @param proposalId The proposal ID.
|
|
446
|
+
* @returns Vote count information.
|
|
447
|
+
*/
|
|
448
|
+
async getProposalVoteCount(proposalId) {
|
|
449
|
+
try {
|
|
450
|
+
const result = await this.deployedContract.callTx.getProposalVoteCount(proposalId);
|
|
451
|
+
return result;
|
|
452
|
+
}
|
|
453
|
+
catch (error) {
|
|
454
|
+
this.logger?.error(`Error getting proposal vote count: ${error}`);
|
|
455
|
+
throw error;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Gets the governor contract status.
|
|
460
|
+
*
|
|
461
|
+
* @returns Governor status information.
|
|
462
|
+
*/
|
|
463
|
+
async getGovernorStatus() {
|
|
464
|
+
this.logger?.info(`Getting governor status`);
|
|
465
|
+
try {
|
|
466
|
+
const result = await this.deployedContract.callTx.getGovernorStatus();
|
|
467
|
+
this.logger?.info(`Governor status retrieved`);
|
|
468
|
+
return result.private.result;
|
|
469
|
+
}
|
|
470
|
+
catch (error) {
|
|
471
|
+
this.logger?.warn(`Could not get governor status: ${error}`);
|
|
472
|
+
return null;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Gets the current time from the contract.
|
|
477
|
+
*
|
|
478
|
+
* @returns Current time.
|
|
479
|
+
*/
|
|
480
|
+
async getCurrentTime() {
|
|
481
|
+
try {
|
|
482
|
+
const result = await this.deployedContract.callTx.getCurrentTime();
|
|
483
|
+
return result;
|
|
484
|
+
}
|
|
485
|
+
catch (error) {
|
|
486
|
+
this.logger?.error(`Error getting current time: ${error}`);
|
|
487
|
+
throw error;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Gets the current contract time as a bigint.
|
|
492
|
+
*
|
|
493
|
+
* @returns Current contract time.
|
|
494
|
+
*/
|
|
495
|
+
async getCurrentContractTime() {
|
|
496
|
+
this.logger?.info(`Getting current contract time`);
|
|
497
|
+
try {
|
|
498
|
+
const status = await this.deployedContract.callTx.getGovernorStatus();
|
|
499
|
+
const currentTime = status.private.result.currentTime;
|
|
500
|
+
this.logger?.info(`Current contract time: ${currentTime}`);
|
|
501
|
+
return currentTime;
|
|
502
|
+
}
|
|
503
|
+
catch (error) {
|
|
504
|
+
this.logger?.error(`Error getting current contract time: ${error}`);
|
|
505
|
+
throw error;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Gets the treasury balance.
|
|
510
|
+
*
|
|
511
|
+
* @returns Treasury balance.
|
|
512
|
+
*
|
|
513
|
+
* @remarks
|
|
514
|
+
* This method queries the contract state to check if the contract exists.
|
|
515
|
+
* The actual treasury balance would need to be retrieved from the contract's
|
|
516
|
+
* public state if the contract implements treasury functionality.
|
|
517
|
+
*/
|
|
518
|
+
async getTreasuryBalance() {
|
|
519
|
+
this.logger?.info(`Getting treasury balance`);
|
|
520
|
+
try {
|
|
521
|
+
const state = await this.providers.publicDataProvider.queryContractState(this.deployedContractAddress);
|
|
522
|
+
if (state != null) {
|
|
523
|
+
this.logger?.info("Governor contract state found");
|
|
524
|
+
// Contract exists but doesn't currently expose treasury balance in public state
|
|
525
|
+
return DEFAULT_BIGINT_VALUE;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
catch (error) {
|
|
529
|
+
this.logger?.warn(`Could not query contract state: ${error}`);
|
|
530
|
+
}
|
|
531
|
+
return DEFAULT_BIGINT_VALUE;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Updates the contract time.
|
|
535
|
+
*
|
|
536
|
+
* @param newTime The new time to set.
|
|
537
|
+
* @returns Transaction data.
|
|
538
|
+
*
|
|
539
|
+
* @internal
|
|
540
|
+
* @deprecated This method is for testing purposes only. Do not use in production.
|
|
541
|
+
*
|
|
542
|
+
* @remarks
|
|
543
|
+
* WARNING: This method manipulates contract time and should only be used in testing environments
|
|
544
|
+
* to simulate time passing. Using this in production can have serious consequences for governance
|
|
545
|
+
* operations and proposal lifecycles.
|
|
546
|
+
*/
|
|
547
|
+
async updateTime(newTime) {
|
|
548
|
+
this.logger?.warn("WARNING: updateTime() is for testing only. Do not use in production!");
|
|
549
|
+
this.logger?.info(`Updating contract time to: ${newTime}`);
|
|
550
|
+
const tx = await this.deployedContract.callTx.storeTimestamp(newTime);
|
|
551
|
+
this.logger?.info(`Time updated with txHash: ${tx.public.txHash}`);
|
|
552
|
+
return tx.public;
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Displays comprehensive governor status.
|
|
556
|
+
*
|
|
557
|
+
* @returns Governor value information.
|
|
558
|
+
*/
|
|
559
|
+
async displayGovernorValue() {
|
|
560
|
+
const contractAddress = this.deployedContractAddress;
|
|
561
|
+
this.logger?.info(`Governor contract deployed at: ${contractAddress}`);
|
|
562
|
+
try {
|
|
563
|
+
const activeProposals = await this.getActiveProposalCount();
|
|
564
|
+
const queuedProposals = await this.getQueuedProposalCount();
|
|
565
|
+
const treasuryBalance = await this.getTreasuryBalance();
|
|
566
|
+
const status = await this.getGovernorStatus();
|
|
567
|
+
this.logger?.info(`Active proposals: ${Number(activeProposals)}`);
|
|
568
|
+
this.logger?.info(`Queued proposals: ${Number(queuedProposals)}`);
|
|
569
|
+
this.logger?.info(`Treasury balance: ${Number(treasuryBalance)}`);
|
|
570
|
+
this.logger?.info(`Current time: ${Number(status.currentTime)}`);
|
|
571
|
+
return {
|
|
572
|
+
contractAddress,
|
|
573
|
+
activeProposals,
|
|
574
|
+
queuedProposals,
|
|
575
|
+
treasuryBalance,
|
|
576
|
+
isActive: true
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
catch (error) {
|
|
580
|
+
this.logger?.error(`Error reading governor state: ${error}`);
|
|
581
|
+
return {
|
|
582
|
+
contractAddress,
|
|
583
|
+
activeProposals: DEFAULT_BIGINT_VALUE,
|
|
584
|
+
queuedProposals: DEFAULT_BIGINT_VALUE,
|
|
585
|
+
treasuryBalance: DEFAULT_BIGINT_VALUE,
|
|
586
|
+
isActive: false
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Deploys a new governor contract to the network.
|
|
592
|
+
*
|
|
593
|
+
* @param providers The governor providers.
|
|
594
|
+
* @param config The deployment configuration including governor, token, and admin settings.
|
|
595
|
+
* @param logger An optional logger to use for logging.
|
|
596
|
+
* @returns A `Promise` that resolves with a {@link GovernorAPI} instance that manages the newly deployed
|
|
597
|
+
* {@link DeployedGovernorContract}; or rejects with a deployment error.
|
|
598
|
+
*
|
|
599
|
+
* @example
|
|
600
|
+
* ```typescript
|
|
601
|
+
* import { GovernorAPI, ConfigPresets } from '@agora-dao-midnight/sdk';
|
|
602
|
+
* import { randomBytes } from '@midnight-ntwrk/midnight-js-utils';
|
|
603
|
+
*
|
|
604
|
+
* const config = {
|
|
605
|
+
* governor: {
|
|
606
|
+
* governance: ConfigPresets.production(),
|
|
607
|
+
* executionDelay: BigInt(172800),
|
|
608
|
+
* gracePeriod: BigInt(604800),
|
|
609
|
+
* maxActiveProposals: BigInt(100),
|
|
610
|
+
* proposalLifetime: BigInt(2592000),
|
|
611
|
+
* emergencyVetoEnabled: true,
|
|
612
|
+
* emergencyVetoThreshold: BigInt(75000),
|
|
613
|
+
* },
|
|
614
|
+
* token: {
|
|
615
|
+
* name: 'My DAO Token',
|
|
616
|
+
* symbol: 'MDAO',
|
|
617
|
+
* decimals: BigInt(18),
|
|
618
|
+
* },
|
|
619
|
+
* adminKey: randomBytes(32),
|
|
620
|
+
* };
|
|
621
|
+
*
|
|
622
|
+
* const governorAPI = await GovernorAPI.deploy(providers, config, logger);
|
|
623
|
+
* ```
|
|
624
|
+
*/
|
|
625
|
+
static async deploy(providers, config, logger) {
|
|
626
|
+
logger?.info("Deploying governor contract");
|
|
627
|
+
// Validate configuration
|
|
628
|
+
validateDeploymentConfig(config);
|
|
629
|
+
const args = [
|
|
630
|
+
config.governor,
|
|
631
|
+
config.token.name,
|
|
632
|
+
config.token.symbol,
|
|
633
|
+
config.token.decimals,
|
|
634
|
+
config.adminKey
|
|
635
|
+
];
|
|
636
|
+
const deployedGovernorContract = await deployContract(providers, {
|
|
637
|
+
privateStateId: GovernorPrivateStateId,
|
|
638
|
+
contract: governorContractInstance,
|
|
639
|
+
initialPrivateState: await GovernorAPI.getPrivateState(providers),
|
|
640
|
+
args
|
|
641
|
+
});
|
|
642
|
+
logger?.trace({
|
|
643
|
+
contractDeployed: {
|
|
644
|
+
finalizedDeployTxData: deployedGovernorContract.deployTxData.public
|
|
645
|
+
}
|
|
646
|
+
});
|
|
647
|
+
return new GovernorAPI(deployedGovernorContract, providers, logger);
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Finds an already deployed governor contract on the network, and joins it.
|
|
651
|
+
*
|
|
652
|
+
* @param providers The governor providers.
|
|
653
|
+
* @param contractAddress The contract address of the deployed governor contract to search for and join.
|
|
654
|
+
* @param logger An optional logger to use for logging.
|
|
655
|
+
* @returns A `Promise` that resolves with a {@link GovernorAPI} instance that manages the joined
|
|
656
|
+
* {@link DeployedGovernorContract}; or rejects with an error.
|
|
657
|
+
*/
|
|
658
|
+
static async join(providers, contractAddress, logger) {
|
|
659
|
+
logger?.info({
|
|
660
|
+
joinContract: {
|
|
661
|
+
contractAddress
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
const deployedGovernorContract = await findDeployedContract(providers, {
|
|
665
|
+
contractAddress,
|
|
666
|
+
contract: governorContractInstance,
|
|
667
|
+
privateStateId: GovernorPrivateStateId,
|
|
668
|
+
initialPrivateState: await GovernorAPI.getPrivateState(providers)
|
|
669
|
+
});
|
|
670
|
+
logger?.trace({
|
|
671
|
+
contractJoined: {
|
|
672
|
+
finalizedDeployTxData: deployedGovernorContract.deployTxData.public
|
|
673
|
+
}
|
|
674
|
+
});
|
|
675
|
+
return new GovernorAPI(deployedGovernorContract, providers, logger);
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Gets or initializes the private state for the governor contract.
|
|
679
|
+
*
|
|
680
|
+
* @param providers The governor providers.
|
|
681
|
+
* @returns The private state.
|
|
682
|
+
*
|
|
683
|
+
* @remarks
|
|
684
|
+
* This method retrieves existing private state from the provider, or creates new private state
|
|
685
|
+
* if none exists. The governance config in the private state is initialized with testing defaults
|
|
686
|
+
* and should be updated from the actual contract configuration after joining.
|
|
687
|
+
*/
|
|
688
|
+
static async getPrivateState(providers) {
|
|
689
|
+
const existingPrivateState = await providers.privateStateProvider.get(GovernorPrivateStateId);
|
|
690
|
+
if (existingPrivateState) {
|
|
691
|
+
return existingPrivateState;
|
|
692
|
+
}
|
|
693
|
+
const secretKey = utils.randomBytes(32);
|
|
694
|
+
const currentTime = getCurrentUnixTimestamp();
|
|
695
|
+
// Use testing config as default for private state initialization
|
|
696
|
+
// The actual governance config should be queried from the contract after joining
|
|
697
|
+
const defaultConfig = ConfigPresets.testing();
|
|
698
|
+
const newPrivateState = {
|
|
699
|
+
secretKey: secretKey,
|
|
700
|
+
currentTime: currentTime,
|
|
701
|
+
userStake: {
|
|
702
|
+
amount: DEFAULT_BIGINT_VALUE,
|
|
703
|
+
delegatedTo: new Uint8Array(32),
|
|
704
|
+
stakedAt: currentTime
|
|
705
|
+
},
|
|
706
|
+
votingHistory: [],
|
|
707
|
+
proposalHistory: [],
|
|
708
|
+
governanceConfig: defaultConfig
|
|
709
|
+
};
|
|
710
|
+
await providers.privateStateProvider.set(GovernorPrivateStateId, newPrivateState);
|
|
711
|
+
return newPrivateState;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
export * as utils from "./utils/index";
|
|
715
|
+
export * from "./common-types";
|
|
716
|
+
//# sourceMappingURL=governor-api.js.map
|