@vocdoni/davinci-sdk 0.0.1

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/dist/index.mjs ADDED
@@ -0,0 +1,2000 @@
1
+ import axios from 'axios';
2
+ import { ProcessRegistry__factory, OrganizationRegistry__factory } from '@vocdoni/davinci-contracts';
3
+ import { groth16 } from 'snarkjs';
4
+ import { sha256 } from 'ethers';
5
+
6
+ var ElectionResultsTypeNames = /* @__PURE__ */ ((ElectionResultsTypeNames2) => {
7
+ ElectionResultsTypeNames2["SINGLE_CHOICE_MULTIQUESTION"] = "single-choice-multiquestion";
8
+ ElectionResultsTypeNames2["MULTIPLE_CHOICE"] = "multiple-choice";
9
+ ElectionResultsTypeNames2["BUDGET"] = "budget-based";
10
+ ElectionResultsTypeNames2["APPROVAL"] = "approval";
11
+ ElectionResultsTypeNames2["QUADRATIC"] = "quadratic";
12
+ return ElectionResultsTypeNames2;
13
+ })(ElectionResultsTypeNames || {});
14
+ const ElectionMetadataTemplate = {
15
+ version: "1.2",
16
+ title: {
17
+ default: ""
18
+ },
19
+ description: {
20
+ default: ""
21
+ },
22
+ media: {
23
+ header: "",
24
+ logo: ""
25
+ },
26
+ meta: {},
27
+ questions: [
28
+ {
29
+ title: {
30
+ default: ""
31
+ },
32
+ description: {
33
+ default: ""
34
+ },
35
+ meta: {},
36
+ choices: [
37
+ {
38
+ title: {
39
+ default: "Yes"
40
+ },
41
+ value: 0,
42
+ meta: {}
43
+ },
44
+ {
45
+ title: {
46
+ default: "No"
47
+ },
48
+ value: 1,
49
+ meta: {}
50
+ }
51
+ ]
52
+ }
53
+ ],
54
+ type: {
55
+ name: "single-choice-multiquestion" /* SINGLE_CHOICE_MULTIQUESTION */,
56
+ properties: {}
57
+ }
58
+ };
59
+ const getElectionMetadataTemplate = () => {
60
+ return JSON.parse(JSON.stringify(ElectionMetadataTemplate));
61
+ };
62
+
63
+ class BaseService {
64
+ constructor(baseURL, config) {
65
+ this.axios = axios.create({ baseURL, ...config });
66
+ }
67
+ async request(config) {
68
+ try {
69
+ const response = await this.axios.request(config);
70
+ return response.data;
71
+ } catch (err) {
72
+ const error = err;
73
+ const message = error.response?.data?.error || error.message;
74
+ const code = error.response?.data?.code || error.code || error.response?.status || 500;
75
+ const e = new Error(message);
76
+ e.code = code;
77
+ throw e;
78
+ }
79
+ }
80
+ }
81
+
82
+ function isUUId(str) {
83
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(str);
84
+ }
85
+ class VocdoniCensusService extends BaseService {
86
+ constructor(baseURL) {
87
+ super(baseURL);
88
+ }
89
+ /**
90
+ * Constructs the URI for accessing a census by its root
91
+ * @param censusRoot - The census root (hex-prefixed)
92
+ * @returns The constructed URI for the census
93
+ */
94
+ getCensusUri(censusRoot) {
95
+ return `${this.axios.defaults.baseURL}/censuses/${censusRoot}`;
96
+ }
97
+ createCensus() {
98
+ return this.request({
99
+ method: "POST",
100
+ url: "/censuses"
101
+ }).then((res) => res.census);
102
+ }
103
+ async addParticipants(censusId, participants) {
104
+ if (!isUUId(censusId)) throw new Error("Invalid census ID format");
105
+ await this.request({
106
+ method: "POST",
107
+ url: `/censuses/${censusId}/participants`,
108
+ data: { participants }
109
+ });
110
+ }
111
+ getParticipants(censusId) {
112
+ if (!isUUId(censusId)) throw new Error("Invalid census ID format");
113
+ return this.request({
114
+ method: "GET",
115
+ url: `/censuses/${censusId}/participants`
116
+ }).then((res) => res.participants);
117
+ }
118
+ getCensusRoot(censusId) {
119
+ if (!isUUId(censusId)) throw new Error("Invalid census ID format");
120
+ return this.request({
121
+ method: "GET",
122
+ url: `/censuses/${censusId}/root`
123
+ }).then((res) => res.root);
124
+ }
125
+ getCensusSizeById(censusId) {
126
+ if (!isUUId(censusId)) throw new Error("Invalid census ID format");
127
+ return this.request({
128
+ method: "GET",
129
+ url: `/censuses/${censusId}/size`
130
+ }).then((res) => res.size);
131
+ }
132
+ getCensusSizeByRoot(censusRoot) {
133
+ return this.request({
134
+ method: "GET",
135
+ url: `/censuses/${censusRoot}/size`
136
+ }).then((res) => res.size);
137
+ }
138
+ getCensusSize(censusIdOrRoot) {
139
+ if (isUUId(censusIdOrRoot)) {
140
+ return this.getCensusSizeById(censusIdOrRoot);
141
+ } else {
142
+ return this.getCensusSizeByRoot(censusIdOrRoot);
143
+ }
144
+ }
145
+ async deleteCensus(censusId) {
146
+ if (!isUUId(censusId)) throw new Error("Invalid census ID format");
147
+ await this.request({
148
+ method: "DELETE",
149
+ url: `/censuses/${censusId}`
150
+ });
151
+ }
152
+ getCensusProof(censusRoot, key) {
153
+ return this.request({
154
+ method: "GET",
155
+ url: `/censuses/${censusRoot}/proof`,
156
+ params: { key }
157
+ });
158
+ }
159
+ publishCensus(censusId) {
160
+ if (!isUUId(censusId)) throw new Error("Invalid census ID format");
161
+ return this.request({
162
+ method: "POST",
163
+ url: `/censuses/${censusId}/publish`
164
+ }).then((response) => ({
165
+ ...response,
166
+ uri: this.getCensusUri(response.root)
167
+ }));
168
+ }
169
+ // BigQuery endpoints
170
+ getSnapshots(params) {
171
+ return this.request({
172
+ method: "GET",
173
+ url: "/snapshots",
174
+ params
175
+ });
176
+ }
177
+ getLatestSnapshot() {
178
+ return this.request({
179
+ method: "GET",
180
+ url: "/snapshots/latest"
181
+ });
182
+ }
183
+ getHealth() {
184
+ return this.request({
185
+ method: "GET",
186
+ url: "/health"
187
+ });
188
+ }
189
+ }
190
+
191
+ var CensusOrigin = /* @__PURE__ */ ((CensusOrigin2) => {
192
+ CensusOrigin2[CensusOrigin2["CensusOriginMerkleTree"] = 1] = "CensusOriginMerkleTree";
193
+ CensusOrigin2[CensusOrigin2["CensusOriginCSP"] = 2] = "CensusOriginCSP";
194
+ return CensusOrigin2;
195
+ })(CensusOrigin || {});
196
+ function isBaseCensusProof(proof) {
197
+ return !!proof && typeof proof.root === "string" && typeof proof.address === "string" && typeof proof.weight === "string" && typeof proof.censusOrigin === "number" && Object.values(CensusOrigin).includes(proof.censusOrigin);
198
+ }
199
+ function isMerkleCensusProof(proof) {
200
+ return isBaseCensusProof(proof) && proof.censusOrigin === 1 /* CensusOriginMerkleTree */ && typeof proof.value === "string" && typeof proof.siblings === "string";
201
+ }
202
+ function isCSPCensusProof(proof) {
203
+ return isBaseCensusProof(proof) && proof.censusOrigin === 2 /* CensusOriginCSP */ && typeof proof.processId === "string" && typeof proof.publicKey === "string" && typeof proof.signature === "string";
204
+ }
205
+ function assertMerkleCensusProof(proof) {
206
+ if (!isMerkleCensusProof(proof)) {
207
+ throw new Error("Invalid Merkle census proof payload");
208
+ }
209
+ }
210
+ function assertCSPCensusProof(proof) {
211
+ if (!isCSPCensusProof(proof)) {
212
+ throw new Error("Invalid CSP census proof payload");
213
+ }
214
+ }
215
+
216
+ function createProcessSignatureMessage(processId) {
217
+ const cleanProcessId = processId.replace(/^0x/, "").toLowerCase();
218
+ return `I am creating a new voting process for the davinci.vote protocol identified with id ${cleanProcessId}`;
219
+ }
220
+ async function signProcessCreation(processId, signer) {
221
+ const message = createProcessSignatureMessage(processId);
222
+ return await signer.signMessage(message);
223
+ }
224
+ function validateProcessId(processId) {
225
+ const cleanId = processId.replace(/^0x/, "");
226
+ return /^[0-9a-fA-F]{64}$/.test(cleanId);
227
+ }
228
+
229
+ function isHexString(str) {
230
+ return /^0x[0-9a-f]{64}$/i.test(str);
231
+ }
232
+ class VocdoniSequencerService extends BaseService {
233
+ constructor(baseURL) {
234
+ super(baseURL);
235
+ }
236
+ async ping() {
237
+ await this.request({ method: "GET", url: "/ping" });
238
+ }
239
+ createProcess(body) {
240
+ if (!validateProcessId(body.processId)) {
241
+ throw new Error("Invalid processId format. Must be a 64-character hex string (32 bytes)");
242
+ }
243
+ return this.request({
244
+ method: "POST",
245
+ url: "/processes",
246
+ data: body
247
+ });
248
+ }
249
+ getProcess(processId) {
250
+ return this.request({
251
+ method: "GET",
252
+ url: `/processes/${processId}`
253
+ });
254
+ }
255
+ listProcesses() {
256
+ return this.request({
257
+ method: "GET",
258
+ url: "/processes"
259
+ }).then((res) => res.processes);
260
+ }
261
+ async submitVote(vote) {
262
+ await this.request({
263
+ method: "POST",
264
+ url: "/votes",
265
+ data: vote
266
+ });
267
+ }
268
+ getVoteStatus(processId, voteId) {
269
+ return this.request({
270
+ method: "GET",
271
+ url: `/votes/${processId}/voteId/${voteId}`
272
+ });
273
+ }
274
+ async hasAddressVoted(processId, address) {
275
+ try {
276
+ await this.request({
277
+ method: "GET",
278
+ url: `/votes/${processId}/address/${address}`
279
+ });
280
+ return true;
281
+ } catch (error) {
282
+ if (error?.code === 40001) {
283
+ return false;
284
+ }
285
+ throw error;
286
+ }
287
+ }
288
+ getInfo() {
289
+ return this.request({
290
+ method: "GET",
291
+ url: "/info"
292
+ });
293
+ }
294
+ pushMetadata(metadata) {
295
+ return this.request({
296
+ method: "POST",
297
+ url: "/metadata",
298
+ data: metadata
299
+ }).then((res) => res.hash);
300
+ }
301
+ async getMetadata(hashOrUrl) {
302
+ if (hashOrUrl.startsWith("http://") || hashOrUrl.startsWith("https://")) {
303
+ try {
304
+ const response = await fetch(hashOrUrl);
305
+ if (!response.ok) {
306
+ throw new Error(`Failed to fetch metadata from URL: ${response.status} ${response.statusText}`);
307
+ }
308
+ return await response.json();
309
+ } catch (error) {
310
+ throw new Error(`Failed to fetch metadata from URL: ${error instanceof Error ? error.message : "Unknown error"}`);
311
+ }
312
+ }
313
+ if (!isHexString(hashOrUrl)) {
314
+ throw new Error("Invalid metadata hash format");
315
+ }
316
+ return this.request({
317
+ method: "GET",
318
+ url: `/metadata/${hashOrUrl}`
319
+ });
320
+ }
321
+ getMetadataUrl(hash) {
322
+ if (!isHexString(hash)) throw new Error("Invalid metadata hash format");
323
+ return `${this.axios.defaults.baseURL}/metadata/${hash}`;
324
+ }
325
+ getStats() {
326
+ return this.request({
327
+ method: "GET",
328
+ url: "/sequencer/stats"
329
+ });
330
+ }
331
+ getWorkers() {
332
+ return this.request({
333
+ method: "GET",
334
+ url: "/sequencer/workers"
335
+ });
336
+ }
337
+ }
338
+
339
+ class VocdoniApiService {
340
+ constructor(config) {
341
+ this.sequencer = new VocdoniSequencerService(config.sequencerURL);
342
+ this.census = new VocdoniCensusService(config.censusURL);
343
+ }
344
+ }
345
+
346
+ const DEFAULT_ENVIRONMENT_URLS = {
347
+ dev: {
348
+ sequencer: "https://sequencer-dev.davinci.vote",
349
+ census: "https://c3-dev.davinci.vote",
350
+ chain: "sepolia"
351
+ },
352
+ stg: {
353
+ sequencer: "https://sequencer1.davinci.vote",
354
+ census: "https://c3.davinci.vote",
355
+ chain: "sepolia"
356
+ },
357
+ prod: {
358
+ // TODO: Add production URLs when available
359
+ sequencer: "",
360
+ census: "",
361
+ chain: "mainnet"
362
+ }
363
+ };
364
+ function getEnvironmentConfig(environment) {
365
+ return DEFAULT_ENVIRONMENT_URLS[environment];
366
+ }
367
+ function getEnvironmentUrls(environment) {
368
+ const config = DEFAULT_ENVIRONMENT_URLS[environment];
369
+ return {
370
+ sequencer: config.sequencer,
371
+ census: config.census
372
+ };
373
+ }
374
+ function getEnvironmentChain(environment) {
375
+ return DEFAULT_ENVIRONMENT_URLS[environment].chain;
376
+ }
377
+ function resolveConfiguration(options = {}) {
378
+ const environment = options.environment || "prod";
379
+ const defaultConfig = getEnvironmentConfig(environment);
380
+ const resolvedConfig = { ...defaultConfig };
381
+ if (options.customUrls) {
382
+ if (options.customUrls.sequencer !== void 0) {
383
+ resolvedConfig.sequencer = options.customUrls.sequencer;
384
+ }
385
+ if (options.customUrls.census !== void 0) {
386
+ resolvedConfig.census = options.customUrls.census;
387
+ }
388
+ }
389
+ if (options.customChain) {
390
+ resolvedConfig.chain = options.customChain;
391
+ }
392
+ return resolvedConfig;
393
+ }
394
+ function resolveUrls(options = {}) {
395
+ const config = resolveConfiguration(options);
396
+ return {
397
+ sequencer: config.sequencer,
398
+ census: config.census
399
+ };
400
+ }
401
+
402
+ var processRegistry = {
403
+ sepolia: "0x40939Ec9FD872eb79A1723B559572dfD71a05d11",
404
+ uzh: "0x69B16f67Bd2fB18bD720379E9C1Ef5EaD3872d67",
405
+ mainnet: "0x0"
406
+ };
407
+ var organizationRegistry = {
408
+ sepolia: "0xe7136ED5a7b0e995A8fe35d8B1B815E4160cB491",
409
+ uzh: "0xf7BCE4546805547bE526Ca864d6722Ed193E51Aa",
410
+ mainnet: "0x0"
411
+ };
412
+ var stateTransitionVerifierGroth16 = {
413
+ sepolia: "0xb7A142D24b9220eCBC4f7fcB89Ee952a6C7E332a",
414
+ uzh: "0x5e4673CD378F05cc3Ae25804539c91E711548741",
415
+ mainnet: "0x0"
416
+ };
417
+ var resultsVerifierGroth16 = {
418
+ sepolia: "0x1188cEbB56ecc90e2bAe5c914274C81Fe1a22e67",
419
+ uzh: "0x00c7F87731346F592197E49A90Ad6EC236Ad9985",
420
+ mainnet: "0x0"
421
+ };
422
+ var sequencerRegistry = {
423
+ sepolia: "0x0",
424
+ uzh: "0x0",
425
+ mainnet: "0x0"
426
+ };
427
+ var addressesJson = {
428
+ processRegistry: processRegistry,
429
+ organizationRegistry: organizationRegistry,
430
+ stateTransitionVerifierGroth16: stateTransitionVerifierGroth16,
431
+ resultsVerifierGroth16: resultsVerifierGroth16,
432
+ sequencerRegistry: sequencerRegistry
433
+ };
434
+
435
+ const deployedAddresses = addressesJson;
436
+ var TxStatus = /* @__PURE__ */ ((TxStatus2) => {
437
+ TxStatus2["Pending"] = "pending";
438
+ TxStatus2["Completed"] = "completed";
439
+ TxStatus2["Reverted"] = "reverted";
440
+ TxStatus2["Failed"] = "failed";
441
+ return TxStatus2;
442
+ })(TxStatus || {});
443
+ class SmartContractService {
444
+ /**
445
+ * Sends a transaction and yields status events during its lifecycle.
446
+ * This method handles the complete transaction flow from submission to completion,
447
+ * including error handling and status updates.
448
+ *
449
+ * @template T - The type of the successful response data
450
+ * @param txPromise - Promise resolving to the transaction response
451
+ * @param responseHandler - Function to process the successful transaction result
452
+ * @returns AsyncGenerator yielding transaction status events
453
+ *
454
+ * @example
455
+ * ```typescript
456
+ * const txStream = await this.sendTx(
457
+ * contract.someMethod(),
458
+ * async () => await contract.getUpdatedValue()
459
+ * );
460
+ *
461
+ * for await (const event of txStream) {
462
+ * switch (event.status) {
463
+ * case TxStatus.Pending:
464
+ * console.log(`Transaction pending: ${event.hash}`);
465
+ * break;
466
+ * case TxStatus.Completed:
467
+ * console.log(`Transaction completed:`, event.response);
468
+ * break;
469
+ * }
470
+ * }
471
+ * ```
472
+ */
473
+ async *sendTx(txPromise, responseHandler) {
474
+ try {
475
+ const tx = await txPromise;
476
+ yield { status: "pending" /* Pending */, hash: tx.hash };
477
+ const receipt = await tx.wait();
478
+ if (!receipt) {
479
+ yield { status: "reverted" /* Reverted */, reason: "Transaction was dropped or not mined." };
480
+ } else if (receipt.status === 0) {
481
+ yield { status: "reverted" /* Reverted */, reason: "Transaction reverted." };
482
+ } else {
483
+ const result = await responseHandler();
484
+ yield { status: "completed" /* Completed */, response: result };
485
+ }
486
+ } catch (err) {
487
+ yield {
488
+ status: "failed" /* Failed */,
489
+ error: err instanceof Error ? err : new Error("Unknown transaction error")
490
+ };
491
+ }
492
+ }
493
+ /**
494
+ * Executes a transaction stream and returns the result or throws an error.
495
+ * This is a convenience method that processes a transaction stream and either
496
+ * returns the successful result or throws an appropriate error.
497
+ *
498
+ * @template T - The type of the successful response data
499
+ * @param stream - AsyncGenerator of transaction status events
500
+ * @returns Promise resolving to the successful response data
501
+ * @throws Error if the transaction fails or reverts
502
+ *
503
+ * @example
504
+ * ```typescript
505
+ * try {
506
+ * const result = await SmartContractService.executeTx(
507
+ * contract.someMethod()
508
+ * );
509
+ * console.log('Transaction successful:', result);
510
+ * } catch (error) {
511
+ * console.error('Transaction failed:', error);
512
+ * }
513
+ * ```
514
+ */
515
+ static async executeTx(stream) {
516
+ for await (const event of stream) {
517
+ switch (event.status) {
518
+ case "completed" /* Completed */:
519
+ return event.response;
520
+ case "failed" /* Failed */:
521
+ throw event.error;
522
+ case "reverted" /* Reverted */:
523
+ throw new Error(`Transaction reverted: ${event.reason || "unknown reason"}`);
524
+ }
525
+ }
526
+ throw new Error("Transaction stream ended unexpectedly");
527
+ }
528
+ /**
529
+ * Normalizes event listener arguments between different ethers.js versions.
530
+ * This helper method ensures consistent event argument handling regardless of
531
+ * whether the event payload follows ethers v5 or v6 format.
532
+ *
533
+ * @template Args - Tuple type representing the expected event arguments
534
+ * @param callback - The event callback function to normalize
535
+ * @returns Normalized event listener function
536
+ *
537
+ * @example
538
+ * ```typescript
539
+ * contract.on('Transfer', this.normalizeListener((from: string, to: string, amount: BigInt) => {
540
+ * console.log(`Transfer from ${from} to ${to}: ${amount}`);
541
+ * }));
542
+ * ```
543
+ */
544
+ normalizeListener(callback) {
545
+ return (...listenerArgs) => {
546
+ let args;
547
+ if (listenerArgs.length === 1 && listenerArgs[0]?.args) {
548
+ args = listenerArgs[0].args;
549
+ } else {
550
+ args = listenerArgs;
551
+ }
552
+ callback(...args);
553
+ };
554
+ }
555
+ }
556
+
557
+ class ContractServiceError extends Error {
558
+ /**
559
+ * Creates a new ContractServiceError instance.
560
+ *
561
+ * @param message - The error message describing what went wrong
562
+ * @param operation - The operation that was being performed when the error occurred
563
+ */
564
+ constructor(message, operation) {
565
+ super(message);
566
+ this.operation = operation;
567
+ this.name = this.constructor.name;
568
+ }
569
+ }
570
+ class OrganizationCreateError extends ContractServiceError {
571
+ }
572
+ class OrganizationUpdateError extends ContractServiceError {
573
+ }
574
+ class OrganizationDeleteError extends ContractServiceError {
575
+ }
576
+ class OrganizationAdministratorError extends ContractServiceError {
577
+ }
578
+ class ProcessCreateError extends ContractServiceError {
579
+ }
580
+ class ProcessStatusError extends ContractServiceError {
581
+ }
582
+ class ProcessCensusError extends ContractServiceError {
583
+ }
584
+ class ProcessDurationError extends ContractServiceError {
585
+ }
586
+ class ProcessStateTransitionError extends ContractServiceError {
587
+ }
588
+ class ProcessResultError extends ContractServiceError {
589
+ }
590
+
591
+ var ProcessStatus = /* @__PURE__ */ ((ProcessStatus2) => {
592
+ ProcessStatus2[ProcessStatus2["READY"] = 0] = "READY";
593
+ ProcessStatus2[ProcessStatus2["ENDED"] = 1] = "ENDED";
594
+ ProcessStatus2[ProcessStatus2["CANCELED"] = 2] = "CANCELED";
595
+ ProcessStatus2[ProcessStatus2["PAUSED"] = 3] = "PAUSED";
596
+ ProcessStatus2[ProcessStatus2["RESULTS"] = 4] = "RESULTS";
597
+ return ProcessStatus2;
598
+ })(ProcessStatus || {});
599
+ class ProcessRegistryService extends SmartContractService {
600
+ constructor(contractAddress, runner) {
601
+ super();
602
+ this.contract = ProcessRegistry__factory.connect(contractAddress, runner);
603
+ }
604
+ // ─── READS ─────────────────────────────────────────────────────────
605
+ async getProcess(processID) {
606
+ return this.contract.getProcess(processID);
607
+ }
608
+ async getProcessCount() {
609
+ const c = await this.contract.processCount();
610
+ return Number(c);
611
+ }
612
+ async getChainID() {
613
+ const chainId = await this.contract.chainID();
614
+ return chainId.toString();
615
+ }
616
+ async getNextProcessId(organizationId) {
617
+ return this.contract.getNextProcessId(organizationId);
618
+ }
619
+ async getProcessEndTime(processID) {
620
+ return this.contract.getProcessEndTime(processID);
621
+ }
622
+ async getRVerifierVKeyHash() {
623
+ return this.contract.getRVerifierVKeyHash();
624
+ }
625
+ async getSTVerifierVKeyHash() {
626
+ return this.contract.getSTVerifierVKeyHash();
627
+ }
628
+ async getMaxCensusOrigin() {
629
+ return this.contract.MAX_CENSUS_ORIGIN();
630
+ }
631
+ async getMaxStatus() {
632
+ return this.contract.MAX_STATUS();
633
+ }
634
+ async getProcessNonce(address) {
635
+ return this.contract.processNonce(address);
636
+ }
637
+ async getProcessDirect(processID) {
638
+ return this.contract.processes(processID);
639
+ }
640
+ async getRVerifier() {
641
+ return this.contract.rVerifier();
642
+ }
643
+ async getSTVerifier() {
644
+ return this.contract.stVerifier();
645
+ }
646
+ // ─── WRITES ────────────────────────────────────────────────────────
647
+ newProcess(status, startTime, duration, ballotMode, census, metadata, encryptionKey, initStateRoot) {
648
+ const contractCensus = {
649
+ censusOrigin: BigInt(census.censusOrigin),
650
+ maxVotes: BigInt(census.maxVotes),
651
+ censusRoot: census.censusRoot,
652
+ censusURI: census.censusURI
653
+ };
654
+ return this.sendTx(
655
+ this.contract.newProcess(
656
+ status,
657
+ startTime,
658
+ duration,
659
+ ballotMode,
660
+ contractCensus,
661
+ metadata,
662
+ encryptionKey,
663
+ initStateRoot
664
+ ).catch((e) => {
665
+ throw new ProcessCreateError(e.message, "create");
666
+ }),
667
+ async () => ({ success: true })
668
+ );
669
+ }
670
+ setProcessStatus(processID, newStatus) {
671
+ return this.sendTx(
672
+ this.contract.setProcessStatus(processID, newStatus).catch((e) => {
673
+ throw new ProcessStatusError(e.message, "setStatus");
674
+ }),
675
+ async () => ({ success: true })
676
+ );
677
+ }
678
+ setProcessCensus(processID, census) {
679
+ const contractCensus = {
680
+ censusOrigin: BigInt(census.censusOrigin),
681
+ maxVotes: BigInt(census.maxVotes),
682
+ censusRoot: census.censusRoot,
683
+ censusURI: census.censusURI
684
+ };
685
+ return this.sendTx(
686
+ this.contract.setProcessCensus(processID, contractCensus).catch((e) => {
687
+ throw new ProcessCensusError(e.message, "setCensus");
688
+ }),
689
+ async () => ({ success: true })
690
+ );
691
+ }
692
+ setProcessDuration(processID, duration) {
693
+ return this.sendTx(
694
+ this.contract.setProcessDuration(processID, duration).catch((e) => {
695
+ throw new ProcessDurationError(e.message, "setDuration");
696
+ }),
697
+ async () => ({ success: true })
698
+ );
699
+ }
700
+ /**
701
+ * Matches the on-chain `submitStateTransition(processId, proof, input)`
702
+ */
703
+ submitStateTransition(processID, proof, input) {
704
+ return this.sendTx(
705
+ this.contract.submitStateTransition(processID, proof, input).catch((e) => {
706
+ throw new ProcessStateTransitionError(e.message, "submitStateTransition");
707
+ }),
708
+ async () => ({ success: true })
709
+ );
710
+ }
711
+ /**
712
+ * Sets the results for a voting process.
713
+ *
714
+ * @param processID - The ID of the process to set results for
715
+ * @param proof - Zero-knowledge proof validating the results
716
+ * @param input - Input data for the proof verification
717
+ * @returns A transaction stream that resolves to success status
718
+ */
719
+ setProcessResults(processID, proof, input) {
720
+ return this.sendTx(
721
+ this.contract.setProcessResults(
722
+ processID,
723
+ proof,
724
+ input
725
+ ).catch((e) => {
726
+ throw new ProcessResultError(e.message, "setResults");
727
+ }),
728
+ async () => ({ success: true })
729
+ );
730
+ }
731
+ // ─── EVENT LISTENERS ───────────────────────────────────────────────────────
732
+ onProcessCreated(cb) {
733
+ this.contract.on(
734
+ this.contract.filters.ProcessCreated(),
735
+ this.normalizeListener(cb)
736
+ );
737
+ }
738
+ onProcessStatusChanged(cb) {
739
+ this.contract.on(
740
+ this.contract.filters.ProcessStatusChanged(),
741
+ this.normalizeListener(cb)
742
+ );
743
+ }
744
+ onCensusUpdated(cb) {
745
+ this.contract.on(
746
+ this.contract.filters.CensusUpdated(),
747
+ this.normalizeListener(cb)
748
+ );
749
+ }
750
+ onProcessDurationChanged(cb) {
751
+ this.contract.on(
752
+ this.contract.filters.ProcessDurationChanged(),
753
+ this.normalizeListener(cb)
754
+ );
755
+ }
756
+ onStateRootUpdated(cb) {
757
+ this.contract.on(
758
+ this.contract.filters.ProcessStateRootUpdated(),
759
+ this.normalizeListener(cb)
760
+ );
761
+ }
762
+ onProcessResultsSet(cb) {
763
+ this.contract.on(
764
+ this.contract.filters.ProcessResultsSet(),
765
+ this.normalizeListener(cb)
766
+ );
767
+ }
768
+ removeAllListeners() {
769
+ this.contract.removeAllListeners();
770
+ }
771
+ }
772
+
773
+ class ProcessOrchestrationService {
774
+ constructor(processRegistry, apiService, organizationRegistry, getCrypto, signer) {
775
+ this.processRegistry = processRegistry;
776
+ this.apiService = apiService;
777
+ this.organizationRegistry = organizationRegistry;
778
+ this.getCrypto = getCrypto;
779
+ this.signer = signer;
780
+ }
781
+ /**
782
+ * Gets user-friendly process information by transforming raw contract data
783
+ * @param processId - The process ID to fetch
784
+ * @returns Promise resolving to the user-friendly process information
785
+ */
786
+ async getProcess(processId) {
787
+ const rawProcess = await this.processRegistry.getProcess(processId);
788
+ let metadata = null;
789
+ let title;
790
+ let description;
791
+ let questions = [];
792
+ try {
793
+ if (rawProcess.metadataURI) {
794
+ metadata = await this.apiService.sequencer.getMetadata(rawProcess.metadataURI);
795
+ title = metadata?.title?.default;
796
+ description = metadata?.description?.default;
797
+ if (metadata?.questions) {
798
+ questions = metadata.questions.map((q) => ({
799
+ title: q.title?.default,
800
+ description: q.description?.default,
801
+ choices: q.choices?.map((c) => ({
802
+ title: c.title?.default,
803
+ value: c.value
804
+ })) || []
805
+ }));
806
+ }
807
+ }
808
+ } catch (error) {
809
+ console.warn(`Failed to fetch metadata for process ${processId}:`, error);
810
+ }
811
+ const now = Math.floor(Date.now() / 1e3);
812
+ const startTime = Number(rawProcess.startTime);
813
+ const duration = Number(rawProcess.duration);
814
+ const endTime = startTime + duration;
815
+ const timeRemaining = now >= endTime ? 0 : now >= startTime ? endTime - now : startTime - now;
816
+ const census = {
817
+ type: Number(rawProcess.census.censusOrigin),
818
+ root: rawProcess.census.censusRoot,
819
+ size: Number(rawProcess.census.maxVotes),
820
+ uri: rawProcess.census.censusURI || ""
821
+ };
822
+ const ballot = {
823
+ numFields: Number(rawProcess.ballotMode.numFields),
824
+ maxValue: rawProcess.ballotMode.maxValue.toString(),
825
+ minValue: rawProcess.ballotMode.minValue.toString(),
826
+ uniqueValues: rawProcess.ballotMode.uniqueValues,
827
+ costFromWeight: rawProcess.ballotMode.costFromWeight,
828
+ costExponent: Number(rawProcess.ballotMode.costExponent),
829
+ maxValueSum: rawProcess.ballotMode.maxValueSum.toString(),
830
+ minValueSum: rawProcess.ballotMode.minValueSum.toString()
831
+ };
832
+ return {
833
+ processId,
834
+ title,
835
+ description,
836
+ census,
837
+ ballot,
838
+ questions,
839
+ status: Number(rawProcess.status),
840
+ creator: rawProcess.organizationId,
841
+ startDate: new Date(startTime * 1e3),
842
+ endDate: new Date(endTime * 1e3),
843
+ duration,
844
+ timeRemaining,
845
+ result: rawProcess.result,
846
+ voteCount: Number(rawProcess.voteCount),
847
+ voteOverwriteCount: Number(rawProcess.voteOverwriteCount),
848
+ metadataURI: rawProcess.metadataURI,
849
+ raw: rawProcess
850
+ };
851
+ }
852
+ /**
853
+ * Creates a complete voting process with minimal configuration
854
+ * This method handles all the complex orchestration internally
855
+ */
856
+ async createProcess(config) {
857
+ const { startTime, duration } = this.calculateTiming(config.timing);
858
+ const signerAddress = await this.signer.getAddress();
859
+ const processId = await this.processRegistry.getNextProcessId(signerAddress);
860
+ const censusRoot = config.census.root;
861
+ const ballotMode = config.ballot;
862
+ const metadata = this.createMetadata(config);
863
+ const metadataHash = await this.apiService.sequencer.pushMetadata(metadata);
864
+ const metadataUri = this.apiService.sequencer.getMetadataUrl(metadataHash);
865
+ const signature = await signProcessCreation(processId, this.signer);
866
+ const sequencerResult = await this.apiService.sequencer.createProcess({
867
+ processId,
868
+ censusRoot,
869
+ ballotMode,
870
+ signature,
871
+ censusOrigin: config.census.type
872
+ });
873
+ const census = {
874
+ censusOrigin: config.census.type,
875
+ maxVotes: config.census.size.toString(),
876
+ censusRoot,
877
+ censusURI: config.census.uri
878
+ };
879
+ const encryptionKey = {
880
+ x: sequencerResult.encryptionPubKey[0],
881
+ y: sequencerResult.encryptionPubKey[1]
882
+ };
883
+ const txStream = this.processRegistry.newProcess(
884
+ ProcessStatus.READY,
885
+ startTime,
886
+ duration,
887
+ ballotMode,
888
+ census,
889
+ metadataUri,
890
+ encryptionKey,
891
+ BigInt(sequencerResult.stateRoot)
892
+ );
893
+ let transactionHash = "unknown";
894
+ for await (const event of txStream) {
895
+ if (event.status === "pending") {
896
+ transactionHash = event.hash;
897
+ } else if (event.status === "completed") {
898
+ break;
899
+ } else if (event.status === "failed") {
900
+ throw event.error;
901
+ } else if (event.status === "reverted") {
902
+ throw new Error(`Transaction reverted: ${event.reason || "unknown reason"}`);
903
+ }
904
+ }
905
+ return {
906
+ processId,
907
+ transactionHash
908
+ };
909
+ }
910
+ /**
911
+ * Validates and calculates timing parameters
912
+ */
913
+ calculateTiming(timing) {
914
+ const { startDate, duration, endDate } = timing;
915
+ if (duration !== void 0 && endDate !== void 0) {
916
+ throw new Error("Cannot specify both 'duration' and 'endDate'. Use one or the other.");
917
+ }
918
+ if (duration === void 0 && endDate === void 0) {
919
+ throw new Error("Must specify either 'duration' (in seconds) or 'endDate'.");
920
+ }
921
+ const startTime = startDate ? this.dateToUnixTimestamp(startDate) : Math.floor(Date.now() / 1e3) + 60;
922
+ let calculatedDuration;
923
+ if (duration !== void 0) {
924
+ calculatedDuration = duration;
925
+ } else {
926
+ const endTime = this.dateToUnixTimestamp(endDate);
927
+ calculatedDuration = endTime - startTime;
928
+ if (calculatedDuration <= 0) {
929
+ throw new Error("End date must be after start date.");
930
+ }
931
+ }
932
+ const now = Math.floor(Date.now() / 1e3);
933
+ if (startTime < now - 30) {
934
+ throw new Error("Start date cannot be in the past.");
935
+ }
936
+ return { startTime, duration: calculatedDuration };
937
+ }
938
+ /**
939
+ * Converts various date formats to Unix timestamp
940
+ */
941
+ dateToUnixTimestamp(date) {
942
+ if (typeof date === "number") {
943
+ if (date > 1e10) {
944
+ return Math.floor(date / 1e3);
945
+ }
946
+ return Math.floor(date);
947
+ }
948
+ if (typeof date === "string") {
949
+ const parsed = new Date(date);
950
+ if (isNaN(parsed.getTime())) {
951
+ throw new Error(`Invalid date string: ${date}`);
952
+ }
953
+ return Math.floor(parsed.getTime() / 1e3);
954
+ }
955
+ if (date instanceof Date) {
956
+ if (isNaN(date.getTime())) {
957
+ throw new Error("Invalid Date object provided.");
958
+ }
959
+ return Math.floor(date.getTime() / 1e3);
960
+ }
961
+ throw new Error("Invalid date format. Use Date object, ISO string, or Unix timestamp.");
962
+ }
963
+ /**
964
+ * Creates metadata from the simplified configuration
965
+ */
966
+ createMetadata(config) {
967
+ const metadata = getElectionMetadataTemplate();
968
+ metadata.title.default = config.title;
969
+ metadata.description.default = config.description || "";
970
+ if (!config.questions || config.questions.length === 0) {
971
+ throw new Error("Questions are required. Please provide at least one question with choices.");
972
+ }
973
+ metadata.questions = config.questions.map((q) => ({
974
+ title: { default: q.title },
975
+ description: { default: q.description || "" },
976
+ meta: {},
977
+ choices: q.choices.map((c) => ({
978
+ title: { default: c.title },
979
+ value: c.value,
980
+ meta: {}
981
+ }))
982
+ }));
983
+ return metadata;
984
+ }
985
+ }
986
+
987
+ class CircomProof {
988
+ constructor(opts = {}) {
989
+ // simple in-memory cache keyed by URL
990
+ this.wasmCache = /* @__PURE__ */ new Map();
991
+ this.zkeyCache = /* @__PURE__ */ new Map();
992
+ this.vkeyCache = /* @__PURE__ */ new Map();
993
+ this.wasmUrl = opts.wasmUrl;
994
+ this.zkeyUrl = opts.zkeyUrl;
995
+ this.vkeyUrl = opts.vkeyUrl;
996
+ this.wasmHash = opts.wasmHash;
997
+ this.zkeyHash = opts.zkeyHash;
998
+ this.vkeyHash = opts.vkeyHash;
999
+ }
1000
+ /**
1001
+ * Computes SHA-256 hash of the given data and compares it with the expected hash.
1002
+ * @param data - The data to hash (string or ArrayBuffer or Uint8Array)
1003
+ * @param expectedHash - The expected SHA-256 hash in hexadecimal format
1004
+ * @param filename - The filename for error reporting
1005
+ * @throws Error if the computed hash doesn't match the expected hash
1006
+ */
1007
+ verifyHash(data, expectedHash, filename) {
1008
+ let bytes;
1009
+ if (typeof data === "string") {
1010
+ bytes = new TextEncoder().encode(data);
1011
+ } else if (data instanceof ArrayBuffer) {
1012
+ bytes = new Uint8Array(data);
1013
+ } else {
1014
+ bytes = data;
1015
+ }
1016
+ const computedHash = sha256(bytes).slice(2);
1017
+ if (computedHash.toLowerCase() !== expectedHash.toLowerCase()) {
1018
+ throw new Error(
1019
+ `Hash verification failed for ${filename}. Expected: ${expectedHash.toLowerCase()}, Computed: ${computedHash.toLowerCase()}`
1020
+ );
1021
+ }
1022
+ }
1023
+ /**
1024
+ * Generate a zk‐SNARK proof.
1025
+ * If you didn't pass wasmUrl/zkeyUrl in the constructor you must supply them here.
1026
+ */
1027
+ async generate(inputs, urls = {}) {
1028
+ const wasmUrl = urls.wasmUrl ?? this.wasmUrl;
1029
+ const zkeyUrl = urls.zkeyUrl ?? this.zkeyUrl;
1030
+ if (!wasmUrl) throw new Error("`wasmUrl` is required to generate a proof");
1031
+ if (!zkeyUrl) throw new Error("`zkeyUrl` is required to generate a proof");
1032
+ let wasmBin = this.wasmCache.get(wasmUrl);
1033
+ if (!wasmBin) {
1034
+ const r = await fetch(wasmUrl);
1035
+ if (!r.ok) throw new Error(`Failed to fetch wasm at ${wasmUrl}: ${r.status}`);
1036
+ const buf = await r.arrayBuffer();
1037
+ wasmBin = new Uint8Array(buf);
1038
+ if (this.wasmHash) {
1039
+ this.verifyHash(wasmBin, this.wasmHash, "circuit.wasm");
1040
+ }
1041
+ this.wasmCache.set(wasmUrl, wasmBin);
1042
+ }
1043
+ let zkeyBin = this.zkeyCache.get(zkeyUrl);
1044
+ if (!zkeyBin) {
1045
+ const r = await fetch(zkeyUrl);
1046
+ if (!r.ok) throw new Error(`Failed to fetch zkey at ${zkeyUrl}: ${r.status}`);
1047
+ const buf = await r.arrayBuffer();
1048
+ zkeyBin = new Uint8Array(buf);
1049
+ if (this.zkeyHash) {
1050
+ this.verifyHash(zkeyBin, this.zkeyHash, "proving_key.zkey");
1051
+ }
1052
+ this.zkeyCache.set(zkeyUrl, zkeyBin);
1053
+ }
1054
+ const { proof, publicSignals } = await groth16.fullProve(
1055
+ inputs,
1056
+ wasmBin,
1057
+ zkeyBin
1058
+ );
1059
+ return {
1060
+ proof,
1061
+ publicSignals
1062
+ };
1063
+ }
1064
+ async verify(proof, publicSignals, urlOverride) {
1065
+ const vkeyUrl = urlOverride ?? this.vkeyUrl;
1066
+ if (!vkeyUrl) throw new Error("`vkeyUrl` is required to verify a proof");
1067
+ let vk = this.vkeyCache.get(vkeyUrl);
1068
+ if (!vk) {
1069
+ const r = await fetch(vkeyUrl);
1070
+ if (!r.ok) throw new Error(`Failed to fetch vkey at ${vkeyUrl}: ${r.status}`);
1071
+ const vkeyText = await r.text();
1072
+ if (this.vkeyHash) {
1073
+ this.verifyHash(vkeyText, this.vkeyHash, "verification_key.json");
1074
+ }
1075
+ vk = JSON.parse(vkeyText);
1076
+ this.vkeyCache.set(vkeyUrl, vk);
1077
+ }
1078
+ return groth16.verify(vk, publicSignals, proof);
1079
+ }
1080
+ }
1081
+
1082
+ var VoteStatus = /* @__PURE__ */ ((VoteStatus2) => {
1083
+ VoteStatus2["Pending"] = "pending";
1084
+ VoteStatus2["Verified"] = "verified";
1085
+ VoteStatus2["Aggregated"] = "aggregated";
1086
+ VoteStatus2["Processed"] = "processed";
1087
+ VoteStatus2["Settled"] = "settled";
1088
+ VoteStatus2["Error"] = "error";
1089
+ return VoteStatus2;
1090
+ })(VoteStatus || {});
1091
+
1092
+ class VoteOrchestrationService {
1093
+ constructor(apiService, getCrypto, signer, censusProviders = {}) {
1094
+ this.apiService = apiService;
1095
+ this.getCrypto = getCrypto;
1096
+ this.signer = signer;
1097
+ this.censusProviders = censusProviders;
1098
+ }
1099
+ /**
1100
+ * Submit a vote with simplified configuration
1101
+ * This method handles all the complex orchestration internally:
1102
+ * - Fetches process information and encryption keys
1103
+ * - Gets census proof (Merkle or CSP)
1104
+ * - Generates cryptographic proofs
1105
+ * - Signs and submits the vote
1106
+ *
1107
+ * @param config - Simplified vote configuration
1108
+ * @returns Promise resolving to vote submission result
1109
+ */
1110
+ async submitVote(config) {
1111
+ const process = await this.apiService.sequencer.getProcess(config.processId);
1112
+ if (!process.isAcceptingVotes) {
1113
+ throw new Error("Process is not currently accepting votes");
1114
+ }
1115
+ const voterAddress = await this.signer.getAddress();
1116
+ const censusProof = await this.getCensusProof(
1117
+ process.census.censusOrigin,
1118
+ process.census.censusRoot,
1119
+ voterAddress,
1120
+ config.processId
1121
+ );
1122
+ const { voteId, cryptoOutput, circomInputs } = await this.generateVoteProofInputs(
1123
+ config.processId,
1124
+ voterAddress,
1125
+ process.encryptionKey,
1126
+ process.ballotMode,
1127
+ config.choices,
1128
+ censusProof.weight,
1129
+ config.randomness
1130
+ );
1131
+ const { proof } = await this.generateZkProof(circomInputs);
1132
+ const signature = await this.signVote(voteId);
1133
+ await this.submitVoteRequest({
1134
+ processId: config.processId,
1135
+ censusProof,
1136
+ ballot: cryptoOutput.ballot,
1137
+ ballotProof: proof,
1138
+ ballotInputsHash: cryptoOutput.ballotInputsHash,
1139
+ address: voterAddress,
1140
+ signature,
1141
+ voteId
1142
+ });
1143
+ const status = await this.apiService.sequencer.getVoteStatus(config.processId, voteId);
1144
+ return {
1145
+ voteId,
1146
+ signature,
1147
+ voterAddress,
1148
+ processId: config.processId,
1149
+ status: status.status
1150
+ };
1151
+ }
1152
+ /**
1153
+ * Get the status of a submitted vote
1154
+ *
1155
+ * @param processId - The process ID
1156
+ * @param voteId - The vote ID
1157
+ * @returns Promise resolving to vote status information
1158
+ */
1159
+ async getVoteStatus(processId, voteId) {
1160
+ const status = await this.apiService.sequencer.getVoteStatus(processId, voteId);
1161
+ return {
1162
+ voteId,
1163
+ status: status.status,
1164
+ processId
1165
+ };
1166
+ }
1167
+ /**
1168
+ * Check if an address has voted in a process
1169
+ *
1170
+ * @param processId - The process ID
1171
+ * @param address - The voter's address
1172
+ * @returns Promise resolving to boolean indicating if the address has voted
1173
+ */
1174
+ async hasAddressVoted(processId, address) {
1175
+ return this.apiService.sequencer.hasAddressVoted(processId, address);
1176
+ }
1177
+ /**
1178
+ * Wait for a vote to reach a specific status
1179
+ *
1180
+ * @param processId - The process ID
1181
+ * @param voteId - The vote ID
1182
+ * @param targetStatus - The target status to wait for (default: "settled")
1183
+ * @param timeoutMs - Maximum time to wait in milliseconds (default: 300000 = 5 minutes)
1184
+ * @param pollIntervalMs - Polling interval in milliseconds (default: 5000 = 5 seconds)
1185
+ * @returns Promise resolving to final vote status
1186
+ */
1187
+ async waitForVoteStatus(processId, voteId, targetStatus = VoteStatus.Settled, timeoutMs = 3e5, pollIntervalMs = 5e3) {
1188
+ const startTime = Date.now();
1189
+ while (Date.now() - startTime < timeoutMs) {
1190
+ const statusInfo = await this.getVoteStatus(processId, voteId);
1191
+ if (statusInfo.status === targetStatus || statusInfo.status === VoteStatus.Error) {
1192
+ return statusInfo;
1193
+ }
1194
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
1195
+ }
1196
+ throw new Error(`Vote did not reach status ${targetStatus} within ${timeoutMs}ms`);
1197
+ }
1198
+ /**
1199
+ * Get census proof based on census origin type
1200
+ */
1201
+ async getCensusProof(censusOrigin, censusRoot, voterAddress, processId) {
1202
+ if (censusOrigin === CensusOrigin.CensusOriginMerkleTree) {
1203
+ if (this.censusProviders.merkle) {
1204
+ const proof = await this.censusProviders.merkle({
1205
+ censusRoot,
1206
+ address: voterAddress
1207
+ });
1208
+ assertMerkleCensusProof(proof);
1209
+ return proof;
1210
+ } else {
1211
+ const proof = await this.apiService.census.getCensusProof(censusRoot, voterAddress);
1212
+ assertMerkleCensusProof(proof);
1213
+ return proof;
1214
+ }
1215
+ }
1216
+ if (censusOrigin === CensusOrigin.CensusOriginCSP) {
1217
+ if (!this.censusProviders.csp) {
1218
+ throw new Error(
1219
+ "CSP voting requires a CSP census proof provider. Pass one via VoteOrchestrationService(..., { csp: yourFn })."
1220
+ );
1221
+ }
1222
+ const proof = await this.censusProviders.csp({
1223
+ processId,
1224
+ address: voterAddress
1225
+ });
1226
+ assertCSPCensusProof(proof);
1227
+ return proof;
1228
+ }
1229
+ throw new Error(`Unsupported census origin: ${censusOrigin}`);
1230
+ }
1231
+ /**
1232
+ * Generate vote proof inputs using DavinciCrypto
1233
+ */
1234
+ async generateVoteProofInputs(processId, voterAddress, encryptionKey, ballotMode, choices, weight, customRandomness) {
1235
+ const crypto = await this.getCrypto();
1236
+ this.validateChoices(choices, ballotMode);
1237
+ const fieldValues = choices.map((choice) => choice.toString());
1238
+ const inputs = {
1239
+ address: voterAddress.replace(/^0x/, ""),
1240
+ processID: processId.replace(/^0x/, ""),
1241
+ encryptionKey: [encryptionKey.x, encryptionKey.y],
1242
+ ballotMode,
1243
+ weight,
1244
+ fieldValues
1245
+ };
1246
+ if (customRandomness) {
1247
+ const hexRandomness = customRandomness.startsWith("0x") ? customRandomness : "0x" + customRandomness;
1248
+ const k = BigInt(hexRandomness).toString();
1249
+ inputs.k = k;
1250
+ }
1251
+ const cryptoOutput = await crypto.proofInputs(inputs);
1252
+ return {
1253
+ voteId: cryptoOutput.voteId,
1254
+ cryptoOutput,
1255
+ circomInputs: cryptoOutput.circomInputs
1256
+ };
1257
+ }
1258
+ /**
1259
+ * Validate user choices based on ballot mode
1260
+ */
1261
+ validateChoices(choices, ballotMode) {
1262
+ const maxValue = parseInt(ballotMode.maxValue);
1263
+ const minValue = parseInt(ballotMode.minValue);
1264
+ for (let i = 0; i < choices.length; i++) {
1265
+ const choice = choices[i];
1266
+ if (choice < minValue || choice > maxValue) {
1267
+ throw new Error(`Choice ${choice} is out of range [${minValue}, ${maxValue}]`);
1268
+ }
1269
+ }
1270
+ }
1271
+ /**
1272
+ * Generate zk-SNARK proof using CircomProof
1273
+ */
1274
+ async generateZkProof(circomInputs) {
1275
+ const info = await this.apiService.sequencer.getInfo();
1276
+ const circomProof = new CircomProof({
1277
+ wasmUrl: info.circuitUrl,
1278
+ zkeyUrl: info.provingKeyUrl,
1279
+ vkeyUrl: info.verificationKeyUrl
1280
+ });
1281
+ const { proof, publicSignals } = await circomProof.generate(circomInputs);
1282
+ const isValid = await circomProof.verify(proof, publicSignals);
1283
+ if (!isValid) {
1284
+ throw new Error("Generated proof is invalid");
1285
+ }
1286
+ return { proof, publicSignals };
1287
+ }
1288
+ hexToBytes(hex) {
1289
+ const clean = hex.replace(/^0x/, "");
1290
+ if (clean.length % 2) throw new Error("Invalid hex length");
1291
+ const out = new Uint8Array(clean.length / 2);
1292
+ for (let i = 0; i < out.length; i++) out[i] = parseInt(clean.substr(i * 2, 2), 16);
1293
+ return out;
1294
+ }
1295
+ /**
1296
+ * Sign the vote using the signer
1297
+ */
1298
+ async signVote(voteId) {
1299
+ return this.signer.signMessage(this.hexToBytes(voteId));
1300
+ }
1301
+ /**
1302
+ * Submit the vote request to the sequencer
1303
+ */
1304
+ async submitVoteRequest(voteRequest) {
1305
+ const ballotProof = {
1306
+ pi_a: voteRequest.ballotProof.pi_a,
1307
+ pi_b: voteRequest.ballotProof.pi_b,
1308
+ pi_c: voteRequest.ballotProof.pi_c,
1309
+ protocol: voteRequest.ballotProof.protocol
1310
+ };
1311
+ const request = {
1312
+ ...voteRequest,
1313
+ ballotProof
1314
+ };
1315
+ await this.apiService.sequencer.submitVote(request);
1316
+ }
1317
+ }
1318
+
1319
+ class OrganizationRegistryService extends SmartContractService {
1320
+ constructor(contractAddress, runner) {
1321
+ super();
1322
+ this.contract = OrganizationRegistry__factory.connect(
1323
+ contractAddress,
1324
+ runner
1325
+ );
1326
+ }
1327
+ // ─── READ OPERATIONS ───────────────────────────────────────────────────────
1328
+ async getOrganization(id) {
1329
+ const { name, metadataURI } = await this.contract.getOrganization(id);
1330
+ return { name, metadataURI };
1331
+ }
1332
+ async existsOrganization(id) {
1333
+ return this.contract.exists(id);
1334
+ }
1335
+ async isAdministrator(id, address) {
1336
+ return this.contract.isAdministrator(id, address);
1337
+ }
1338
+ async getOrganizationCount() {
1339
+ const count = await this.contract.organizationCount();
1340
+ return Number(count);
1341
+ }
1342
+ // ─── WRITE OPERATIONS ──────────────────────────────────────────────────────
1343
+ createOrganization(name, metadataURI, administrators) {
1344
+ return this.sendTx(
1345
+ this.contract.createOrganization(name, metadataURI, administrators).catch((e) => {
1346
+ throw new OrganizationCreateError(e.message, "create");
1347
+ }),
1348
+ async () => ({ success: true })
1349
+ );
1350
+ }
1351
+ updateOrganization(id, name, metadataURI) {
1352
+ return this.sendTx(
1353
+ this.contract.updateOrganization(id, name, metadataURI).catch((e) => {
1354
+ throw new OrganizationUpdateError(e.message, "update");
1355
+ }),
1356
+ async () => ({ success: true })
1357
+ );
1358
+ }
1359
+ addAdministrator(id, administrator) {
1360
+ return this.sendTx(
1361
+ this.contract.addAdministrator(id, administrator).catch((e) => {
1362
+ throw new OrganizationAdministratorError(e.message, "addAdministrator");
1363
+ }),
1364
+ async () => ({ success: true })
1365
+ );
1366
+ }
1367
+ removeAdministrator(id, administrator) {
1368
+ return this.sendTx(
1369
+ this.contract.removeAdministrator(id, administrator).catch((e) => {
1370
+ throw new OrganizationAdministratorError(e.message, "removeAdministrator");
1371
+ }),
1372
+ async () => ({ success: true })
1373
+ );
1374
+ }
1375
+ deleteOrganization(id) {
1376
+ return this.sendTx(
1377
+ this.contract.deleteOrganization(id).catch((e) => {
1378
+ throw new OrganizationDeleteError(e.message, "delete");
1379
+ }),
1380
+ async () => ({ success: true })
1381
+ );
1382
+ }
1383
+ // ─── EVENT LISTENERS ───────────────────────────────────────────────────────
1384
+ onOrganizationCreated(cb) {
1385
+ this.contract.on(
1386
+ this.contract.filters.OrganizationCreated(),
1387
+ this.normalizeListener(cb)
1388
+ );
1389
+ }
1390
+ onOrganizationUpdated(cb) {
1391
+ this.contract.on(
1392
+ this.contract.filters.OrganizationUpdated(),
1393
+ this.normalizeListener(cb)
1394
+ );
1395
+ }
1396
+ onAdministratorAdded(cb) {
1397
+ this.contract.on(
1398
+ this.contract.filters.AdministratorAdded(),
1399
+ this.normalizeListener(cb)
1400
+ );
1401
+ }
1402
+ onAdministratorRemoved(cb) {
1403
+ this.contract.on(
1404
+ this.contract.filters.AdministratorRemoved(),
1405
+ this.normalizeListener(cb)
1406
+ );
1407
+ }
1408
+ removeAllListeners() {
1409
+ this.contract.removeAllListeners();
1410
+ }
1411
+ }
1412
+
1413
+ class DavinciCrypto {
1414
+ constructor(opts) {
1415
+ this.initialized = false;
1416
+ const { wasmExecUrl, wasmUrl, initTimeoutMs, wasmExecHash, wasmHash } = opts;
1417
+ if (!wasmExecUrl) throw new Error("`wasmExecUrl` is required");
1418
+ if (!wasmUrl) throw new Error("`wasmUrl` is required");
1419
+ this.wasmExecUrl = wasmExecUrl;
1420
+ this.wasmUrl = wasmUrl;
1421
+ this.initTimeoutMs = initTimeoutMs ?? 5e3;
1422
+ this.wasmExecHash = wasmExecHash;
1423
+ this.wasmHash = wasmHash;
1424
+ }
1425
+ static {
1426
+ // Cache for wasm files
1427
+ this.wasmExecCache = /* @__PURE__ */ new Map();
1428
+ }
1429
+ static {
1430
+ this.wasmBinaryCache = /* @__PURE__ */ new Map();
1431
+ }
1432
+ /**
1433
+ * Computes SHA-256 hash of the given data and compares it with the expected hash.
1434
+ * @param data - The data to hash (string or ArrayBuffer)
1435
+ * @param expectedHash - The expected SHA-256 hash in hexadecimal format
1436
+ * @param filename - The filename for error reporting
1437
+ * @throws Error if the computed hash doesn't match the expected hash
1438
+ */
1439
+ verifyHash(data, expectedHash, filename) {
1440
+ let bytes;
1441
+ if (typeof data === "string") {
1442
+ bytes = new TextEncoder().encode(data);
1443
+ } else {
1444
+ bytes = new Uint8Array(data);
1445
+ }
1446
+ const computedHash = sha256(bytes).slice(2);
1447
+ if (computedHash.toLowerCase() !== expectedHash.toLowerCase()) {
1448
+ throw new Error(
1449
+ `Hash verification failed for ${filename}. Expected: ${expectedHash.toLowerCase()}, Computed: ${computedHash.toLowerCase()}`
1450
+ );
1451
+ }
1452
+ }
1453
+ /**
1454
+ * Must be awaited before calling `proofInputs()`.
1455
+ * Safe to call multiple times.
1456
+ */
1457
+ async init() {
1458
+ if (this.initialized) return;
1459
+ let shimCode = DavinciCrypto.wasmExecCache.get(this.wasmExecUrl);
1460
+ if (!shimCode) {
1461
+ const shim = await fetch(this.wasmExecUrl);
1462
+ if (!shim.ok) {
1463
+ throw new Error(`Failed to fetch wasm_exec.js from ${this.wasmExecUrl}`);
1464
+ }
1465
+ shimCode = await shim.text();
1466
+ if (this.wasmExecHash) {
1467
+ this.verifyHash(shimCode, this.wasmExecHash, "wasm_exec.js");
1468
+ }
1469
+ DavinciCrypto.wasmExecCache.set(this.wasmExecUrl, shimCode);
1470
+ }
1471
+ new Function(shimCode)();
1472
+ if (typeof globalThis.Go !== "function") {
1473
+ throw new Error("Global `Go` constructor not found after loading wasm_exec.js");
1474
+ }
1475
+ this.go = new globalThis.Go();
1476
+ let bytes = DavinciCrypto.wasmBinaryCache.get(this.wasmUrl);
1477
+ if (!bytes) {
1478
+ const resp = await fetch(this.wasmUrl);
1479
+ if (!resp.ok) {
1480
+ throw new Error(`Failed to fetch ballotproof.wasm from ${this.wasmUrl}`);
1481
+ }
1482
+ bytes = await resp.arrayBuffer();
1483
+ if (this.wasmHash) {
1484
+ this.verifyHash(bytes, this.wasmHash, "davinci_crypto.wasm");
1485
+ }
1486
+ DavinciCrypto.wasmBinaryCache.set(this.wasmUrl, bytes);
1487
+ }
1488
+ const { instance } = await WebAssembly.instantiate(bytes, this.go.importObject);
1489
+ this.go.run(instance).catch(() => {
1490
+ });
1491
+ const deadline = Date.now() + this.initTimeoutMs;
1492
+ while (Date.now() < deadline && !globalThis.DavinciCrypto) {
1493
+ await new Promise((r) => setTimeout(r, 50));
1494
+ }
1495
+ if (!globalThis.DavinciCrypto) {
1496
+ throw new Error("`DavinciCrypto` not initialized within timeout");
1497
+ }
1498
+ this.initialized = true;
1499
+ }
1500
+ /**
1501
+ * Convert your inputs into JSON, hand off to Go/WASM, then parse & return.
1502
+ * @throws if called before `await init()`, or if Go returns an error
1503
+ */
1504
+ async proofInputs(inputs) {
1505
+ if (!this.initialized) {
1506
+ throw new Error("DavinciCrypto not initialized \u2014 call `await init()` first");
1507
+ }
1508
+ const raw = globalThis.DavinciCrypto.proofInputs(JSON.stringify(inputs));
1509
+ if (raw.error) {
1510
+ throw new Error(`Go/WASM proofInputs error: ${raw.error}`);
1511
+ }
1512
+ if (!raw.data) {
1513
+ throw new Error("Go/WASM proofInputs returned no data");
1514
+ }
1515
+ return raw.data;
1516
+ }
1517
+ /**
1518
+ * Generate a CSP (Credential Service Provider) signature for census proof.
1519
+ * @param censusOrigin - The census origin type (e.g., CensusOrigin.CensusOriginCSP)
1520
+ * @param privKey - The private key in hex format
1521
+ * @param processId - The process ID in hex format
1522
+ * @param address - The address in hex format
1523
+ * @returns The CSP proof as a parsed JSON object
1524
+ * @throws if called before `await init()`, or if Go returns an error
1525
+ */
1526
+ async cspSign(censusOrigin, privKey, processId, address) {
1527
+ if (!this.initialized) {
1528
+ throw new Error("DavinciCrypto not initialized \u2014 call `await init()` first");
1529
+ }
1530
+ const raw = globalThis.DavinciCrypto.cspSign(censusOrigin, privKey, processId, address);
1531
+ if (raw.error) {
1532
+ throw new Error(`Go/WASM cspSign error: ${raw.error}`);
1533
+ }
1534
+ if (!raw.data) {
1535
+ throw new Error("Go/WASM cspSign returned no data");
1536
+ }
1537
+ return raw.data;
1538
+ }
1539
+ /**
1540
+ * Verify a CSP (Credential Service Provider) proof.
1541
+ * @param censusOrigin - The census origin type (e.g., CensusOrigin.CensusOriginCSP)
1542
+ * @param root - The census root
1543
+ * @param address - The address
1544
+ * @param processId - The process ID
1545
+ * @param publicKey - The public key
1546
+ * @param signature - The signature
1547
+ * @returns The verification result
1548
+ * @throws if called before `await init()`, or if Go returns an error
1549
+ */
1550
+ async cspVerify(censusOrigin, root, address, processId, publicKey, signature) {
1551
+ if (!this.initialized) {
1552
+ throw new Error("DavinciCrypto not initialized \u2014 call `await init()` first");
1553
+ }
1554
+ const cspProof = {
1555
+ censusOrigin,
1556
+ root,
1557
+ address,
1558
+ processId,
1559
+ publicKey,
1560
+ signature
1561
+ };
1562
+ const raw = globalThis.DavinciCrypto.cspVerify(JSON.stringify(cspProof));
1563
+ if (raw.error) {
1564
+ throw new Error(`Go/WASM cspVerify error: ${raw.error}`);
1565
+ }
1566
+ if (!raw.data) {
1567
+ throw new Error("Go/WASM cspVerify returned no data");
1568
+ }
1569
+ return raw.data;
1570
+ }
1571
+ /**
1572
+ * Generate a CSP (Credential Service Provider) census root.
1573
+ * @param censusOrigin - The census origin type (e.g., CensusOrigin.CensusOriginCSP)
1574
+ * @param privKey - The private key in hex format
1575
+ * @returns The census root as a hexadecimal string
1576
+ * @throws if called before `await init()`, or if Go returns an error
1577
+ */
1578
+ async cspCensusRoot(censusOrigin, privKey) {
1579
+ if (!this.initialized) {
1580
+ throw new Error("DavinciCrypto not initialized \u2014 call `await init()` first");
1581
+ }
1582
+ const raw = globalThis.DavinciCrypto.cspCensusRoot(censusOrigin, privKey);
1583
+ if (raw.error) {
1584
+ throw new Error(`Go/WASM cspCensusRoot error: ${raw.error}`);
1585
+ }
1586
+ if (!raw.data) {
1587
+ throw new Error("Go/WASM cspCensusRoot returned no data");
1588
+ }
1589
+ return raw.data.root;
1590
+ }
1591
+ }
1592
+
1593
+ class DavinciSDK {
1594
+ constructor(config) {
1595
+ this.initialized = false;
1596
+ const resolvedConfig = resolveConfiguration({
1597
+ environment: config.environment,
1598
+ customUrls: {
1599
+ sequencer: config.sequencerUrl,
1600
+ census: config.censusUrl
1601
+ },
1602
+ customChain: config.chain
1603
+ });
1604
+ this.config = {
1605
+ signer: config.signer,
1606
+ sequencerUrl: config.sequencerUrl ?? resolvedConfig.sequencer,
1607
+ censusUrl: config.censusUrl ?? resolvedConfig.census,
1608
+ chain: config.chain ?? resolvedConfig.chain,
1609
+ contractAddresses: config.contractAddresses || {},
1610
+ useSequencerAddresses: config.useSequencerAddresses || false
1611
+ };
1612
+ this.apiService = new VocdoniApiService({
1613
+ sequencerURL: this.config.sequencerUrl,
1614
+ censusURL: this.config.censusUrl
1615
+ });
1616
+ this.censusProviders = config.censusProviders || {};
1617
+ }
1618
+ /**
1619
+ * Initialize the SDK and all its components
1620
+ * This must be called before using any SDK functionality
1621
+ */
1622
+ async init() {
1623
+ if (this.initialized) return;
1624
+ if (this.config.useSequencerAddresses) {
1625
+ await this.updateContractAddressesFromSequencer();
1626
+ }
1627
+ this.initialized = true;
1628
+ }
1629
+ /**
1630
+ * Get the API service for direct access to sequencer and census APIs
1631
+ */
1632
+ get api() {
1633
+ return this.apiService;
1634
+ }
1635
+ /**
1636
+ * Get the process registry service for process management
1637
+ */
1638
+ get processes() {
1639
+ if (!this._processRegistry) {
1640
+ const processRegistryAddress = this.resolveContractAddress("processRegistry");
1641
+ this._processRegistry = new ProcessRegistryService(processRegistryAddress, this.config.signer);
1642
+ }
1643
+ return this._processRegistry;
1644
+ }
1645
+ /**
1646
+ * Get the organization registry service for organization management
1647
+ */
1648
+ get organizations() {
1649
+ if (!this._organizationRegistry) {
1650
+ const organizationRegistryAddress = this.resolveContractAddress("organizationRegistry");
1651
+ this._organizationRegistry = new OrganizationRegistryService(organizationRegistryAddress, this.config.signer);
1652
+ }
1653
+ return this._organizationRegistry;
1654
+ }
1655
+ /**
1656
+ * Get or initialize the DavinciCrypto service for cryptographic operations
1657
+ */
1658
+ async getCrypto() {
1659
+ if (!this.davinciCrypto) {
1660
+ const info = await this.apiService.sequencer.getInfo();
1661
+ this.davinciCrypto = new DavinciCrypto({
1662
+ wasmExecUrl: info.ballotProofWasmHelperExecJsUrl,
1663
+ wasmUrl: info.ballotProofWasmHelperUrl
1664
+ });
1665
+ await this.davinciCrypto.init();
1666
+ }
1667
+ return this.davinciCrypto;
1668
+ }
1669
+ /**
1670
+ * Get the process orchestration service for simplified process creation
1671
+ */
1672
+ get processOrchestrator() {
1673
+ if (!this._processOrchestrator) {
1674
+ this._processOrchestrator = new ProcessOrchestrationService(
1675
+ this.processes,
1676
+ this.apiService,
1677
+ this.organizations,
1678
+ () => this.getCrypto(),
1679
+ this.config.signer
1680
+ );
1681
+ }
1682
+ return this._processOrchestrator;
1683
+ }
1684
+ /**
1685
+ * Get the vote orchestration service for simplified voting
1686
+ */
1687
+ get voteOrchestrator() {
1688
+ if (!this._voteOrchestrator) {
1689
+ this._voteOrchestrator = new VoteOrchestrationService(
1690
+ this.apiService,
1691
+ () => this.getCrypto(),
1692
+ this.config.signer,
1693
+ this.censusProviders
1694
+ );
1695
+ }
1696
+ return this._voteOrchestrator;
1697
+ }
1698
+ /**
1699
+ * Gets user-friendly process information from the blockchain.
1700
+ * This method fetches raw contract data and transforms it into a user-friendly format
1701
+ * that matches the ProcessConfig interface used for creation, plus additional runtime data.
1702
+ *
1703
+ * @param processId - The process ID to fetch
1704
+ * @returns Promise resolving to user-friendly process information
1705
+ *
1706
+ * @example
1707
+ * ```typescript
1708
+ * const processInfo = await sdk.getProcess("0x1234567890abcdef...");
1709
+ *
1710
+ * // Access the same fields as ProcessConfig
1711
+ * console.log("Title:", processInfo.title);
1712
+ * console.log("Description:", processInfo.description);
1713
+ * console.log("Questions:", processInfo.questions);
1714
+ * console.log("Census size:", processInfo.census.size);
1715
+ * console.log("Ballot config:", processInfo.ballot);
1716
+ *
1717
+ * // Plus additional runtime information
1718
+ * console.log("Status:", processInfo.status);
1719
+ * console.log("Creator:", processInfo.creator);
1720
+ * console.log("Start date:", processInfo.startDate);
1721
+ * console.log("End date:", processInfo.endDate);
1722
+ * console.log("Duration:", processInfo.duration, "seconds");
1723
+ * console.log("Time remaining:", processInfo.timeRemaining, "seconds");
1724
+ *
1725
+ * // Access raw contract data if needed
1726
+ * console.log("Raw data:", processInfo.raw);
1727
+ * ```
1728
+ */
1729
+ async getProcess(processId) {
1730
+ if (!this.initialized) {
1731
+ throw new Error("SDK must be initialized before getting processes. Call sdk.init() first.");
1732
+ }
1733
+ return this.processOrchestrator.getProcess(processId);
1734
+ }
1735
+ /**
1736
+ * Creates a complete voting process with minimal configuration.
1737
+ * This is the ultra-easy method for end users that handles all the complex orchestration internally.
1738
+ *
1739
+ * The method automatically:
1740
+ * - Gets encryption keys and initial state root from the sequencer
1741
+ * - Handles process creation signatures
1742
+ * - Coordinates between sequencer API and on-chain contract calls
1743
+ * - Creates and pushes metadata
1744
+ * - Submits the on-chain transaction
1745
+ *
1746
+ * @param config - Simplified process configuration
1747
+ * @returns Promise resolving to the process creation result
1748
+ *
1749
+ * @example
1750
+ * ```typescript
1751
+ * // Option 1: Using duration (traditional approach)
1752
+ * const result1 = await sdk.createProcess({
1753
+ * title: "My Election",
1754
+ * description: "A simple election",
1755
+ * census: {
1756
+ * type: CensusOrigin.CensusOriginMerkleTree,
1757
+ * root: "0x1234...",
1758
+ * size: 100,
1759
+ * uri: "ipfs://your-census-uri"
1760
+ * },
1761
+ * ballot: {
1762
+ * numFields: 2,
1763
+ * maxValue: "3",
1764
+ * minValue: "0",
1765
+ * uniqueValues: false,
1766
+ * costFromWeight: false,
1767
+ * costExponent: 10000,
1768
+ * maxValueSum: "6",
1769
+ * minValueSum: "0"
1770
+ * },
1771
+ * timing: {
1772
+ * startDate: new Date("2024-12-01T10:00:00Z"),
1773
+ * duration: 3600 * 24
1774
+ * },
1775
+ * questions: [
1776
+ * {
1777
+ * title: "What is your favorite color?",
1778
+ * choices: [
1779
+ * { title: "Red", value: 0 },
1780
+ * { title: "Blue", value: 1 }
1781
+ * ]
1782
+ * }
1783
+ * ]
1784
+ * });
1785
+ *
1786
+ * // Option 2: Using start and end dates
1787
+ * const result2 = await sdk.createProcess({
1788
+ * title: "Weekend Vote",
1789
+ * timing: {
1790
+ * startDate: "2024-12-07T09:00:00Z",
1791
+ * endDate: "2024-12-08T18:00:00Z"
1792
+ * }
1793
+ * });
1794
+ * ```
1795
+ */
1796
+ async createProcess(config) {
1797
+ if (!this.initialized) {
1798
+ throw new Error("SDK must be initialized before creating processes. Call sdk.init() first.");
1799
+ }
1800
+ return this.processOrchestrator.createProcess(config);
1801
+ }
1802
+ /**
1803
+ * Submit a vote with simplified configuration.
1804
+ * This is the ultra-easy method for end users that handles all the complex voting workflow internally.
1805
+ *
1806
+ * The method automatically:
1807
+ * - Fetches process information and validates voting is allowed
1808
+ * - Gets census proof (Merkle tree based)
1809
+ * - Generates cryptographic proofs and encrypts the vote
1810
+ * - Signs and submits the vote to the sequencer
1811
+ *
1812
+ * @param config - Simplified vote configuration
1813
+ * @returns Promise resolving to vote submission result
1814
+ *
1815
+ * @example
1816
+ * ```typescript
1817
+ * // Submit a vote with voter's private key
1818
+ * const voteResult = await sdk.submitVote({
1819
+ * processId: "0x1234567890abcdef...",
1820
+ * choices: [1, 0], // Vote for option 1 in question 1, option 0 in question 2
1821
+ * voterKey: "0x1234567890abcdef..." // Voter's private key
1822
+ * });
1823
+ *
1824
+ * console.log("Vote ID:", voteResult.voteId);
1825
+ * console.log("Status:", voteResult.status);
1826
+ *
1827
+ * // Submit a vote with a Wallet instance
1828
+ * import { Wallet } from "ethers";
1829
+ * const voterWallet = new Wallet("0x...");
1830
+ *
1831
+ * const voteResult2 = await sdk.submitVote({
1832
+ * processId: "0x1234567890abcdef...",
1833
+ * choices: [2], // Single question vote
1834
+ * voterKey: voterWallet
1835
+ * });
1836
+ * ```
1837
+ */
1838
+ async submitVote(config) {
1839
+ if (!this.initialized) {
1840
+ throw new Error("SDK must be initialized before submitting votes. Call sdk.init() first.");
1841
+ }
1842
+ return this.voteOrchestrator.submitVote(config);
1843
+ }
1844
+ /**
1845
+ * Get the status of a submitted vote.
1846
+ *
1847
+ * @param processId - The process ID
1848
+ * @param voteId - The vote ID returned from submitVote()
1849
+ * @returns Promise resolving to vote status information
1850
+ *
1851
+ * @example
1852
+ * ```typescript
1853
+ * const statusInfo = await sdk.getVoteStatus(processId, voteId);
1854
+ * console.log("Vote status:", statusInfo.status);
1855
+ * // Possible statuses: "pending", "verified", "aggregated", "processed", "settled", "error"
1856
+ * ```
1857
+ */
1858
+ async getVoteStatus(processId, voteId) {
1859
+ if (!this.initialized) {
1860
+ throw new Error("SDK must be initialized before getting vote status. Call sdk.init() first.");
1861
+ }
1862
+ return this.voteOrchestrator.getVoteStatus(processId, voteId);
1863
+ }
1864
+ /**
1865
+ * Check if an address has voted in a process.
1866
+ *
1867
+ * @param processId - The process ID
1868
+ * @param address - The voter's address
1869
+ * @returns Promise resolving to boolean indicating if the address has voted
1870
+ *
1871
+ * @example
1872
+ * ```typescript
1873
+ * const hasVoted = await sdk.hasAddressVoted(processId, "0x1234567890abcdef...");
1874
+ * if (hasVoted) {
1875
+ * console.log("This address has already voted");
1876
+ * }
1877
+ * ```
1878
+ */
1879
+ async hasAddressVoted(processId, address) {
1880
+ if (!this.initialized) {
1881
+ throw new Error("SDK must be initialized before checking vote status. Call sdk.init() first.");
1882
+ }
1883
+ return this.voteOrchestrator.hasAddressVoted(processId, address);
1884
+ }
1885
+ /**
1886
+ * Wait for a vote to reach a specific status.
1887
+ * Useful for waiting for vote confirmation and processing.
1888
+ *
1889
+ * @param processId - The process ID
1890
+ * @param voteId - The vote ID
1891
+ * @param targetStatus - The target status to wait for (default: "settled")
1892
+ * @param timeoutMs - Maximum time to wait in milliseconds (default: 300000 = 5 minutes)
1893
+ * @param pollIntervalMs - Polling interval in milliseconds (default: 5000 = 5 seconds)
1894
+ * @returns Promise resolving to final vote status
1895
+ *
1896
+ * @example
1897
+ * ```typescript
1898
+ * // Submit vote and wait for it to be settled
1899
+ * const voteResult = await sdk.submitVote({
1900
+ * processId: "0x1234567890abcdef...",
1901
+ * choices: [1],
1902
+ * voterKey: "0x..."
1903
+ * });
1904
+ *
1905
+ * // Wait for the vote to be fully processed
1906
+ * const finalStatus = await sdk.waitForVoteStatus(
1907
+ * voteResult.processId,
1908
+ * voteResult.voteId,
1909
+ * "settled", // Wait until vote is settled
1910
+ * 300000, // 5 minute timeout
1911
+ * 5000 // Check every 5 seconds
1912
+ * );
1913
+ *
1914
+ * console.log("Vote final status:", finalStatus.status);
1915
+ * ```
1916
+ */
1917
+ async waitForVoteStatus(processId, voteId, targetStatus = VoteStatus.Settled, timeoutMs = 3e5, pollIntervalMs = 5e3) {
1918
+ if (!this.initialized) {
1919
+ throw new Error("SDK must be initialized before waiting for vote status. Call sdk.init() first.");
1920
+ }
1921
+ return this.voteOrchestrator.waitForVoteStatus(processId, voteId, targetStatus, timeoutMs, pollIntervalMs);
1922
+ }
1923
+ /**
1924
+ * Resolve contract address based on configuration priority:
1925
+ * 1. If useSequencerAddresses is true: addresses from sequencer (highest priority)
1926
+ * 2. Custom addresses from config (if provided by user)
1927
+ * 3. Default deployed addresses from npm package
1928
+ */
1929
+ resolveContractAddress(contractName) {
1930
+ if (this.config.useSequencerAddresses) {
1931
+ return this.getDefaultContractAddress(contractName);
1932
+ }
1933
+ const customAddress = this.config.contractAddresses[contractName];
1934
+ if (customAddress) {
1935
+ return customAddress;
1936
+ }
1937
+ return this.getDefaultContractAddress(contractName);
1938
+ }
1939
+ /**
1940
+ * Get default contract address from deployed addresses
1941
+ */
1942
+ getDefaultContractAddress(contractName) {
1943
+ const chain = this.config.chain;
1944
+ switch (contractName) {
1945
+ case "processRegistry":
1946
+ return deployedAddresses.processRegistry[chain];
1947
+ case "organizationRegistry":
1948
+ return deployedAddresses.organizationRegistry[chain];
1949
+ case "stateTransitionVerifier":
1950
+ return deployedAddresses.stateTransitionVerifierGroth16[chain];
1951
+ case "resultsVerifier":
1952
+ return deployedAddresses.resultsVerifierGroth16[chain];
1953
+ case "sequencerRegistry":
1954
+ return deployedAddresses.sequencerRegistry[chain];
1955
+ default:
1956
+ throw new Error(`Unknown contract: ${contractName}`);
1957
+ }
1958
+ }
1959
+ /**
1960
+ * Update contract addresses from sequencer info if useSequencerAddresses is enabled
1961
+ * Sequencer addresses have priority over user-provided addresses
1962
+ */
1963
+ async updateContractAddressesFromSequencer() {
1964
+ try {
1965
+ const info = await this.apiService.sequencer.getInfo();
1966
+ const contracts = info.contracts;
1967
+ if (contracts.process) {
1968
+ this._processRegistry = new ProcessRegistryService(contracts.process, this.config.signer);
1969
+ this.config.contractAddresses.processRegistry = contracts.process;
1970
+ }
1971
+ if (contracts.organization) {
1972
+ this._organizationRegistry = new OrganizationRegistryService(contracts.organization, this.config.signer);
1973
+ this.config.contractAddresses.organizationRegistry = contracts.organization;
1974
+ }
1975
+ if (contracts.stateTransitionVerifier) {
1976
+ this.config.contractAddresses.stateTransitionVerifier = contracts.stateTransitionVerifier;
1977
+ }
1978
+ if (contracts.resultsVerifier) {
1979
+ this.config.contractAddresses.resultsVerifier = contracts.resultsVerifier;
1980
+ }
1981
+ } catch (error) {
1982
+ console.warn("Failed to fetch contract addresses from sequencer, using defaults:", error);
1983
+ }
1984
+ }
1985
+ /**
1986
+ * Get the current configuration
1987
+ */
1988
+ getConfig() {
1989
+ return { ...this.config };
1990
+ }
1991
+ /**
1992
+ * Check if the SDK has been initialized
1993
+ */
1994
+ isInitialized() {
1995
+ return this.initialized;
1996
+ }
1997
+ }
1998
+
1999
+ export { BaseService, CensusOrigin, CircomProof, ContractServiceError, DEFAULT_ENVIRONMENT_URLS, DavinciCrypto, DavinciSDK, ElectionMetadataTemplate, ElectionResultsTypeNames, OrganizationAdministratorError, OrganizationCreateError, OrganizationDeleteError, OrganizationRegistryService, OrganizationUpdateError, ProcessCensusError, ProcessCreateError, ProcessDurationError, ProcessOrchestrationService, ProcessRegistryService, ProcessResultError, ProcessStateTransitionError, ProcessStatus, ProcessStatusError, SmartContractService, TxStatus, VocdoniApiService, VocdoniCensusService, VocdoniSequencerService, VoteOrchestrationService, VoteStatus, assertCSPCensusProof, assertMerkleCensusProof, createProcessSignatureMessage, deployedAddresses, getElectionMetadataTemplate, getEnvironmentChain, getEnvironmentConfig, getEnvironmentUrls, isCSPCensusProof, isMerkleCensusProof, resolveConfiguration, resolveUrls, signProcessCreation, validateProcessId };
2000
+ //# sourceMappingURL=index.mjs.map