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