@vocdoni/davinci-sdk 0.0.1 → 0.0.3

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 CHANGED
@@ -213,6 +213,281 @@ function assertCSPCensusProof(proof) {
213
213
  }
214
214
  }
215
215
 
216
+ var CensusType = /* @__PURE__ */ ((CensusType2) => {
217
+ CensusType2["PLAIN"] = "plain";
218
+ CensusType2["WEIGHTED"] = "weighted";
219
+ CensusType2["CSP"] = "csp";
220
+ return CensusType2;
221
+ })(CensusType || {});
222
+ class Census {
223
+ constructor(type) {
224
+ this._censusId = null;
225
+ this._censusRoot = null;
226
+ this._censusURI = null;
227
+ this._size = null;
228
+ this._type = type;
229
+ }
230
+ get censusId() {
231
+ return this._censusId;
232
+ }
233
+ get censusRoot() {
234
+ return this._censusRoot;
235
+ }
236
+ get censusURI() {
237
+ return this._censusURI;
238
+ }
239
+ get type() {
240
+ return this._type;
241
+ }
242
+ get size() {
243
+ return this._size;
244
+ }
245
+ get isPublished() {
246
+ return this._censusRoot !== null && this._censusURI !== null;
247
+ }
248
+ /**
249
+ * Convert CensusType to CensusOrigin enum for API compatibility
250
+ */
251
+ get censusOrigin() {
252
+ switch (this._type) {
253
+ case "plain" /* PLAIN */:
254
+ case "weighted" /* WEIGHTED */:
255
+ return CensusOrigin.CensusOriginMerkleTree;
256
+ case "csp" /* CSP */:
257
+ return CensusOrigin.CensusOriginCSP;
258
+ default:
259
+ throw new Error(`Unknown census type: ${this._type}`);
260
+ }
261
+ }
262
+ }
263
+
264
+ class PlainCensus extends Census {
265
+ constructor() {
266
+ super(CensusType.PLAIN);
267
+ this._participants = /* @__PURE__ */ new Set();
268
+ }
269
+ /**
270
+ * Add participant(s) with automatic weight=1
271
+ * @param addresses - Single address or array of addresses
272
+ */
273
+ add(addresses) {
274
+ const toAdd = Array.isArray(addresses) ? addresses : [addresses];
275
+ for (const address of toAdd) {
276
+ this.validateAddress(address);
277
+ this._participants.add(address.toLowerCase());
278
+ }
279
+ }
280
+ /**
281
+ * Remove participant by address
282
+ */
283
+ remove(address) {
284
+ this._participants.delete(address.toLowerCase());
285
+ }
286
+ /**
287
+ * Get all participants as CensusParticipant array (for API)
288
+ * All participants have weight="1"
289
+ */
290
+ get participants() {
291
+ return Array.from(this._participants).map((key) => ({
292
+ key,
293
+ weight: "1"
294
+ // Everyone has weight=1 in plain census
295
+ }));
296
+ }
297
+ /**
298
+ * Get addresses only
299
+ */
300
+ get addresses() {
301
+ return Array.from(this._participants);
302
+ }
303
+ validateAddress(address) {
304
+ if (!address || typeof address !== "string") {
305
+ throw new Error("Address is required and must be a string");
306
+ }
307
+ if (!/^(0x)?[0-9a-fA-F]{40}$/i.test(address)) {
308
+ throw new Error(`Invalid Ethereum address format: ${address}`);
309
+ }
310
+ }
311
+ /**
312
+ * Internal method called after publishing
313
+ * @internal
314
+ */
315
+ _setPublishedData(root, uri, size, censusId) {
316
+ this._censusRoot = root;
317
+ this._censusURI = uri;
318
+ this._size = size;
319
+ if (censusId) this._censusId = censusId;
320
+ }
321
+ }
322
+
323
+ class WeightedCensus extends Census {
324
+ constructor() {
325
+ super(CensusType.WEIGHTED);
326
+ this._participants = /* @__PURE__ */ new Map();
327
+ }
328
+ /**
329
+ * Add participant(s) with custom weights
330
+ * Weight can be provided as string, number, or bigint - will be converted to string internally
331
+ * @param participant - Single participant or array of participants with custom weights
332
+ */
333
+ add(participant) {
334
+ const toAdd = Array.isArray(participant) ? participant : [participant];
335
+ for (const p of toAdd) {
336
+ this.validateParticipant(p);
337
+ const weightString = this.normalizeWeight(p.weight);
338
+ this._participants.set(p.key.toLowerCase(), weightString);
339
+ }
340
+ }
341
+ /**
342
+ * Remove participant by address
343
+ */
344
+ remove(address) {
345
+ this._participants.delete(address.toLowerCase());
346
+ }
347
+ /**
348
+ * Get all participants as CensusParticipant array
349
+ */
350
+ get participants() {
351
+ return Array.from(this._participants.entries()).map(([key, weight]) => ({
352
+ key,
353
+ weight
354
+ }));
355
+ }
356
+ /**
357
+ * Get participant addresses
358
+ */
359
+ get addresses() {
360
+ return Array.from(this._participants.keys());
361
+ }
362
+ /**
363
+ * Get weight for specific address
364
+ */
365
+ getWeight(address) {
366
+ return this._participants.get(address.toLowerCase());
367
+ }
368
+ /**
369
+ * Normalizes weight from string, number, or bigint to string
370
+ */
371
+ normalizeWeight(weight) {
372
+ if (typeof weight === "string") {
373
+ if (!/^\d+$/.test(weight)) {
374
+ throw new Error(`Invalid weight format: ${weight}. Must be a positive integer.`);
375
+ }
376
+ return weight;
377
+ }
378
+ if (typeof weight === "number") {
379
+ if (!Number.isInteger(weight) || weight < 0) {
380
+ throw new Error(`Invalid weight: ${weight}. Must be a positive integer.`);
381
+ }
382
+ return weight.toString();
383
+ }
384
+ if (typeof weight === "bigint") {
385
+ if (weight < 0n) {
386
+ throw new Error(`Invalid weight: ${weight}. Must be a positive integer.`);
387
+ }
388
+ return weight.toString();
389
+ }
390
+ throw new Error(`Invalid weight type. Must be string, number, or bigint.`);
391
+ }
392
+ validateParticipant(participant) {
393
+ if (!participant.key || typeof participant.key !== "string") {
394
+ throw new Error("Participant key (address) is required");
395
+ }
396
+ if (!/^(0x)?[0-9a-fA-F]{40}$/i.test(participant.key)) {
397
+ throw new Error(`Invalid Ethereum address format: ${participant.key}`);
398
+ }
399
+ if (participant.weight === void 0 || participant.weight === null) {
400
+ throw new Error("Participant weight is required");
401
+ }
402
+ }
403
+ /**
404
+ * Internal method called after publishing
405
+ * @internal
406
+ */
407
+ _setPublishedData(root, uri, size, censusId) {
408
+ this._censusRoot = root;
409
+ this._censusURI = uri;
410
+ this._size = size;
411
+ if (censusId) this._censusId = censusId;
412
+ }
413
+ }
414
+
415
+ class CspCensus extends Census {
416
+ constructor(publicKey, cspURI, size) {
417
+ super(CensusType.CSP);
418
+ if (!/^(0x)?[0-9a-fA-F]+$/.test(publicKey)) {
419
+ throw new Error("Public key is missing or invalid");
420
+ }
421
+ try {
422
+ new URL(cspURI);
423
+ } catch {
424
+ throw new Error("CSP URI is missing or invalid");
425
+ }
426
+ this._publicKey = publicKey;
427
+ this._cspURI = cspURI;
428
+ this._censusRoot = publicKey;
429
+ this._censusURI = cspURI;
430
+ this._size = size;
431
+ }
432
+ get publicKey() {
433
+ return this._publicKey;
434
+ }
435
+ get cspURI() {
436
+ return this._cspURI;
437
+ }
438
+ }
439
+
440
+ class PublishedCensus extends Census {
441
+ constructor(type, root, uri, size) {
442
+ super(type);
443
+ this._censusRoot = root;
444
+ this._censusURI = uri;
445
+ this._size = size;
446
+ }
447
+ }
448
+
449
+ class CensusOrchestrator {
450
+ constructor(censusService) {
451
+ this.censusService = censusService;
452
+ }
453
+ /**
454
+ * Publishes a PlainCensus or WeightedCensus
455
+ * Creates a working census, adds participants, and publishes it
456
+ */
457
+ async publish(census) {
458
+ if (census.isPublished) {
459
+ throw new Error("Census is already published");
460
+ }
461
+ if (census.participants.length === 0) {
462
+ throw new Error("Cannot publish empty census");
463
+ }
464
+ const censusId = await this.censusService.createCensus();
465
+ await this.censusService.addParticipants(censusId, census.participants);
466
+ const publishResponse = await this.censusService.publishCensus(censusId);
467
+ census._setPublishedData(
468
+ publishResponse.root,
469
+ publishResponse.uri,
470
+ publishResponse.participantCount,
471
+ censusId
472
+ );
473
+ }
474
+ /**
475
+ * Gets census data for process creation
476
+ * Throws if census is not published
477
+ */
478
+ getCensusData(census) {
479
+ if (!census.isPublished) {
480
+ throw new Error("Census must be published before creating a process");
481
+ }
482
+ return {
483
+ type: census.censusOrigin,
484
+ root: census.censusRoot,
485
+ uri: census.censusURI,
486
+ size: census.size
487
+ };
488
+ }
489
+ }
490
+
216
491
  function createProcessSignatureMessage(processId) {
217
492
  const cleanProcessId = processId.replace(/^0x/, "").toLowerCase();
218
493
  return `I am creating a new voting process for the davinci.vote protocol identified with id ${cleanProcessId}`;
@@ -303,11 +578,15 @@ class VocdoniSequencerService extends BaseService {
303
578
  try {
304
579
  const response = await fetch(hashOrUrl);
305
580
  if (!response.ok) {
306
- throw new Error(`Failed to fetch metadata from URL: ${response.status} ${response.statusText}`);
581
+ throw new Error(
582
+ `Failed to fetch metadata from URL: ${response.status} ${response.statusText}`
583
+ );
307
584
  }
308
585
  return await response.json();
309
586
  } catch (error) {
310
- throw new Error(`Failed to fetch metadata from URL: ${error instanceof Error ? error.message : "Unknown error"}`);
587
+ throw new Error(
588
+ `Failed to fetch metadata from URL: ${error instanceof Error ? error.message : "Unknown error"}`
589
+ );
311
590
  }
312
591
  }
313
592
  if (!isHexString(hashOrUrl)) {
@@ -343,96 +622,6 @@ class VocdoniApiService {
343
622
  }
344
623
  }
345
624
 
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
625
  var TxStatus = /* @__PURE__ */ ((TxStatus2) => {
437
626
  TxStatus2["Pending"] = "pending";
438
627
  TxStatus2["Completed"] = "completed";
@@ -441,23 +630,29 @@ var TxStatus = /* @__PURE__ */ ((TxStatus2) => {
441
630
  return TxStatus2;
442
631
  })(TxStatus || {});
443
632
  class SmartContractService {
633
+ constructor() {
634
+ /** Active polling intervals for event listeners using fallback mode */
635
+ this.pollingIntervals = [];
636
+ /** Default polling interval in milliseconds for event listener fallback */
637
+ this.eventPollingInterval = 5e3;
638
+ }
444
639
  /**
445
640
  * Sends a transaction and yields status events during its lifecycle.
446
641
  * This method handles the complete transaction flow from submission to completion,
447
642
  * including error handling and status updates.
448
- *
643
+ *
449
644
  * @template T - The type of the successful response data
450
645
  * @param txPromise - Promise resolving to the transaction response
451
646
  * @param responseHandler - Function to process the successful transaction result
452
647
  * @returns AsyncGenerator yielding transaction status events
453
- *
648
+ *
454
649
  * @example
455
650
  * ```typescript
456
651
  * const txStream = await this.sendTx(
457
652
  * contract.someMethod(),
458
653
  * async () => await contract.getUpdatedValue()
459
654
  * );
460
- *
655
+ *
461
656
  * for await (const event of txStream) {
462
657
  * switch (event.status) {
463
658
  * case TxStatus.Pending:
@@ -494,12 +689,12 @@ class SmartContractService {
494
689
  * Executes a transaction stream and returns the result or throws an error.
495
690
  * This is a convenience method that processes a transaction stream and either
496
691
  * returns the successful result or throws an appropriate error.
497
- *
692
+ *
498
693
  * @template T - The type of the successful response data
499
694
  * @param stream - AsyncGenerator of transaction status events
500
695
  * @returns Promise resolving to the successful response data
501
696
  * @throws Error if the transaction fails or reverts
502
- *
697
+ *
503
698
  * @example
504
699
  * ```typescript
505
700
  * try {
@@ -529,11 +724,11 @@ class SmartContractService {
529
724
  * Normalizes event listener arguments between different ethers.js versions.
530
725
  * This helper method ensures consistent event argument handling regardless of
531
726
  * whether the event payload follows ethers v5 or v6 format.
532
- *
727
+ *
533
728
  * @template Args - Tuple type representing the expected event arguments
534
729
  * @param callback - The event callback function to normalize
535
730
  * @returns Normalized event listener function
536
- *
731
+ *
537
732
  * @example
538
733
  * ```typescript
539
734
  * contract.on('Transfer', this.normalizeListener((from: string, to: string, amount: BigInt) => {
@@ -552,12 +747,153 @@ class SmartContractService {
552
747
  callback(...args);
553
748
  };
554
749
  }
750
+ /**
751
+ * Sets up an event listener with automatic fallback for RPCs that don't support eth_newFilter.
752
+ * First attempts to use contract.on() which relies on eth_newFilter. If the RPC doesn't support
753
+ * this method (error code -32601), automatically falls back to polling with queryFilter.
754
+ *
755
+ * @template Args - Tuple type representing the event arguments
756
+ * @param contract - The contract instance to listen to
757
+ * @param eventFilter - The event filter to listen for
758
+ * @param callback - The callback function to invoke when the event occurs
759
+ *
760
+ * @example
761
+ * ```typescript
762
+ * this.setupEventListener(
763
+ * this.contract,
764
+ * this.contract.filters.Transfer(),
765
+ * (from: string, to: string, amount: bigint) => {
766
+ * console.log(`Transfer: ${from} -> ${to}: ${amount}`);
767
+ * }
768
+ * );
769
+ * ```
770
+ */
771
+ async setupEventListener(contract, eventFilter, callback) {
772
+ const normalizedCallback = this.normalizeListener(callback);
773
+ const provider = contract.runner?.provider;
774
+ if (!provider) {
775
+ console.warn("No provider available for event listeners");
776
+ return;
777
+ }
778
+ try {
779
+ const testFilter = {
780
+ address: await contract.getAddress(),
781
+ topics: []
782
+ };
783
+ if ("send" in provider && typeof provider.send === "function") {
784
+ try {
785
+ const filterId = await provider.send("eth_newFilter", [testFilter]);
786
+ await provider.send("eth_getFilterChanges", [filterId]);
787
+ contract.on(eventFilter, normalizedCallback);
788
+ return;
789
+ } catch (error) {
790
+ if (this.isUnsupportedMethodError(error)) {
791
+ console.warn(
792
+ "RPC does not fully support eth_newFilter/eth_getFilterChanges, falling back to polling for events. This may result in delayed event notifications."
793
+ );
794
+ this.setupPollingListener(contract, eventFilter, callback);
795
+ return;
796
+ }
797
+ }
798
+ }
799
+ const errorHandler = (error) => {
800
+ if (this.isUnsupportedMethodError(error)) {
801
+ contract.off(eventFilter, normalizedCallback);
802
+ contract.off("error", errorHandler);
803
+ console.warn(
804
+ "RPC does not support eth_newFilter, falling back to polling for events. This may result in delayed event notifications."
805
+ );
806
+ this.setupPollingListener(contract, eventFilter, callback);
807
+ }
808
+ };
809
+ contract.once("error", errorHandler);
810
+ contract.on(eventFilter, normalizedCallback);
811
+ } catch (error) {
812
+ console.warn("Error setting up event listener, falling back to polling:", error.message);
813
+ this.setupPollingListener(contract, eventFilter, callback);
814
+ }
815
+ }
816
+ /**
817
+ * Checks if an error indicates that the RPC method is unsupported or filter operations are not working.
818
+ * This includes:
819
+ * - Method not found (-32601): RPC doesn't support eth_newFilter
820
+ * - Filter not found (-32000): RPC doesn't properly maintain filters
821
+ *
822
+ * @param error - The error to check
823
+ * @returns true if the error indicates unsupported or broken filter functionality
824
+ */
825
+ isUnsupportedMethodError(error) {
826
+ const isMethodNotFound = error?.code === -32601 || error?.error?.code === -32601 || error?.data?.code === -32601 || typeof error?.message === "string" && error.message.includes("unsupported method");
827
+ const isFilterNotFound = (error?.code === -32e3 || error?.error?.code === -32e3 || error?.code === "UNKNOWN_ERROR" && error?.error?.code === -32e3) && (error?.message?.includes("filter not found") || error?.error?.message?.includes("filter not found"));
828
+ return isMethodNotFound || isFilterNotFound;
829
+ }
830
+ /**
831
+ * Sets up a polling-based event listener as fallback when eth_newFilter is not supported.
832
+ * Periodically queries for new events and invokes the callback for each new event found.
833
+ *
834
+ * @template Args - Tuple type representing the event arguments
835
+ * @param contract - The contract instance to poll
836
+ * @param eventFilter - The event filter to poll for
837
+ * @param callback - The callback function to invoke for each event
838
+ */
839
+ setupPollingListener(contract, eventFilter, callback) {
840
+ let lastProcessedBlock = 0;
841
+ const poll = async () => {
842
+ try {
843
+ const provider = contract.runner?.provider;
844
+ if (!provider) {
845
+ console.warn("No provider available for polling events");
846
+ return;
847
+ }
848
+ const currentBlock = await provider.getBlockNumber();
849
+ if (lastProcessedBlock === 0) {
850
+ lastProcessedBlock = currentBlock - 1;
851
+ }
852
+ if (currentBlock > lastProcessedBlock) {
853
+ const events = await contract.queryFilter(
854
+ eventFilter,
855
+ lastProcessedBlock + 1,
856
+ currentBlock
857
+ );
858
+ for (const event of events) {
859
+ if ("args" in event && event.args) {
860
+ callback(...event.args);
861
+ }
862
+ }
863
+ lastProcessedBlock = currentBlock;
864
+ }
865
+ } catch (error) {
866
+ console.error("Error polling for events:", error);
867
+ }
868
+ };
869
+ const intervalId = setInterval(poll, this.eventPollingInterval);
870
+ this.pollingIntervals.push(intervalId);
871
+ poll();
872
+ }
873
+ /**
874
+ * Clears all active polling intervals.
875
+ * Should be called when removing all listeners or cleaning up the service.
876
+ */
877
+ clearPollingIntervals() {
878
+ for (const intervalId of this.pollingIntervals) {
879
+ clearInterval(intervalId);
880
+ }
881
+ this.pollingIntervals = [];
882
+ }
883
+ /**
884
+ * Sets the polling interval for event listeners using the fallback mechanism.
885
+ *
886
+ * @param intervalMs - Polling interval in milliseconds
887
+ */
888
+ setEventPollingInterval(intervalMs) {
889
+ this.eventPollingInterval = intervalMs;
890
+ }
555
891
  }
556
892
 
557
893
  class ContractServiceError extends Error {
558
894
  /**
559
895
  * Creates a new ContractServiceError instance.
560
- *
896
+ *
561
897
  * @param message - The error message describing what went wrong
562
898
  * @param operation - The operation that was being performed when the error occurred
563
899
  */
@@ -710,7 +1046,7 @@ class ProcessRegistryService extends SmartContractService {
710
1046
  }
711
1047
  /**
712
1048
  * Sets the results for a voting process.
713
- *
1049
+ *
714
1050
  * @param processID - The ID of the process to set results for
715
1051
  * @param proof - Zero-knowledge proof validating the results
716
1052
  * @param input - Input data for the proof verification
@@ -718,11 +1054,7 @@ class ProcessRegistryService extends SmartContractService {
718
1054
  */
719
1055
  setProcessResults(processID, proof, input) {
720
1056
  return this.sendTx(
721
- this.contract.setProcessResults(
722
- processID,
723
- proof,
724
- input
725
- ).catch((e) => {
1057
+ this.contract.setProcessResults(processID, proof, input).catch((e) => {
726
1058
  throw new ProcessResultError(e.message, "setResults");
727
1059
  }),
728
1060
  async () => ({ success: true })
@@ -730,43 +1062,50 @@ class ProcessRegistryService extends SmartContractService {
730
1062
  }
731
1063
  // ─── EVENT LISTENERS ───────────────────────────────────────────────────────
732
1064
  onProcessCreated(cb) {
733
- this.contract.on(
1065
+ this.setupEventListener(
1066
+ this.contract,
734
1067
  this.contract.filters.ProcessCreated(),
735
- this.normalizeListener(cb)
736
- );
1068
+ cb
1069
+ ).catch((err) => console.error("Error setting up ProcessCreated listener:", err));
737
1070
  }
738
1071
  onProcessStatusChanged(cb) {
739
- this.contract.on(
1072
+ this.setupEventListener(
1073
+ this.contract,
740
1074
  this.contract.filters.ProcessStatusChanged(),
741
- this.normalizeListener(cb)
742
- );
1075
+ cb
1076
+ ).catch((err) => console.error("Error setting up ProcessStatusChanged listener:", err));
743
1077
  }
744
1078
  onCensusUpdated(cb) {
745
- this.contract.on(
1079
+ this.setupEventListener(
1080
+ this.contract,
746
1081
  this.contract.filters.CensusUpdated(),
747
- this.normalizeListener(cb)
748
- );
1082
+ cb
1083
+ ).catch((err) => console.error("Error setting up CensusUpdated listener:", err));
749
1084
  }
750
1085
  onProcessDurationChanged(cb) {
751
- this.contract.on(
1086
+ this.setupEventListener(
1087
+ this.contract,
752
1088
  this.contract.filters.ProcessDurationChanged(),
753
- this.normalizeListener(cb)
754
- );
1089
+ cb
1090
+ ).catch((err) => console.error("Error setting up ProcessDurationChanged listener:", err));
755
1091
  }
756
1092
  onStateRootUpdated(cb) {
757
- this.contract.on(
1093
+ this.setupEventListener(
1094
+ this.contract,
758
1095
  this.contract.filters.ProcessStateRootUpdated(),
759
- this.normalizeListener(cb)
760
- );
1096
+ cb
1097
+ ).catch((err) => console.error("Error setting up StateRootUpdated listener:", err));
761
1098
  }
762
1099
  onProcessResultsSet(cb) {
763
- this.contract.on(
1100
+ this.setupEventListener(
1101
+ this.contract,
764
1102
  this.contract.filters.ProcessResultsSet(),
765
- this.normalizeListener(cb)
766
- );
1103
+ cb
1104
+ ).catch((err) => console.error("Error setting up ProcessResultsSet listener:", err));
767
1105
  }
768
1106
  removeAllListeners() {
769
1107
  this.contract.removeAllListeners();
1108
+ this.clearPollingIntervals();
770
1109
  }
771
1110
  }
772
1111
 
@@ -777,6 +1116,34 @@ class ProcessOrchestrationService {
777
1116
  this.organizationRegistry = organizationRegistry;
778
1117
  this.getCrypto = getCrypto;
779
1118
  this.signer = signer;
1119
+ this.censusOrchestrator = new CensusOrchestrator(apiService.census);
1120
+ }
1121
+ /**
1122
+ * Handles census - auto-publishes if needed and returns census config
1123
+ * @private
1124
+ */
1125
+ async handleCensus(census) {
1126
+ if ("isPublished" in census) {
1127
+ if (census instanceof PlainCensus || census instanceof WeightedCensus) {
1128
+ if (!census.isPublished) {
1129
+ const censusBaseURL = this.apiService.census?.["axios"]?.defaults?.baseURL;
1130
+ if (!censusBaseURL || censusBaseURL === "" || censusBaseURL === "undefined") {
1131
+ throw new Error(
1132
+ 'Census API URL is required to publish PlainCensus or WeightedCensus. Please provide "censusUrl" when initializing DavinciSDK, or use a pre-published census.'
1133
+ );
1134
+ }
1135
+ await this.censusOrchestrator.publish(census);
1136
+ }
1137
+ }
1138
+ const censusData = this.censusOrchestrator.getCensusData(census);
1139
+ return {
1140
+ type: censusData.type,
1141
+ root: censusData.root,
1142
+ size: censusData.size,
1143
+ uri: censusData.uri
1144
+ };
1145
+ }
1146
+ return census;
780
1147
  }
781
1148
  /**
782
1149
  * Gets user-friendly process information by transforming raw contract data
@@ -850,14 +1217,119 @@ class ProcessOrchestrationService {
850
1217
  };
851
1218
  }
852
1219
  /**
853
- * Creates a complete voting process with minimal configuration
854
- * This method handles all the complex orchestration internally
1220
+ * Creates a complete voting process and returns an async generator that yields transaction status events.
1221
+ * This method allows you to monitor the transaction progress in real-time.
1222
+ *
1223
+ * @param config - Process configuration
1224
+ * @returns AsyncGenerator yielding transaction status events with ProcessCreationResult
1225
+ *
1226
+ * @example
1227
+ * ```typescript
1228
+ * const stream = sdk.createProcessStream({
1229
+ * title: "My Election",
1230
+ * description: "A simple election",
1231
+ * census: { ... },
1232
+ * ballot: { ... },
1233
+ * timing: { ... },
1234
+ * questions: [ ... ]
1235
+ * });
1236
+ *
1237
+ * for await (const event of stream) {
1238
+ * switch (event.status) {
1239
+ * case "pending":
1240
+ * console.log("Transaction pending:", event.hash);
1241
+ * break;
1242
+ * case "completed":
1243
+ * console.log("Process created:", event.response.processId);
1244
+ * console.log("Transaction hash:", event.response.transactionHash);
1245
+ * break;
1246
+ * case "failed":
1247
+ * console.error("Transaction failed:", event.error);
1248
+ * break;
1249
+ * case "reverted":
1250
+ * console.error("Transaction reverted:", event.reason);
1251
+ * break;
1252
+ * }
1253
+ * }
1254
+ * ```
1255
+ */
1256
+ async *createProcessStream(config) {
1257
+ const data = await this.prepareProcessCreation(config);
1258
+ const encryptionKey = {
1259
+ x: data.sequencerResult.encryptionPubKey[0],
1260
+ y: data.sequencerResult.encryptionPubKey[1]
1261
+ };
1262
+ const txStream = this.processRegistry.newProcess(
1263
+ ProcessStatus.READY,
1264
+ data.startTime,
1265
+ data.duration,
1266
+ data.ballotMode,
1267
+ data.census,
1268
+ data.metadataUri,
1269
+ encryptionKey,
1270
+ BigInt(data.sequencerResult.stateRoot)
1271
+ );
1272
+ let transactionHash = "unknown";
1273
+ for await (const event of txStream) {
1274
+ if (event.status === TxStatus.Pending) {
1275
+ transactionHash = event.hash;
1276
+ yield { status: TxStatus.Pending, hash: event.hash };
1277
+ } else if (event.status === TxStatus.Completed) {
1278
+ yield {
1279
+ status: TxStatus.Completed,
1280
+ response: {
1281
+ processId: data.processId,
1282
+ transactionHash
1283
+ }
1284
+ };
1285
+ break;
1286
+ } else if (event.status === TxStatus.Failed) {
1287
+ yield { status: TxStatus.Failed, error: event.error };
1288
+ break;
1289
+ } else if (event.status === TxStatus.Reverted) {
1290
+ yield { status: TxStatus.Reverted, reason: event.reason };
1291
+ break;
1292
+ }
1293
+ }
1294
+ }
1295
+ /**
1296
+ * Creates a complete voting process with minimal configuration.
1297
+ * This is the ultra-easy method for end users that handles all the complex orchestration internally.
1298
+ *
1299
+ * For real-time transaction status updates, use createProcessStream() instead.
1300
+ *
1301
+ * The method automatically:
1302
+ * - Gets encryption keys and initial state root from the sequencer
1303
+ * - Handles process creation signatures
1304
+ * - Coordinates between sequencer API and on-chain contract calls
1305
+ * - Creates and pushes metadata
1306
+ * - Submits the on-chain transaction
1307
+ *
1308
+ * @param config - Simplified process configuration
1309
+ * @returns Promise resolving to the process creation result
855
1310
  */
856
1311
  async createProcess(config) {
1312
+ for await (const event of this.createProcessStream(config)) {
1313
+ if (event.status === "completed") {
1314
+ return event.response;
1315
+ } else if (event.status === "failed") {
1316
+ throw event.error;
1317
+ } else if (event.status === "reverted") {
1318
+ throw new Error(`Transaction reverted: ${event.reason || "unknown reason"}`);
1319
+ }
1320
+ }
1321
+ throw new Error("Process creation stream ended unexpectedly");
1322
+ }
1323
+ /**
1324
+ * Prepares all data needed for process creation
1325
+ * @private
1326
+ */
1327
+ async prepareProcessCreation(config) {
857
1328
  const { startTime, duration } = this.calculateTiming(config.timing);
858
1329
  const signerAddress = await this.signer.getAddress();
859
1330
  const processId = await this.processRegistry.getNextProcessId(signerAddress);
860
- const censusRoot = config.census.root;
1331
+ const censusConfig = await this.handleCensus(config.census);
1332
+ const censusRoot = censusConfig.root;
861
1333
  const ballotMode = config.ballot;
862
1334
  const metadata = this.createMetadata(config);
863
1335
  const metadataHash = await this.apiService.sequencer.pushMetadata(metadata);
@@ -868,43 +1340,23 @@ class ProcessOrchestrationService {
868
1340
  censusRoot,
869
1341
  ballotMode,
870
1342
  signature,
871
- censusOrigin: config.census.type
1343
+ censusOrigin: censusConfig.type
872
1344
  });
873
1345
  const census = {
874
- censusOrigin: config.census.type,
875
- maxVotes: config.census.size.toString(),
1346
+ censusOrigin: censusConfig.type,
1347
+ maxVotes: censusConfig.size.toString(),
876
1348
  censusRoot,
877
- censusURI: config.census.uri
1349
+ censusURI: censusConfig.uri
878
1350
  };
879
- const encryptionKey = {
880
- x: sequencerResult.encryptionPubKey[0],
881
- y: sequencerResult.encryptionPubKey[1]
882
- };
883
- const txStream = this.processRegistry.newProcess(
884
- ProcessStatus.READY,
1351
+ return {
1352
+ processId,
885
1353
  startTime,
886
1354
  duration,
1355
+ censusRoot,
887
1356
  ballotMode,
888
- census,
889
1357
  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
1358
+ sequencerResult,
1359
+ census
908
1360
  };
909
1361
  }
910
1362
  /**
@@ -970,17 +1422,323 @@ class ProcessOrchestrationService {
970
1422
  if (!config.questions || config.questions.length === 0) {
971
1423
  throw new Error("Questions are required. Please provide at least one question with choices.");
972
1424
  }
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;
1425
+ metadata.questions = config.questions.map((q) => ({
1426
+ title: { default: q.title },
1427
+ description: { default: q.description || "" },
1428
+ meta: {},
1429
+ choices: q.choices.map((c) => ({
1430
+ title: { default: c.title },
1431
+ value: c.value,
1432
+ meta: {}
1433
+ }))
1434
+ }));
1435
+ return metadata;
1436
+ }
1437
+ /**
1438
+ * Ends a voting process by setting its status to ENDED.
1439
+ * Returns an async generator that yields transaction status events.
1440
+ *
1441
+ * @param processId - The process ID to end
1442
+ * @returns AsyncGenerator yielding transaction status events
1443
+ *
1444
+ * @example
1445
+ * ```typescript
1446
+ * const stream = sdk.endProcessStream("0x1234567890abcdef...");
1447
+ *
1448
+ * for await (const event of stream) {
1449
+ * switch (event.status) {
1450
+ * case "pending":
1451
+ * console.log("Transaction pending:", event.hash);
1452
+ * break;
1453
+ * case "completed":
1454
+ * console.log("Process ended successfully");
1455
+ * break;
1456
+ * case "failed":
1457
+ * console.error("Transaction failed:", event.error);
1458
+ * break;
1459
+ * case "reverted":
1460
+ * console.error("Transaction reverted:", event.reason);
1461
+ * break;
1462
+ * }
1463
+ * }
1464
+ * ```
1465
+ */
1466
+ async *endProcessStream(processId) {
1467
+ const txStream = this.processRegistry.setProcessStatus(processId, ProcessStatus.ENDED);
1468
+ for await (const event of txStream) {
1469
+ if (event.status === TxStatus.Pending) {
1470
+ yield { status: TxStatus.Pending, hash: event.hash };
1471
+ } else if (event.status === TxStatus.Completed) {
1472
+ yield {
1473
+ status: TxStatus.Completed,
1474
+ response: { success: true }
1475
+ };
1476
+ break;
1477
+ } else if (event.status === TxStatus.Failed) {
1478
+ yield { status: TxStatus.Failed, error: event.error };
1479
+ break;
1480
+ } else if (event.status === TxStatus.Reverted) {
1481
+ yield { status: TxStatus.Reverted, reason: event.reason };
1482
+ break;
1483
+ }
1484
+ }
1485
+ }
1486
+ /**
1487
+ * Ends a voting process by setting its status to ENDED.
1488
+ * This is a simplified method that waits for transaction completion.
1489
+ *
1490
+ * For real-time transaction status updates, use endProcessStream() instead.
1491
+ *
1492
+ * @param processId - The process ID to end
1493
+ * @returns Promise resolving when the process is ended
1494
+ *
1495
+ * @example
1496
+ * ```typescript
1497
+ * await sdk.endProcess("0x1234567890abcdef...");
1498
+ * console.log("Process ended successfully");
1499
+ * ```
1500
+ */
1501
+ async endProcess(processId) {
1502
+ for await (const event of this.endProcessStream(processId)) {
1503
+ if (event.status === "completed") {
1504
+ return;
1505
+ } else if (event.status === "failed") {
1506
+ throw event.error;
1507
+ } else if (event.status === "reverted") {
1508
+ throw new Error(`Transaction reverted: ${event.reason || "unknown reason"}`);
1509
+ }
1510
+ }
1511
+ throw new Error("End process stream ended unexpectedly");
1512
+ }
1513
+ /**
1514
+ * Pauses a voting process by setting its status to PAUSED.
1515
+ * Returns an async generator that yields transaction status events.
1516
+ *
1517
+ * @param processId - The process ID to pause
1518
+ * @returns AsyncGenerator yielding transaction status events
1519
+ *
1520
+ * @example
1521
+ * ```typescript
1522
+ * const stream = sdk.pauseProcessStream("0x1234567890abcdef...");
1523
+ *
1524
+ * for await (const event of stream) {
1525
+ * switch (event.status) {
1526
+ * case "pending":
1527
+ * console.log("Transaction pending:", event.hash);
1528
+ * break;
1529
+ * case "completed":
1530
+ * console.log("Process paused successfully");
1531
+ * break;
1532
+ * case "failed":
1533
+ * console.error("Transaction failed:", event.error);
1534
+ * break;
1535
+ * case "reverted":
1536
+ * console.error("Transaction reverted:", event.reason);
1537
+ * break;
1538
+ * }
1539
+ * }
1540
+ * ```
1541
+ */
1542
+ async *pauseProcessStream(processId) {
1543
+ const txStream = this.processRegistry.setProcessStatus(processId, ProcessStatus.PAUSED);
1544
+ for await (const event of txStream) {
1545
+ if (event.status === TxStatus.Pending) {
1546
+ yield { status: TxStatus.Pending, hash: event.hash };
1547
+ } else if (event.status === TxStatus.Completed) {
1548
+ yield {
1549
+ status: TxStatus.Completed,
1550
+ response: { success: true }
1551
+ };
1552
+ break;
1553
+ } else if (event.status === TxStatus.Failed) {
1554
+ yield { status: TxStatus.Failed, error: event.error };
1555
+ break;
1556
+ } else if (event.status === TxStatus.Reverted) {
1557
+ yield { status: TxStatus.Reverted, reason: event.reason };
1558
+ break;
1559
+ }
1560
+ }
1561
+ }
1562
+ /**
1563
+ * Pauses a voting process by setting its status to PAUSED.
1564
+ * This is a simplified method that waits for transaction completion.
1565
+ *
1566
+ * For real-time transaction status updates, use pauseProcessStream() instead.
1567
+ *
1568
+ * @param processId - The process ID to pause
1569
+ * @returns Promise resolving when the process is paused
1570
+ *
1571
+ * @example
1572
+ * ```typescript
1573
+ * await sdk.pauseProcess("0x1234567890abcdef...");
1574
+ * console.log("Process paused successfully");
1575
+ * ```
1576
+ */
1577
+ async pauseProcess(processId) {
1578
+ for await (const event of this.pauseProcessStream(processId)) {
1579
+ if (event.status === "completed") {
1580
+ return;
1581
+ } else if (event.status === "failed") {
1582
+ throw event.error;
1583
+ } else if (event.status === "reverted") {
1584
+ throw new Error(`Transaction reverted: ${event.reason || "unknown reason"}`);
1585
+ }
1586
+ }
1587
+ throw new Error("Pause process stream ended unexpectedly");
1588
+ }
1589
+ /**
1590
+ * Cancels a voting process by setting its status to CANCELED.
1591
+ * Returns an async generator that yields transaction status events.
1592
+ *
1593
+ * @param processId - The process ID to cancel
1594
+ * @returns AsyncGenerator yielding transaction status events
1595
+ *
1596
+ * @example
1597
+ * ```typescript
1598
+ * const stream = sdk.cancelProcessStream("0x1234567890abcdef...");
1599
+ *
1600
+ * for await (const event of stream) {
1601
+ * switch (event.status) {
1602
+ * case "pending":
1603
+ * console.log("Transaction pending:", event.hash);
1604
+ * break;
1605
+ * case "completed":
1606
+ * console.log("Process canceled successfully");
1607
+ * break;
1608
+ * case "failed":
1609
+ * console.error("Transaction failed:", event.error);
1610
+ * break;
1611
+ * case "reverted":
1612
+ * console.error("Transaction reverted:", event.reason);
1613
+ * break;
1614
+ * }
1615
+ * }
1616
+ * ```
1617
+ */
1618
+ async *cancelProcessStream(processId) {
1619
+ const txStream = this.processRegistry.setProcessStatus(processId, ProcessStatus.CANCELED);
1620
+ for await (const event of txStream) {
1621
+ if (event.status === TxStatus.Pending) {
1622
+ yield { status: TxStatus.Pending, hash: event.hash };
1623
+ } else if (event.status === TxStatus.Completed) {
1624
+ yield {
1625
+ status: TxStatus.Completed,
1626
+ response: { success: true }
1627
+ };
1628
+ break;
1629
+ } else if (event.status === TxStatus.Failed) {
1630
+ yield { status: TxStatus.Failed, error: event.error };
1631
+ break;
1632
+ } else if (event.status === TxStatus.Reverted) {
1633
+ yield { status: TxStatus.Reverted, reason: event.reason };
1634
+ break;
1635
+ }
1636
+ }
1637
+ }
1638
+ /**
1639
+ * Cancels a voting process by setting its status to CANCELED.
1640
+ * This is a simplified method that waits for transaction completion.
1641
+ *
1642
+ * For real-time transaction status updates, use cancelProcessStream() instead.
1643
+ *
1644
+ * @param processId - The process ID to cancel
1645
+ * @returns Promise resolving when the process is canceled
1646
+ *
1647
+ * @example
1648
+ * ```typescript
1649
+ * await sdk.cancelProcess("0x1234567890abcdef...");
1650
+ * console.log("Process canceled successfully");
1651
+ * ```
1652
+ */
1653
+ async cancelProcess(processId) {
1654
+ for await (const event of this.cancelProcessStream(processId)) {
1655
+ if (event.status === "completed") {
1656
+ return;
1657
+ } else if (event.status === "failed") {
1658
+ throw event.error;
1659
+ } else if (event.status === "reverted") {
1660
+ throw new Error(`Transaction reverted: ${event.reason || "unknown reason"}`);
1661
+ }
1662
+ }
1663
+ throw new Error("Cancel process stream ended unexpectedly");
1664
+ }
1665
+ /**
1666
+ * Resumes a voting process by setting its status to READY.
1667
+ * This is typically used to resume a paused process.
1668
+ * Returns an async generator that yields transaction status events.
1669
+ *
1670
+ * @param processId - The process ID to resume
1671
+ * @returns AsyncGenerator yielding transaction status events
1672
+ *
1673
+ * @example
1674
+ * ```typescript
1675
+ * const stream = sdk.resumeProcessStream("0x1234567890abcdef...");
1676
+ *
1677
+ * for await (const event of stream) {
1678
+ * switch (event.status) {
1679
+ * case "pending":
1680
+ * console.log("Transaction pending:", event.hash);
1681
+ * break;
1682
+ * case "completed":
1683
+ * console.log("Process resumed successfully");
1684
+ * break;
1685
+ * case "failed":
1686
+ * console.error("Transaction failed:", event.error);
1687
+ * break;
1688
+ * case "reverted":
1689
+ * console.error("Transaction reverted:", event.reason);
1690
+ * break;
1691
+ * }
1692
+ * }
1693
+ * ```
1694
+ */
1695
+ async *resumeProcessStream(processId) {
1696
+ const txStream = this.processRegistry.setProcessStatus(processId, ProcessStatus.READY);
1697
+ for await (const event of txStream) {
1698
+ if (event.status === TxStatus.Pending) {
1699
+ yield { status: TxStatus.Pending, hash: event.hash };
1700
+ } else if (event.status === TxStatus.Completed) {
1701
+ yield {
1702
+ status: TxStatus.Completed,
1703
+ response: { success: true }
1704
+ };
1705
+ break;
1706
+ } else if (event.status === TxStatus.Failed) {
1707
+ yield { status: TxStatus.Failed, error: event.error };
1708
+ break;
1709
+ } else if (event.status === TxStatus.Reverted) {
1710
+ yield { status: TxStatus.Reverted, reason: event.reason };
1711
+ break;
1712
+ }
1713
+ }
1714
+ }
1715
+ /**
1716
+ * Resumes a voting process by setting its status to READY.
1717
+ * This is typically used to resume a paused process.
1718
+ * This is a simplified method that waits for transaction completion.
1719
+ *
1720
+ * For real-time transaction status updates, use resumeProcessStream() instead.
1721
+ *
1722
+ * @param processId - The process ID to resume
1723
+ * @returns Promise resolving when the process is resumed
1724
+ *
1725
+ * @example
1726
+ * ```typescript
1727
+ * await sdk.resumeProcess("0x1234567890abcdef...");
1728
+ * console.log("Process resumed successfully");
1729
+ * ```
1730
+ */
1731
+ async resumeProcess(processId) {
1732
+ for await (const event of this.resumeProcessStream(processId)) {
1733
+ if (event.status === "completed") {
1734
+ return;
1735
+ } else if (event.status === "failed") {
1736
+ throw event.error;
1737
+ } else if (event.status === "reverted") {
1738
+ throw new Error(`Transaction reverted: ${event.reason || "unknown reason"}`);
1739
+ }
1740
+ }
1741
+ throw new Error("Resume process stream ended unexpectedly");
984
1742
  }
985
1743
  }
986
1744
 
@@ -1051,11 +1809,7 @@ class CircomProof {
1051
1809
  }
1052
1810
  this.zkeyCache.set(zkeyUrl, zkeyBin);
1053
1811
  }
1054
- const { proof, publicSignals } = await groth16.fullProve(
1055
- inputs,
1056
- wasmBin,
1057
- zkeyBin
1058
- );
1812
+ const { proof, publicSignals } = await groth16.fullProve(inputs, wasmBin, zkeyBin);
1059
1813
  return {
1060
1814
  proof,
1061
1815
  publicSignals
@@ -1090,11 +1844,13 @@ var VoteStatus = /* @__PURE__ */ ((VoteStatus2) => {
1090
1844
  })(VoteStatus || {});
1091
1845
 
1092
1846
  class VoteOrchestrationService {
1093
- constructor(apiService, getCrypto, signer, censusProviders = {}) {
1847
+ constructor(apiService, getCrypto, signer, censusProviders = {}, config = {}) {
1094
1848
  this.apiService = apiService;
1095
1849
  this.getCrypto = getCrypto;
1096
1850
  this.signer = signer;
1097
1851
  this.censusProviders = censusProviders;
1852
+ this.verifyCircuitFiles = config.verifyCircuitFiles ?? true;
1853
+ this.verifyProof = config.verifyProof ?? true;
1098
1854
  }
1099
1855
  /**
1100
1856
  * Submit a vote with simplified configuration
@@ -1103,7 +1859,7 @@ class VoteOrchestrationService {
1103
1859
  * - Gets census proof (Merkle or CSP)
1104
1860
  * - Generates cryptographic proofs
1105
1861
  * - Signs and submits the vote
1106
- *
1862
+ *
1107
1863
  * @param config - Simplified vote configuration
1108
1864
  * @returns Promise resolving to vote submission result
1109
1865
  */
@@ -1151,7 +1907,7 @@ class VoteOrchestrationService {
1151
1907
  }
1152
1908
  /**
1153
1909
  * Get the status of a submitted vote
1154
- *
1910
+ *
1155
1911
  * @param processId - The process ID
1156
1912
  * @param voteId - The vote ID
1157
1913
  * @returns Promise resolving to vote status information
@@ -1166,7 +1922,7 @@ class VoteOrchestrationService {
1166
1922
  }
1167
1923
  /**
1168
1924
  * Check if an address has voted in a process
1169
- *
1925
+ *
1170
1926
  * @param processId - The process ID
1171
1927
  * @param address - The voter's address
1172
1928
  * @returns Promise resolving to boolean indicating if the address has voted
@@ -1175,26 +1931,79 @@ class VoteOrchestrationService {
1175
1931
  return this.apiService.sequencer.hasAddressVoted(processId, address);
1176
1932
  }
1177
1933
  /**
1178
- * Wait for a vote to reach a specific status
1179
- *
1934
+ * Watch vote status changes in real-time using an async generator.
1935
+ * Yields each status change as it happens, allowing for reactive UI updates.
1936
+ *
1180
1937
  * @param processId - The process ID
1181
1938
  * @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
1939
+ * @param options - Optional configuration
1940
+ * @returns AsyncGenerator yielding vote status updates
1941
+ *
1942
+ * @example
1943
+ * ```typescript
1944
+ * const vote = await sdk.submitVote({ processId, choices: [1] });
1945
+ *
1946
+ * for await (const statusInfo of sdk.watchVoteStatus(vote.processId, vote.voteId)) {
1947
+ * console.log(`Vote status: ${statusInfo.status}`);
1948
+ *
1949
+ * switch (statusInfo.status) {
1950
+ * case VoteStatus.Pending:
1951
+ * console.log("⏳ Processing...");
1952
+ * break;
1953
+ * case VoteStatus.Verified:
1954
+ * console.log("✓ Verified");
1955
+ * break;
1956
+ * case VoteStatus.Settled:
1957
+ * console.log("✅ Settled");
1958
+ * break;
1959
+ * }
1960
+ * }
1961
+ * ```
1186
1962
  */
1187
- async waitForVoteStatus(processId, voteId, targetStatus = VoteStatus.Settled, timeoutMs = 3e5, pollIntervalMs = 5e3) {
1963
+ async *watchVoteStatus(processId, voteId, options) {
1964
+ const targetStatus = options?.targetStatus ?? VoteStatus.Settled;
1965
+ const timeoutMs = options?.timeoutMs ?? 3e5;
1966
+ const pollIntervalMs = options?.pollIntervalMs ?? 5e3;
1188
1967
  const startTime = Date.now();
1968
+ let previousStatus = null;
1189
1969
  while (Date.now() - startTime < timeoutMs) {
1190
1970
  const statusInfo = await this.getVoteStatus(processId, voteId);
1191
- if (statusInfo.status === targetStatus || statusInfo.status === VoteStatus.Error) {
1192
- return statusInfo;
1971
+ if (statusInfo.status !== previousStatus) {
1972
+ previousStatus = statusInfo.status;
1973
+ yield statusInfo;
1974
+ if (statusInfo.status === targetStatus || statusInfo.status === VoteStatus.Error) {
1975
+ return;
1976
+ }
1193
1977
  }
1194
1978
  await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
1195
1979
  }
1196
1980
  throw new Error(`Vote did not reach status ${targetStatus} within ${timeoutMs}ms`);
1197
1981
  }
1982
+ /**
1983
+ * Wait for a vote to reach a specific status.
1984
+ * This is a simpler alternative to watchVoteStatus() that returns only the final status.
1985
+ *
1986
+ * @param processId - The process ID
1987
+ * @param voteId - The vote ID
1988
+ * @param targetStatus - The target status to wait for (default: "settled")
1989
+ * @param timeoutMs - Maximum time to wait in milliseconds (default: 300000 = 5 minutes)
1990
+ * @param pollIntervalMs - Polling interval in milliseconds (default: 5000 = 5 seconds)
1991
+ * @returns Promise resolving to final vote status
1992
+ */
1993
+ async waitForVoteStatus(processId, voteId, targetStatus = VoteStatus.Settled, timeoutMs = 3e5, pollIntervalMs = 5e3) {
1994
+ let finalStatus = null;
1995
+ for await (const statusInfo of this.watchVoteStatus(processId, voteId, {
1996
+ targetStatus,
1997
+ timeoutMs,
1998
+ pollIntervalMs
1999
+ })) {
2000
+ finalStatus = statusInfo;
2001
+ }
2002
+ if (!finalStatus) {
2003
+ throw new Error(`Vote did not reach status ${targetStatus} within ${timeoutMs}ms`);
2004
+ }
2005
+ return finalStatus;
2006
+ }
1198
2007
  /**
1199
2008
  * Get census proof based on census origin type
1200
2009
  */
@@ -1276,12 +2085,20 @@ class VoteOrchestrationService {
1276
2085
  const circomProof = new CircomProof({
1277
2086
  wasmUrl: info.circuitUrl,
1278
2087
  zkeyUrl: info.provingKeyUrl,
1279
- vkeyUrl: info.verificationKeyUrl
2088
+ vkeyUrl: info.verificationKeyUrl,
2089
+ // Only pass hashes if verifyCircuitFiles is enabled
2090
+ ...this.verifyCircuitFiles && {
2091
+ wasmHash: info.circuitHash,
2092
+ zkeyHash: info.provingKeyHash,
2093
+ vkeyHash: info.verificationKeyHash
2094
+ }
1280
2095
  });
1281
2096
  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");
2097
+ if (this.verifyProof) {
2098
+ const isValid = await circomProof.verify(proof, publicSignals);
2099
+ if (!isValid) {
2100
+ throw new Error("Generated proof is invalid");
2101
+ }
1285
2102
  }
1286
2103
  return { proof, publicSignals };
1287
2104
  }
@@ -1319,10 +2136,7 @@ class VoteOrchestrationService {
1319
2136
  class OrganizationRegistryService extends SmartContractService {
1320
2137
  constructor(contractAddress, runner) {
1321
2138
  super();
1322
- this.contract = OrganizationRegistry__factory.connect(
1323
- contractAddress,
1324
- runner
1325
- );
2139
+ this.contract = OrganizationRegistry__factory.connect(contractAddress, runner);
1326
2140
  }
1327
2141
  // ─── READ OPERATIONS ───────────────────────────────────────────────────────
1328
2142
  async getOrganization(id) {
@@ -1382,31 +2196,36 @@ class OrganizationRegistryService extends SmartContractService {
1382
2196
  }
1383
2197
  // ─── EVENT LISTENERS ───────────────────────────────────────────────────────
1384
2198
  onOrganizationCreated(cb) {
1385
- this.contract.on(
2199
+ this.setupEventListener(
2200
+ this.contract,
1386
2201
  this.contract.filters.OrganizationCreated(),
1387
- this.normalizeListener(cb)
1388
- );
2202
+ cb
2203
+ ).catch((err) => console.error("Error setting up OrganizationCreated listener:", err));
1389
2204
  }
1390
2205
  onOrganizationUpdated(cb) {
1391
- this.contract.on(
2206
+ this.setupEventListener(
2207
+ this.contract,
1392
2208
  this.contract.filters.OrganizationUpdated(),
1393
- this.normalizeListener(cb)
1394
- );
2209
+ cb
2210
+ ).catch((err) => console.error("Error setting up OrganizationUpdated listener:", err));
1395
2211
  }
1396
2212
  onAdministratorAdded(cb) {
1397
- this.contract.on(
2213
+ this.setupEventListener(
2214
+ this.contract,
1398
2215
  this.contract.filters.AdministratorAdded(),
1399
- this.normalizeListener(cb)
1400
- );
2216
+ cb
2217
+ ).catch((err) => console.error("Error setting up AdministratorAdded listener:", err));
1401
2218
  }
1402
2219
  onAdministratorRemoved(cb) {
1403
- this.contract.on(
2220
+ this.setupEventListener(
2221
+ this.contract,
1404
2222
  this.contract.filters.AdministratorRemoved(),
1405
- this.normalizeListener(cb)
1406
- );
2223
+ cb
2224
+ ).catch((err) => console.error("Error setting up AdministratorRemoved listener:", err));
1407
2225
  }
1408
2226
  removeAllListeners() {
1409
2227
  this.contract.removeAllListeners();
2228
+ this.clearPollingIntervals();
1410
2229
  }
1411
2230
  }
1412
2231
 
@@ -1593,25 +2412,23 @@ class DavinciCrypto {
1593
2412
  class DavinciSDK {
1594
2413
  constructor(config) {
1595
2414
  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
- });
2415
+ const hasCustomAddresses = !!config.addresses && Object.keys(config.addresses).length > 0;
1604
2416
  this.config = {
1605
2417
  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
2418
+ sequencerUrl: config.sequencerUrl,
2419
+ censusUrl: config.censusUrl,
2420
+ customAddresses: config.addresses || {},
2421
+ fetchAddressesFromSequencer: !hasCustomAddresses,
2422
+ // Automatic: fetch if no custom addresses
2423
+ verifyCircuitFiles: config.verifyCircuitFiles ?? true,
2424
+ // Default to true for security
2425
+ verifyProof: config.verifyProof ?? true
2426
+ // Default to true for security
1611
2427
  };
1612
2428
  this.apiService = new VocdoniApiService({
1613
2429
  sequencerURL: this.config.sequencerUrl,
1614
- censusURL: this.config.censusUrl
2430
+ censusURL: this.config.censusUrl || ""
2431
+ // Use empty string if not provided
1615
2432
  });
1616
2433
  this.censusProviders = config.censusProviders || {};
1617
2434
  }
@@ -1621,9 +2438,10 @@ class DavinciSDK {
1621
2438
  */
1622
2439
  async init() {
1623
2440
  if (this.initialized) return;
1624
- if (this.config.useSequencerAddresses) {
1625
- await this.updateContractAddressesFromSequencer();
2441
+ if (this.config.fetchAddressesFromSequencer) {
2442
+ await this.fetchContractAddressesFromSequencer();
1626
2443
  }
2444
+ if (!this.config.censusUrl) ;
1627
2445
  this.initialized = true;
1628
2446
  }
1629
2447
  /**
@@ -1633,22 +2451,36 @@ class DavinciSDK {
1633
2451
  return this.apiService;
1634
2452
  }
1635
2453
  /**
1636
- * Get the process registry service for process management
2454
+ * Get the process registry service for process management.
2455
+ * Requires a signer with a provider for blockchain interactions.
2456
+ *
2457
+ * @throws Error if signer does not have a provider
1637
2458
  */
1638
2459
  get processes() {
2460
+ this.ensureProvider();
1639
2461
  if (!this._processRegistry) {
1640
2462
  const processRegistryAddress = this.resolveContractAddress("processRegistry");
1641
- this._processRegistry = new ProcessRegistryService(processRegistryAddress, this.config.signer);
2463
+ this._processRegistry = new ProcessRegistryService(
2464
+ processRegistryAddress,
2465
+ this.config.signer
2466
+ );
1642
2467
  }
1643
2468
  return this._processRegistry;
1644
2469
  }
1645
2470
  /**
1646
- * Get the organization registry service for organization management
2471
+ * Get the organization registry service for organization management.
2472
+ * Requires a signer with a provider for blockchain interactions.
2473
+ *
2474
+ * @throws Error if signer does not have a provider
1647
2475
  */
1648
2476
  get organizations() {
2477
+ this.ensureProvider();
1649
2478
  if (!this._organizationRegistry) {
1650
2479
  const organizationRegistryAddress = this.resolveContractAddress("organizationRegistry");
1651
- this._organizationRegistry = new OrganizationRegistryService(organizationRegistryAddress, this.config.signer);
2480
+ this._organizationRegistry = new OrganizationRegistryService(
2481
+ organizationRegistryAddress,
2482
+ this.config.signer
2483
+ );
1652
2484
  }
1653
2485
  return this._organizationRegistry;
1654
2486
  }
@@ -1667,9 +2499,13 @@ class DavinciSDK {
1667
2499
  return this.davinciCrypto;
1668
2500
  }
1669
2501
  /**
1670
- * Get the process orchestration service for simplified process creation
2502
+ * Get the process orchestration service for simplified process creation.
2503
+ * Requires a signer with a provider for blockchain interactions.
2504
+ *
2505
+ * @throws Error if signer does not have a provider
1671
2506
  */
1672
2507
  get processOrchestrator() {
2508
+ this.ensureProvider();
1673
2509
  if (!this._processOrchestrator) {
1674
2510
  this._processOrchestrator = new ProcessOrchestrationService(
1675
2511
  this.processes,
@@ -1690,7 +2526,11 @@ class DavinciSDK {
1690
2526
  this.apiService,
1691
2527
  () => this.getCrypto(),
1692
2528
  this.config.signer,
1693
- this.censusProviders
2529
+ this.censusProviders,
2530
+ {
2531
+ verifyCircuitFiles: this.config.verifyCircuitFiles,
2532
+ verifyProof: this.config.verifyProof
2533
+ }
1694
2534
  );
1695
2535
  }
1696
2536
  return this._voteOrchestrator;
@@ -1699,21 +2539,24 @@ class DavinciSDK {
1699
2539
  * Gets user-friendly process information from the blockchain.
1700
2540
  * This method fetches raw contract data and transforms it into a user-friendly format
1701
2541
  * that matches the ProcessConfig interface used for creation, plus additional runtime data.
1702
- *
2542
+ *
2543
+ * Requires a signer with a provider for blockchain interactions.
2544
+ *
1703
2545
  * @param processId - The process ID to fetch
1704
2546
  * @returns Promise resolving to user-friendly process information
1705
- *
2547
+ * @throws Error if signer does not have a provider
2548
+ *
1706
2549
  * @example
1707
2550
  * ```typescript
1708
2551
  * const processInfo = await sdk.getProcess("0x1234567890abcdef...");
1709
- *
2552
+ *
1710
2553
  * // Access the same fields as ProcessConfig
1711
2554
  * console.log("Title:", processInfo.title);
1712
2555
  * console.log("Description:", processInfo.description);
1713
2556
  * console.log("Questions:", processInfo.questions);
1714
2557
  * console.log("Census size:", processInfo.census.size);
1715
2558
  * console.log("Ballot config:", processInfo.ballot);
1716
- *
2559
+ *
1717
2560
  * // Plus additional runtime information
1718
2561
  * console.log("Status:", processInfo.status);
1719
2562
  * console.log("Creator:", processInfo.creator);
@@ -1721,7 +2564,7 @@ class DavinciSDK {
1721
2564
  * console.log("End date:", processInfo.endDate);
1722
2565
  * console.log("Duration:", processInfo.duration, "seconds");
1723
2566
  * console.log("Time remaining:", processInfo.timeRemaining, "seconds");
1724
- *
2567
+ *
1725
2568
  * // Access raw contract data if needed
1726
2569
  * console.log("Raw data:", processInfo.raw);
1727
2570
  * ```
@@ -1730,22 +2573,106 @@ class DavinciSDK {
1730
2573
  if (!this.initialized) {
1731
2574
  throw new Error("SDK must be initialized before getting processes. Call sdk.init() first.");
1732
2575
  }
2576
+ this.ensureProvider();
1733
2577
  return this.processOrchestrator.getProcess(processId);
1734
2578
  }
2579
+ /**
2580
+ * Creates a complete voting process and returns an async generator that yields transaction status events.
2581
+ * This method allows you to monitor the transaction progress in real-time, including pending, completed,
2582
+ * failed, and reverted states.
2583
+ *
2584
+ * Requires a signer with a provider for blockchain interactions.
2585
+ *
2586
+ * @param config - Simplified process configuration
2587
+ * @returns AsyncGenerator yielding transaction status events
2588
+ * @throws Error if signer does not have a provider
2589
+ *
2590
+ * @example
2591
+ * ```typescript
2592
+ * const stream = sdk.createProcessStream({
2593
+ * title: "My Election",
2594
+ * description: "A simple election",
2595
+ * census: {
2596
+ * type: CensusOrigin.CensusOriginMerkleTree,
2597
+ * root: "0x1234...",
2598
+ * size: 100,
2599
+ * uri: "ipfs://..."
2600
+ * },
2601
+ * ballot: {
2602
+ * numFields: 2,
2603
+ * maxValue: "3",
2604
+ * minValue: "0",
2605
+ * uniqueValues: false,
2606
+ * costFromWeight: false,
2607
+ * costExponent: 10000,
2608
+ * maxValueSum: "6",
2609
+ * minValueSum: "0"
2610
+ * },
2611
+ * timing: {
2612
+ * startDate: new Date("2024-12-01T10:00:00Z"),
2613
+ * duration: 3600 * 24
2614
+ * },
2615
+ * questions: [
2616
+ * {
2617
+ * title: "What is your favorite color?",
2618
+ * choices: [
2619
+ * { title: "Red", value: 0 },
2620
+ * { title: "Blue", value: 1 }
2621
+ * ]
2622
+ * }
2623
+ * ]
2624
+ * });
2625
+ *
2626
+ * // Monitor transaction progress
2627
+ * for await (const event of stream) {
2628
+ * switch (event.status) {
2629
+ * case TxStatus.Pending:
2630
+ * console.log("Transaction pending:", event.hash);
2631
+ * // Update UI to show pending state
2632
+ * break;
2633
+ * case TxStatus.Completed:
2634
+ * console.log("Process created:", event.response.processId);
2635
+ * console.log("Transaction hash:", event.response.transactionHash);
2636
+ * // Update UI to show success
2637
+ * break;
2638
+ * case TxStatus.Failed:
2639
+ * console.error("Transaction failed:", event.error);
2640
+ * // Update UI to show error
2641
+ * break;
2642
+ * case TxStatus.Reverted:
2643
+ * console.error("Transaction reverted:", event.reason);
2644
+ * // Update UI to show revert reason
2645
+ * break;
2646
+ * }
2647
+ * }
2648
+ * ```
2649
+ */
2650
+ createProcessStream(config) {
2651
+ if (!this.initialized) {
2652
+ throw new Error("SDK must be initialized before creating processes. Call sdk.init() first.");
2653
+ }
2654
+ this.ensureProvider();
2655
+ return this.processOrchestrator.createProcessStream(config);
2656
+ }
1735
2657
  /**
1736
2658
  * Creates a complete voting process with minimal configuration.
1737
2659
  * This is the ultra-easy method for end users that handles all the complex orchestration internally.
1738
- *
2660
+ *
2661
+ * For real-time transaction status updates, use createProcessStream() instead.
2662
+ *
2663
+ * Requires a signer with a provider for blockchain interactions.
2664
+ *
1739
2665
  * The method automatically:
1740
2666
  * - Gets encryption keys and initial state root from the sequencer
1741
2667
  * - Handles process creation signatures
1742
2668
  * - Coordinates between sequencer API and on-chain contract calls
1743
2669
  * - Creates and pushes metadata
1744
2670
  * - Submits the on-chain transaction
1745
- *
2671
+ *
1746
2672
  * @param config - Simplified process configuration
1747
2673
  * @returns Promise resolving to the process creation result
1748
- *
2674
+ * @throws Error if signer does not have a provider
2675
+ *
1749
2676
  * @example
1750
2677
  * ```typescript
1751
2678
  * // Option 1: Using duration (traditional approach)
@@ -1782,7 +2709,7 @@ class DavinciSDK {
1782
2709
  * }
1783
2710
  * ]
1784
2711
  * });
1785
- *
2712
+ *
1786
2713
  * // Option 2: Using start and end dates
1787
2714
  * const result2 = await sdk.createProcess({
1788
2715
  * title: "Weekend Vote",
@@ -1797,21 +2724,26 @@ class DavinciSDK {
1797
2724
  if (!this.initialized) {
1798
2725
  throw new Error("SDK must be initialized before creating processes. Call sdk.init() first.");
1799
2726
  }
2727
+ this.ensureProvider();
1800
2728
  return this.processOrchestrator.createProcess(config);
1801
2729
  }
1802
2730
  /**
1803
2731
  * Submit a vote with simplified configuration.
1804
2732
  * This is the ultra-easy method for end users that handles all the complex voting workflow internally.
1805
- *
2733
+ *
2734
+ * Does NOT require a provider - can be used with a bare Wallet for signing only.
2735
+ * IMPORTANT: Requires censusUrl to be configured in the SDK for fetching census proofs (unless using custom census providers).
2736
+ *
1806
2737
  * The method automatically:
1807
2738
  * - Fetches process information and validates voting is allowed
1808
2739
  * - Gets census proof (Merkle tree based)
1809
2740
  * - Generates cryptographic proofs and encrypts the vote
1810
2741
  * - Signs and submits the vote to the sequencer
1811
- *
2742
+ *
1812
2743
  * @param config - Simplified vote configuration
1813
2744
  * @returns Promise resolving to vote submission result
1814
- *
2745
+ * @throws Error if censusUrl is not configured (unless using custom census providers)
2746
+ *
1815
2747
  * @example
1816
2748
  * ```typescript
1817
2749
  * // Submit a vote with voter's private key
@@ -1820,14 +2752,14 @@ class DavinciSDK {
1820
2752
  * choices: [1, 0], // Vote for option 1 in question 1, option 0 in question 2
1821
2753
  * voterKey: "0x1234567890abcdef..." // Voter's private key
1822
2754
  * });
1823
- *
2755
+ *
1824
2756
  * console.log("Vote ID:", voteResult.voteId);
1825
2757
  * console.log("Status:", voteResult.status);
1826
- *
2758
+ *
1827
2759
  * // Submit a vote with a Wallet instance
1828
2760
  * import { Wallet } from "ethers";
1829
2761
  * const voterWallet = new Wallet("0x...");
1830
- *
2762
+ *
1831
2763
  * const voteResult2 = await sdk.submitVote({
1832
2764
  * processId: "0x1234567890abcdef...",
1833
2765
  * choices: [2], // Single question vote
@@ -1839,15 +2771,22 @@ class DavinciSDK {
1839
2771
  if (!this.initialized) {
1840
2772
  throw new Error("SDK must be initialized before submitting votes. Call sdk.init() first.");
1841
2773
  }
2774
+ if (!this.config.censusUrl && !this.censusProviders.merkle && !this.censusProviders.csp) {
2775
+ throw new Error(
2776
+ "Census URL is required for voting. Provide censusUrl in the SDK constructor config, or use custom census providers."
2777
+ );
2778
+ }
1842
2779
  return this.voteOrchestrator.submitVote(config);
1843
2780
  }
1844
2781
  /**
1845
2782
  * Get the status of a submitted vote.
1846
- *
2783
+ *
2784
+ * Does NOT require a provider - uses API calls only.
2785
+ *
1847
2786
  * @param processId - The process ID
1848
2787
  * @param voteId - The vote ID returned from submitVote()
1849
2788
  * @returns Promise resolving to vote status information
1850
- *
2789
+ *
1851
2790
  * @example
1852
2791
  * ```typescript
1853
2792
  * const statusInfo = await sdk.getVoteStatus(processId, voteId);
@@ -1863,11 +2802,13 @@ class DavinciSDK {
1863
2802
  }
1864
2803
  /**
1865
2804
  * Check if an address has voted in a process.
1866
- *
2805
+ *
2806
+ * Does NOT require a provider - uses API calls only.
2807
+ *
1867
2808
  * @param processId - The process ID
1868
2809
  * @param address - The voter's address
1869
2810
  * @returns Promise resolving to boolean indicating if the address has voted
1870
- *
2811
+ *
1871
2812
  * @example
1872
2813
  * ```typescript
1873
2814
  * const hasVoted = await sdk.hasAddressVoted(processId, "0x1234567890abcdef...");
@@ -1878,108 +2819,415 @@ class DavinciSDK {
1878
2819
  */
1879
2820
  async hasAddressVoted(processId, address) {
1880
2821
  if (!this.initialized) {
1881
- throw new Error("SDK must be initialized before checking vote status. Call sdk.init() first.");
2822
+ throw new Error(
2823
+ "SDK must be initialized before checking vote status. Call sdk.init() first."
2824
+ );
1882
2825
  }
1883
2826
  return this.voteOrchestrator.hasAddressVoted(processId, address);
1884
2827
  }
2828
+ /**
2829
+ * Watch vote status changes in real-time using an async generator.
2830
+ * This method yields each status change as it happens, perfect for showing
2831
+ * progress indicators in UI applications.
2832
+ *
2833
+ * Does NOT require a provider - uses API calls only.
2834
+ *
2835
+ * @param processId - The process ID
2836
+ * @param voteId - The vote ID
2837
+ * @param options - Optional configuration
2838
+ * @returns AsyncGenerator yielding vote status updates
2839
+ *
2840
+ * @example
2841
+ * ```typescript
2842
+ * // Submit vote
2843
+ * const voteResult = await sdk.submitVote({
2844
+ * processId: "0x1234567890abcdef...",
2845
+ * choices: [1]
2846
+ * });
2847
+ *
2848
+ * // Watch status changes in real-time
2849
+ * for await (const statusInfo of sdk.watchVoteStatus(voteResult.processId, voteResult.voteId)) {
2850
+ * console.log(`Vote status: ${statusInfo.status}`);
2851
+ *
2852
+ * switch (statusInfo.status) {
2853
+ * case VoteStatus.Pending:
2854
+ * console.log("⏳ Processing...");
2855
+ * break;
2856
+ * case VoteStatus.Verified:
2857
+ * console.log("✓ Vote verified");
2858
+ * break;
2859
+ * case VoteStatus.Aggregated:
2860
+ * console.log("📊 Vote aggregated");
2861
+ * break;
2862
+ * case VoteStatus.Settled:
2863
+ * console.log("✅ Vote settled");
2864
+ * break;
2865
+ * }
2866
+ * }
2867
+ * ```
2868
+ */
2869
+ watchVoteStatus(processId, voteId, options) {
2870
+ if (!this.initialized) {
2871
+ throw new Error(
2872
+ "SDK must be initialized before watching vote status. Call sdk.init() first."
2873
+ );
2874
+ }
2875
+ return this.voteOrchestrator.watchVoteStatus(processId, voteId, options);
2876
+ }
1885
2877
  /**
1886
2878
  * Wait for a vote to reach a specific status.
1887
- * Useful for waiting for vote confirmation and processing.
1888
- *
2879
+ * This is a simpler alternative to watchVoteStatus() that returns only the final status.
2880
+ * Useful for waiting for vote confirmation and processing without needing to handle each intermediate status.
2881
+ *
2882
+ * Does NOT require a provider - uses API calls only.
2883
+ *
1889
2884
  * @param processId - The process ID
1890
2885
  * @param voteId - The vote ID
1891
2886
  * @param targetStatus - The target status to wait for (default: "settled")
1892
2887
  * @param timeoutMs - Maximum time to wait in milliseconds (default: 300000 = 5 minutes)
1893
2888
  * @param pollIntervalMs - Polling interval in milliseconds (default: 5000 = 5 seconds)
1894
2889
  * @returns Promise resolving to final vote status
1895
- *
2890
+ *
1896
2891
  * @example
1897
2892
  * ```typescript
1898
2893
  * // Submit vote and wait for it to be settled
1899
2894
  * const voteResult = await sdk.submitVote({
1900
2895
  * processId: "0x1234567890abcdef...",
1901
- * choices: [1],
1902
- * voterKey: "0x..."
2896
+ * choices: [1]
1903
2897
  * });
1904
- *
2898
+ *
1905
2899
  * // Wait for the vote to be fully processed
1906
2900
  * const finalStatus = await sdk.waitForVoteStatus(
1907
2901
  * voteResult.processId,
1908
2902
  * voteResult.voteId,
1909
- * "settled", // Wait until vote is settled
2903
+ * VoteStatus.Settled, // Wait until vote is settled
1910
2904
  * 300000, // 5 minute timeout
1911
2905
  * 5000 // Check every 5 seconds
1912
2906
  * );
1913
- *
2907
+ *
1914
2908
  * console.log("Vote final status:", finalStatus.status);
1915
2909
  * ```
1916
2910
  */
1917
2911
  async waitForVoteStatus(processId, voteId, targetStatus = VoteStatus.Settled, timeoutMs = 3e5, pollIntervalMs = 5e3) {
1918
2912
  if (!this.initialized) {
1919
- throw new Error("SDK must be initialized before waiting for vote status. Call sdk.init() first.");
2913
+ throw new Error(
2914
+ "SDK must be initialized before waiting for vote status. Call sdk.init() first."
2915
+ );
1920
2916
  }
1921
- return this.voteOrchestrator.waitForVoteStatus(processId, voteId, targetStatus, timeoutMs, pollIntervalMs);
2917
+ return this.voteOrchestrator.waitForVoteStatus(
2918
+ processId,
2919
+ voteId,
2920
+ targetStatus,
2921
+ timeoutMs,
2922
+ pollIntervalMs
2923
+ );
1922
2924
  }
1923
2925
  /**
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
2926
+ * Ends a voting process by setting its status to ENDED and returns an async generator
2927
+ * that yields transaction status events. This method allows you to monitor the
2928
+ * transaction progress in real-time.
2929
+ *
2930
+ * Requires a signer with a provider for blockchain interactions.
2931
+ *
2932
+ * @param processId - The process ID to end
2933
+ * @returns AsyncGenerator yielding transaction status events
2934
+ * @throws Error if signer does not have a provider
2935
+ *
2936
+ * @example
2937
+ * ```typescript
2938
+ * const stream = sdk.endProcessStream("0x1234567890abcdef...");
2939
+ *
2940
+ * for await (const event of stream) {
2941
+ * switch (event.status) {
2942
+ * case TxStatus.Pending:
2943
+ * console.log("Transaction pending:", event.hash);
2944
+ * break;
2945
+ * case TxStatus.Completed:
2946
+ * console.log("Process ended successfully");
2947
+ * break;
2948
+ * case TxStatus.Failed:
2949
+ * console.error("Transaction failed:", event.error);
2950
+ * break;
2951
+ * case TxStatus.Reverted:
2952
+ * console.error("Transaction reverted:", event.reason);
2953
+ * break;
2954
+ * }
2955
+ * }
2956
+ * ```
1928
2957
  */
1929
- resolveContractAddress(contractName) {
1930
- if (this.config.useSequencerAddresses) {
1931
- return this.getDefaultContractAddress(contractName);
2958
+ endProcessStream(processId) {
2959
+ if (!this.initialized) {
2960
+ throw new Error("SDK must be initialized before ending processes. Call sdk.init() first.");
1932
2961
  }
1933
- const customAddress = this.config.contractAddresses[contractName];
1934
- if (customAddress) {
1935
- return customAddress;
2962
+ this.ensureProvider();
2963
+ return this.processOrchestrator.endProcessStream(processId);
2964
+ }
2965
+ /**
2966
+ * Ends a voting process by setting its status to ENDED.
2967
+ * This is the simplified method that waits for transaction completion.
2968
+ *
2969
+ * For real-time transaction status updates, use endProcessStream() instead.
2970
+ *
2971
+ * Requires a signer with a provider for blockchain interactions.
2972
+ *
2973
+ * @param processId - The process ID to end
2974
+ * @returns Promise resolving when the process is ended
2975
+ * @throws Error if signer does not have a provider
2976
+ *
2977
+ * @example
2978
+ * ```typescript
2979
+ * await sdk.endProcess("0x1234567890abcdef...");
2980
+ * console.log("Process ended successfully");
2981
+ * ```
2982
+ */
2983
+ async endProcess(processId) {
2984
+ if (!this.initialized) {
2985
+ throw new Error("SDK must be initialized before ending processes. Call sdk.init() first.");
1936
2986
  }
1937
- return this.getDefaultContractAddress(contractName);
2987
+ this.ensureProvider();
2988
+ return this.processOrchestrator.endProcess(processId);
1938
2989
  }
1939
2990
  /**
1940
- * Get default contract address from deployed addresses
2991
+ * Pauses a voting process by setting its status to PAUSED and returns an async generator
2992
+ * that yields transaction status events. This method allows you to monitor the
2993
+ * transaction progress in real-time.
2994
+ *
2995
+ * Requires a signer with a provider for blockchain interactions.
2996
+ *
2997
+ * @param processId - The process ID to pause
2998
+ * @returns AsyncGenerator yielding transaction status events
2999
+ * @throws Error if signer does not have a provider
3000
+ *
3001
+ * @example
3002
+ * ```typescript
3003
+ * const stream = sdk.pauseProcessStream("0x1234567890abcdef...");
3004
+ *
3005
+ * for await (const event of stream) {
3006
+ * switch (event.status) {
3007
+ * case TxStatus.Pending:
3008
+ * console.log("Transaction pending:", event.hash);
3009
+ * break;
3010
+ * case TxStatus.Completed:
3011
+ * console.log("Process paused successfully");
3012
+ * break;
3013
+ * case TxStatus.Failed:
3014
+ * console.error("Transaction failed:", event.error);
3015
+ * break;
3016
+ * case TxStatus.Reverted:
3017
+ * console.error("Transaction reverted:", event.reason);
3018
+ * break;
3019
+ * }
3020
+ * }
3021
+ * ```
1941
3022
  */
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}`);
3023
+ pauseProcessStream(processId) {
3024
+ if (!this.initialized) {
3025
+ throw new Error("SDK must be initialized before pausing processes. Call sdk.init() first.");
3026
+ }
3027
+ this.ensureProvider();
3028
+ return this.processOrchestrator.pauseProcessStream(processId);
3029
+ }
3030
+ /**
3031
+ * Pauses a voting process by setting its status to PAUSED.
3032
+ * This is the simplified method that waits for transaction completion.
3033
+ *
3034
+ * For real-time transaction status updates, use pauseProcessStream() instead.
3035
+ *
3036
+ * Requires a signer with a provider for blockchain interactions.
3037
+ *
3038
+ * @param processId - The process ID to pause
3039
+ * @returns Promise resolving when the process is paused
3040
+ * @throws Error if signer does not have a provider
3041
+ *
3042
+ * @example
3043
+ * ```typescript
3044
+ * await sdk.pauseProcess("0x1234567890abcdef...");
3045
+ * console.log("Process paused successfully");
3046
+ * ```
3047
+ */
3048
+ async pauseProcess(processId) {
3049
+ if (!this.initialized) {
3050
+ throw new Error("SDK must be initialized before pausing processes. Call sdk.init() first.");
3051
+ }
3052
+ this.ensureProvider();
3053
+ return this.processOrchestrator.pauseProcess(processId);
3054
+ }
3055
+ /**
3056
+ * Cancels a voting process by setting its status to CANCELED and returns an async generator
3057
+ * that yields transaction status events. This method allows you to monitor the
3058
+ * transaction progress in real-time.
3059
+ *
3060
+ * Requires a signer with a provider for blockchain interactions.
3061
+ *
3062
+ * @param processId - The process ID to cancel
3063
+ * @returns AsyncGenerator yielding transaction status events
3064
+ * @throws Error if signer does not have a provider
3065
+ *
3066
+ * @example
3067
+ * ```typescript
3068
+ * const stream = sdk.cancelProcessStream("0x1234567890abcdef...");
3069
+ *
3070
+ * for await (const event of stream) {
3071
+ * switch (event.status) {
3072
+ * case TxStatus.Pending:
3073
+ * console.log("Transaction pending:", event.hash);
3074
+ * break;
3075
+ * case TxStatus.Completed:
3076
+ * console.log("Process canceled successfully");
3077
+ * break;
3078
+ * case TxStatus.Failed:
3079
+ * console.error("Transaction failed:", event.error);
3080
+ * break;
3081
+ * case TxStatus.Reverted:
3082
+ * console.error("Transaction reverted:", event.reason);
3083
+ * break;
3084
+ * }
3085
+ * }
3086
+ * ```
3087
+ */
3088
+ cancelProcessStream(processId) {
3089
+ if (!this.initialized) {
3090
+ throw new Error("SDK must be initialized before canceling processes. Call sdk.init() first.");
3091
+ }
3092
+ this.ensureProvider();
3093
+ return this.processOrchestrator.cancelProcessStream(processId);
3094
+ }
3095
+ /**
3096
+ * Cancels a voting process by setting its status to CANCELED.
3097
+ * This is the simplified method that waits for transaction completion.
3098
+ *
3099
+ * For real-time transaction status updates, use cancelProcessStream() instead.
3100
+ *
3101
+ * Requires a signer with a provider for blockchain interactions.
3102
+ *
3103
+ * @param processId - The process ID to cancel
3104
+ * @returns Promise resolving when the process is canceled
3105
+ * @throws Error if signer does not have a provider
3106
+ *
3107
+ * @example
3108
+ * ```typescript
3109
+ * await sdk.cancelProcess("0x1234567890abcdef...");
3110
+ * console.log("Process canceled successfully");
3111
+ * ```
3112
+ */
3113
+ async cancelProcess(processId) {
3114
+ if (!this.initialized) {
3115
+ throw new Error("SDK must be initialized before canceling processes. Call sdk.init() first.");
3116
+ }
3117
+ this.ensureProvider();
3118
+ return this.processOrchestrator.cancelProcess(processId);
3119
+ }
3120
+ /**
3121
+ * Resumes a voting process by setting its status to READY and returns an async generator
3122
+ * that yields transaction status events. This is typically used to resume a paused process.
3123
+ *
3124
+ * Requires a signer with a provider for blockchain interactions.
3125
+ *
3126
+ * @param processId - The process ID to resume
3127
+ * @returns AsyncGenerator yielding transaction status events
3128
+ * @throws Error if signer does not have a provider
3129
+ *
3130
+ * @example
3131
+ * ```typescript
3132
+ * const stream = sdk.resumeProcessStream("0x1234567890abcdef...");
3133
+ *
3134
+ * for await (const event of stream) {
3135
+ * switch (event.status) {
3136
+ * case TxStatus.Pending:
3137
+ * console.log("Transaction pending:", event.hash);
3138
+ * break;
3139
+ * case TxStatus.Completed:
3140
+ * console.log("Process resumed successfully");
3141
+ * break;
3142
+ * case TxStatus.Failed:
3143
+ * console.error("Transaction failed:", event.error);
3144
+ * break;
3145
+ * case TxStatus.Reverted:
3146
+ * console.error("Transaction reverted:", event.reason);
3147
+ * break;
3148
+ * }
3149
+ * }
3150
+ * ```
3151
+ */
3152
+ resumeProcessStream(processId) {
3153
+ if (!this.initialized) {
3154
+ throw new Error("SDK must be initialized before resuming processes. Call sdk.init() first.");
3155
+ }
3156
+ this.ensureProvider();
3157
+ return this.processOrchestrator.resumeProcessStream(processId);
3158
+ }
3159
+ /**
3160
+ * Resumes a voting process by setting its status to READY.
3161
+ * This is typically used to resume a paused process.
3162
+ * This is the simplified method that waits for transaction completion.
3163
+ *
3164
+ * For real-time transaction status updates, use resumeProcessStream() instead.
3165
+ *
3166
+ * Requires a signer with a provider for blockchain interactions.
3167
+ *
3168
+ * @param processId - The process ID to resume
3169
+ * @returns Promise resolving when the process is resumed
3170
+ * @throws Error if signer does not have a provider
3171
+ *
3172
+ * @example
3173
+ * ```typescript
3174
+ * await sdk.resumeProcess("0x1234567890abcdef...");
3175
+ * console.log("Process resumed successfully");
3176
+ * ```
3177
+ */
3178
+ async resumeProcess(processId) {
3179
+ if (!this.initialized) {
3180
+ throw new Error("SDK must be initialized before resuming processes. Call sdk.init() first.");
3181
+ }
3182
+ this.ensureProvider();
3183
+ return this.processOrchestrator.resumeProcess(processId);
3184
+ }
3185
+ /**
3186
+ * Resolve contract address based on configuration priority:
3187
+ * 1. Custom addresses from user config (if provided)
3188
+ * 2. Addresses from sequencer (fetched during init if no custom addresses provided)
3189
+ */
3190
+ resolveContractAddress(contractName) {
3191
+ const customAddress = this.config.customAddresses[contractName];
3192
+ if (customAddress) {
3193
+ return customAddress;
1957
3194
  }
3195
+ if (!this.config.customAddresses[contractName]) {
3196
+ throw new Error(
3197
+ `Contract address for '${contractName}' not found. Make sure SDK is initialized with sdk.init() or provide custom addresses in config.`
3198
+ );
3199
+ }
3200
+ return this.config.customAddresses[contractName];
1958
3201
  }
1959
3202
  /**
1960
- * Update contract addresses from sequencer info if useSequencerAddresses is enabled
1961
- * Sequencer addresses have priority over user-provided addresses
3203
+ * Fetch contract addresses from sequencer info
3204
+ * This is called during init() if custom addresses are not provided
1962
3205
  */
1963
- async updateContractAddressesFromSequencer() {
3206
+ async fetchContractAddressesFromSequencer() {
1964
3207
  try {
1965
3208
  const info = await this.apiService.sequencer.getInfo();
1966
3209
  const contracts = info.contracts;
1967
3210
  if (contracts.process) {
3211
+ this.config.customAddresses.processRegistry = contracts.process;
1968
3212
  this._processRegistry = new ProcessRegistryService(contracts.process, this.config.signer);
1969
- this.config.contractAddresses.processRegistry = contracts.process;
1970
3213
  }
1971
3214
  if (contracts.organization) {
1972
- this._organizationRegistry = new OrganizationRegistryService(contracts.organization, this.config.signer);
1973
- this.config.contractAddresses.organizationRegistry = contracts.organization;
3215
+ this.config.customAddresses.organizationRegistry = contracts.organization;
3216
+ this._organizationRegistry = new OrganizationRegistryService(
3217
+ contracts.organization,
3218
+ this.config.signer
3219
+ );
1974
3220
  }
1975
3221
  if (contracts.stateTransitionVerifier) {
1976
- this.config.contractAddresses.stateTransitionVerifier = contracts.stateTransitionVerifier;
3222
+ this.config.customAddresses.stateTransitionVerifier = contracts.stateTransitionVerifier;
1977
3223
  }
1978
3224
  if (contracts.resultsVerifier) {
1979
- this.config.contractAddresses.resultsVerifier = contracts.resultsVerifier;
3225
+ this.config.customAddresses.resultsVerifier = contracts.resultsVerifier;
1980
3226
  }
1981
3227
  } catch (error) {
1982
- console.warn("Failed to fetch contract addresses from sequencer, using defaults:", error);
3228
+ throw new Error(
3229
+ `Failed to fetch contract addresses from sequencer: ${error instanceof Error ? error.message : String(error)}. You can provide custom addresses in the SDK config to avoid this error.`
3230
+ );
1983
3231
  }
1984
3232
  }
1985
3233
  /**
@@ -1994,7 +3242,19 @@ class DavinciSDK {
1994
3242
  isInitialized() {
1995
3243
  return this.initialized;
1996
3244
  }
3245
+ /**
3246
+ * Ensures that the signer has a provider for blockchain operations.
3247
+ * @throws Error if the signer does not have a provider
3248
+ * @private
3249
+ */
3250
+ ensureProvider() {
3251
+ if (!this.config.signer.provider) {
3252
+ throw new Error(
3253
+ "Provider required for blockchain operations (process/organization management). The signer must be connected to a provider. Use wallet.connect(provider) or a browser signer like MetaMask. Note: Voting operations do not require a provider."
3254
+ );
3255
+ }
3256
+ }
1997
3257
  }
1998
3258
 
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 };
3259
+ export { BaseService, Census, CensusOrchestrator, CensusOrigin, CensusType, CircomProof, ContractServiceError, CspCensus, DavinciCrypto, DavinciSDK, ElectionMetadataTemplate, ElectionResultsTypeNames, OrganizationAdministratorError, OrganizationCreateError, OrganizationDeleteError, OrganizationRegistryService, OrganizationUpdateError, PlainCensus, ProcessCensusError, ProcessCreateError, ProcessDurationError, ProcessOrchestrationService, ProcessRegistryService, ProcessResultError, ProcessStateTransitionError, ProcessStatus, ProcessStatusError, PublishedCensus, SmartContractService, TxStatus, VocdoniApiService, VocdoniCensusService, VocdoniSequencerService, VoteOrchestrationService, VoteStatus, WeightedCensus, assertCSPCensusProof, assertMerkleCensusProof, createProcessSignatureMessage, getElectionMetadataTemplate, isCSPCensusProof, isMerkleCensusProof, signProcessCreation, validateProcessId };
2000
3260
  //# sourceMappingURL=index.mjs.map