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