@vocdoni/davinci-sdk 0.0.6 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -192,18 +192,20 @@ class VocdoniCensusService extends BaseService {
192
192
  }
193
193
 
194
194
  var CensusOrigin = /* @__PURE__ */ ((CensusOrigin2) => {
195
- CensusOrigin2[CensusOrigin2["CensusOriginMerkleTree"] = 1] = "CensusOriginMerkleTree";
196
- CensusOrigin2[CensusOrigin2["CensusOriginCSP"] = 2] = "CensusOriginCSP";
195
+ CensusOrigin2[CensusOrigin2["OffchainStatic"] = 1] = "OffchainStatic";
196
+ CensusOrigin2[CensusOrigin2["OffchainDynamic"] = 2] = "OffchainDynamic";
197
+ CensusOrigin2[CensusOrigin2["Onchain"] = 3] = "Onchain";
198
+ CensusOrigin2[CensusOrigin2["CSP"] = 4] = "CSP";
197
199
  return CensusOrigin2;
198
200
  })(CensusOrigin || {});
199
201
  function isBaseCensusProof(proof) {
200
202
  return !!proof && typeof proof.root === "string" && typeof proof.address === "string" && typeof proof.censusOrigin === "number" && Object.values(CensusOrigin).includes(proof.censusOrigin);
201
203
  }
202
204
  function isMerkleCensusProof(proof) {
203
- return isBaseCensusProof(proof) && proof.censusOrigin === 1 /* CensusOriginMerkleTree */ && typeof proof.weight === "string" && typeof proof.value === "string" && typeof proof.siblings === "string";
205
+ return isBaseCensusProof(proof) && (proof.censusOrigin === 1 /* OffchainStatic */ || proof.censusOrigin === 2 /* OffchainDynamic */ || proof.censusOrigin === 3 /* Onchain */) && typeof proof.weight === "string" && typeof proof.value === "string" && typeof proof.siblings === "string";
204
206
  }
205
207
  function isCSPCensusProof(proof) {
206
- return isBaseCensusProof(proof) && proof.censusOrigin === 2 /* CensusOriginCSP */ && typeof proof.weight === "string" && typeof proof.processId === "string" && typeof proof.publicKey === "string" && typeof proof.signature === "string";
208
+ return isBaseCensusProof(proof) && proof.censusOrigin === 4 /* CSP */ && typeof proof.weight === "string" && typeof proof.processId === "string" && typeof proof.publicKey === "string" && typeof proof.signature === "string";
207
209
  }
208
210
  function assertMerkleCensusProof(proof) {
209
211
  if (!isMerkleCensusProof(proof)) {
@@ -216,19 +218,12 @@ function assertCSPCensusProof(proof) {
216
218
  }
217
219
  }
218
220
 
219
- var CensusType = /* @__PURE__ */ ((CensusType2) => {
220
- CensusType2["PLAIN"] = "plain";
221
- CensusType2["WEIGHTED"] = "weighted";
222
- CensusType2["CSP"] = "csp";
223
- return CensusType2;
224
- })(CensusType || {});
225
221
  class Census {
226
- constructor(type) {
222
+ constructor(censusOrigin) {
227
223
  this._censusId = null;
228
224
  this._censusRoot = null;
229
225
  this._censusURI = null;
230
- this._size = null;
231
- this._type = type;
226
+ this._censusOrigin = censusOrigin;
232
227
  }
233
228
  get censusId() {
234
229
  return this._censusId;
@@ -239,106 +234,50 @@ class Census {
239
234
  get censusURI() {
240
235
  return this._censusURI;
241
236
  }
242
- get type() {
243
- return this._type;
244
- }
245
- get size() {
246
- return this._size;
247
- }
248
237
  get isPublished() {
249
238
  return this._censusRoot !== null && this._censusURI !== null;
250
239
  }
251
240
  /**
252
- * Convert CensusType to CensusOrigin enum for API compatibility
241
+ * Get the census origin (OffchainStatic, OffchainDynamic, Onchain, or CSP)
253
242
  */
254
243
  get censusOrigin() {
255
- switch (this._type) {
256
- case "plain" /* PLAIN */:
257
- case "weighted" /* WEIGHTED */:
258
- return CensusOrigin.CensusOriginMerkleTree;
259
- case "csp" /* CSP */:
260
- return CensusOrigin.CensusOriginCSP;
261
- default:
262
- throw new Error(`Unknown census type: ${this._type}`);
263
- }
264
- }
265
- }
266
-
267
- class PlainCensus extends Census {
268
- constructor() {
269
- super(CensusType.PLAIN);
270
- this._participants = /* @__PURE__ */ new Set();
271
- }
272
- /**
273
- * Add participant(s) with automatic weight=1
274
- * @param addresses - Single address or array of addresses
275
- */
276
- add(addresses) {
277
- const toAdd = Array.isArray(addresses) ? addresses : [addresses];
278
- for (const address of toAdd) {
279
- this.validateAddress(address);
280
- this._participants.add(address.toLowerCase());
281
- }
282
- }
283
- /**
284
- * Remove participant by address
285
- */
286
- remove(address) {
287
- this._participants.delete(address.toLowerCase());
288
- }
289
- /**
290
- * Get all participants as CensusParticipant array (for API)
291
- * All participants have weight="1"
292
- */
293
- get participants() {
294
- return Array.from(this._participants).map((key) => ({
295
- key,
296
- weight: "1"
297
- // Everyone has weight=1 in plain census
298
- }));
244
+ return this._censusOrigin;
299
245
  }
300
246
  /**
301
- * Get addresses only
247
+ * Check if this census requires publishing via the Census API
248
+ * Merkle censuses (OffchainStatic, OffchainDynamic) need to be published
249
+ * Onchain and CSP censuses are ready immediately upon construction
302
250
  */
303
- get addresses() {
304
- return Array.from(this._participants);
305
- }
306
- validateAddress(address) {
307
- if (!address || typeof address !== "string") {
308
- throw new Error("Address is required and must be a string");
309
- }
310
- if (!/^(0x)?[0-9a-fA-F]{40}$/i.test(address)) {
311
- throw new Error(`Invalid Ethereum address format: ${address}`);
312
- }
313
- }
314
- /**
315
- * Internal method called after publishing
316
- * @internal
317
- */
318
- _setPublishedData(root, uri, size, censusId) {
319
- this._censusRoot = root;
320
- this._censusURI = uri;
321
- this._size = size;
322
- if (censusId) this._censusId = censusId;
251
+ get requiresPublishing() {
252
+ return this._censusOrigin === CensusOrigin.OffchainStatic || this._censusOrigin === CensusOrigin.OffchainDynamic;
323
253
  }
324
254
  }
325
255
 
326
- class WeightedCensus extends Census {
327
- constructor() {
328
- super(CensusType.WEIGHTED);
256
+ class MerkleCensus extends Census {
257
+ constructor(censusOrigin) {
258
+ super(censusOrigin);
329
259
  this._participants = /* @__PURE__ */ new Map();
330
260
  }
331
261
  /**
332
- * Add participant(s) with custom weights
333
- * Weight can be provided as string, number, or bigint - will be converted to string internally
334
- * @param participant - Single participant or array of participants with custom weights
262
+ * Add participant(s) - supports both plain addresses and weighted participants
263
+ * @param data - Can be:
264
+ * - string: single address (weight=1)
265
+ * - string[]: array of addresses (weight=1 for all)
266
+ * - {key: string, weight: string|number|bigint}: single weighted participant
267
+ * - Array of weighted participants
335
268
  */
336
- add(participant) {
337
- const toAdd = Array.isArray(participant) ? participant : [participant];
338
- for (const p of toAdd) {
339
- this.validateParticipant(p);
340
- const weightString = this.normalizeWeight(p.weight);
341
- this._participants.set(p.key.toLowerCase(), weightString);
269
+ add(data) {
270
+ if (typeof data === "string") {
271
+ this.addAddress(data, "1");
272
+ } else if (Array.isArray(data)) {
273
+ if (data.length === 0) return;
274
+ if (typeof data[0] === "string") {
275
+ data.forEach((addr) => this.addAddress(addr, "1"));
276
+ } else {
277
+ data.forEach((p) => this.addParticipant(p));
278
+ }
279
+ } else {
280
+ this.addParticipant(data);
342
281
  }
343
282
  }
344
283
  /**
@@ -368,6 +307,21 @@ class WeightedCensus extends Census {
368
307
  getWeight(address) {
369
308
  return this._participants.get(address.toLowerCase());
370
309
  }
310
+ /**
311
+ * Internal method to add a plain address with a given weight
312
+ */
313
+ addAddress(address, weight) {
314
+ this.validateAddress(address);
315
+ this._participants.set(address.toLowerCase(), weight);
316
+ }
317
+ /**
318
+ * Internal method to add a weighted participant
319
+ */
320
+ addParticipant(participant) {
321
+ this.validateAddress(participant.key);
322
+ const weight = this.normalizeWeight(participant.weight);
323
+ this._participants.set(participant.key.toLowerCase(), weight);
324
+ }
371
325
  /**
372
326
  * Normalizes weight from string, number, or bigint to string
373
327
  */
@@ -392,32 +346,63 @@ class WeightedCensus extends Census {
392
346
  }
393
347
  throw new Error(`Invalid weight type. Must be string, number, or bigint.`);
394
348
  }
395
- validateParticipant(participant) {
396
- if (!participant.key || typeof participant.key !== "string") {
397
- throw new Error("Participant key (address) is required");
398
- }
399
- if (!/^(0x)?[0-9a-fA-F]{40}$/i.test(participant.key)) {
400
- throw new Error(`Invalid Ethereum address format: ${participant.key}`);
349
+ /**
350
+ * Validates Ethereum address format
351
+ */
352
+ validateAddress(address) {
353
+ if (!address || typeof address !== "string") {
354
+ throw new Error("Address is required and must be a string");
401
355
  }
402
- if (participant.weight === void 0 || participant.weight === null) {
403
- throw new Error("Participant weight is required");
356
+ if (!/^(0x)?[0-9a-fA-F]{40}$/i.test(address)) {
357
+ throw new Error(`Invalid Ethereum address format: ${address}`);
404
358
  }
405
359
  }
406
360
  /**
407
361
  * Internal method called after publishing
408
362
  * @internal
409
363
  */
410
- _setPublishedData(root, uri, size, censusId) {
364
+ _setPublishedData(root, uri, censusId) {
411
365
  this._censusRoot = root;
412
366
  this._censusURI = uri;
413
- this._size = size;
414
367
  if (censusId) this._censusId = censusId;
415
368
  }
416
369
  }
417
370
 
371
+ class OffchainCensus extends MerkleCensus {
372
+ constructor() {
373
+ super(CensusOrigin.OffchainStatic);
374
+ }
375
+ }
376
+
377
+ class OffchainDynamicCensus extends MerkleCensus {
378
+ constructor() {
379
+ super(CensusOrigin.OffchainDynamic);
380
+ }
381
+ }
382
+
383
+ class OnchainCensus extends Census {
384
+ /**
385
+ * Creates an OnchainCensus
386
+ * @param contractAddress - The address of the smart contract (e.g., ERC20, ERC721)
387
+ * @param uri - Optional URI with census information
388
+ */
389
+ constructor(contractAddress, uri) {
390
+ super(CensusOrigin.Onchain);
391
+ if (!/^(0x)?[0-9a-fA-F]{40}$/i.test(contractAddress)) {
392
+ throw new Error("Contract address is missing or invalid");
393
+ }
394
+ this._contractAddress = contractAddress;
395
+ this._censusRoot = contractAddress;
396
+ this._censusURI = uri || `contract://${contractAddress}`;
397
+ }
398
+ get contractAddress() {
399
+ return this._contractAddress;
400
+ }
401
+ }
402
+
418
403
  class CspCensus extends Census {
419
- constructor(publicKey, cspURI, size) {
420
- super(CensusType.CSP);
404
+ constructor(publicKey, cspURI) {
405
+ super(CensusOrigin.CSP);
421
406
  if (!/^(0x)?[0-9a-fA-F]+$/.test(publicKey)) {
422
407
  throw new Error("Public key is missing or invalid");
423
408
  }
@@ -430,7 +415,6 @@ class CspCensus extends Census {
430
415
  this._cspURI = cspURI;
431
416
  this._censusRoot = publicKey;
432
417
  this._censusURI = cspURI;
433
- this._size = size;
434
418
  }
435
419
  get publicKey() {
436
420
  return this._publicKey;
@@ -441,11 +425,16 @@ class CspCensus extends Census {
441
425
  }
442
426
 
443
427
  class PublishedCensus extends Census {
444
- constructor(type, root, uri, size) {
445
- super(type);
428
+ /**
429
+ * Creates a PublishedCensus from existing census data
430
+ * @param censusOrigin - The census origin (OffchainStatic, OffchainDynamic, Onchain, or CSP)
431
+ * @param root - The census root
432
+ * @param uri - The census URI
433
+ */
434
+ constructor(censusOrigin, root, uri) {
435
+ super(censusOrigin);
446
436
  this._censusRoot = root;
447
437
  this._censusURI = uri;
448
- this._size = size;
449
438
  }
450
439
  }
451
440
 
@@ -454,7 +443,7 @@ class CensusOrchestrator {
454
443
  this.censusService = censusService;
455
444
  }
456
445
  /**
457
- * Publishes a PlainCensus or WeightedCensus
446
+ * Publishes a MerkleCensus (OffchainCensus, OffchainDynamicCensus, or OnchainCensus)
458
447
  * Creates a working census, adds participants, and publishes it
459
448
  */
460
449
  async publish(census) {
@@ -470,23 +459,24 @@ class CensusOrchestrator {
470
459
  census._setPublishedData(
471
460
  publishResponse.root,
472
461
  publishResponse.uri,
473
- publishResponse.participantCount,
474
462
  censusId
475
463
  );
476
464
  }
477
465
  /**
478
466
  * Gets census data for process creation
479
- * Throws if census is not published
467
+ * Throws if census is not ready (published for Merkle/CSP, or constructed for Onchain)
480
468
  */
481
469
  getCensusData(census) {
482
- if (!census.isPublished) {
483
- throw new Error("Census must be published before creating a process");
470
+ if (census.requiresPublishing && !census.isPublished) {
471
+ throw new Error("Merkle census must be published before creating a process");
472
+ }
473
+ if (!census.censusRoot || !census.censusURI) {
474
+ throw new Error("Census data is incomplete");
484
475
  }
485
476
  return {
486
477
  type: census.censusOrigin,
487
478
  root: census.censusRoot,
488
- uri: census.censusURI,
489
- size: census.size
479
+ uri: census.censusURI
490
480
  };
491
481
  }
492
482
  }
@@ -941,6 +931,8 @@ class ProcessStatusError extends ContractServiceError {
941
931
  }
942
932
  class ProcessCensusError extends ContractServiceError {
943
933
  }
934
+ class CensusNotUpdatable extends ContractServiceError {
935
+ }
944
936
  class ProcessDurationError extends ContractServiceError {
945
937
  }
946
938
  class ProcessStateTransitionError extends ContractServiceError {
@@ -1120,12 +1112,12 @@ class ProcessRegistryService extends SmartContractService {
1120
1112
  cb
1121
1113
  ).catch((err) => console.error("Error setting up ProcessDurationChanged listener:", err));
1122
1114
  }
1123
- onStateRootUpdated(cb) {
1115
+ onStateTransitioned(cb) {
1124
1116
  this.setupEventListener(
1125
1117
  this.contract,
1126
- this.contract.filters.ProcessStateRootUpdated(),
1118
+ this.contract.filters.ProcessStateTransitioned(),
1127
1119
  cb
1128
- ).catch((err) => console.error("Error setting up StateRootUpdated listener:", err));
1120
+ ).catch((err) => console.error("Error setting up ProcessStateTransitioned listener:", err));
1129
1121
  }
1130
1122
  onProcessResultsSet(cb) {
1131
1123
  this.setupEventListener(
@@ -1162,26 +1154,24 @@ class ProcessOrchestrationService {
1162
1154
  */
1163
1155
  async handleCensus(census) {
1164
1156
  if ("isPublished" in census) {
1165
- if (census instanceof PlainCensus || census instanceof WeightedCensus) {
1166
- if (!census.isPublished) {
1167
- const censusBaseURL = this.apiService.census?.["axios"]?.defaults?.baseURL;
1168
- if (!censusBaseURL || censusBaseURL === "" || censusBaseURL === "undefined") {
1169
- throw new Error(
1170
- 'Census API URL is required to publish PlainCensus or WeightedCensus. Please provide "censusUrl" when initializing DavinciSDK, or use a pre-published census.'
1171
- );
1172
- }
1173
- await this.censusOrchestrator.publish(census);
1157
+ if (census.requiresPublishing && !census.isPublished) {
1158
+ const censusBaseURL = this.apiService.census?.["axios"]?.defaults?.baseURL;
1159
+ if (!censusBaseURL || censusBaseURL === "" || censusBaseURL === "undefined") {
1160
+ throw new Error(
1161
+ 'Census API URL is required to publish Merkle censuses (OffchainCensus, OffchainDynamicCensus). Please provide "censusUrl" when initializing DavinciSDK, or use a pre-published census.'
1162
+ );
1174
1163
  }
1164
+ await this.censusOrchestrator.publish(census);
1175
1165
  }
1176
1166
  const censusData = this.censusOrchestrator.getCensusData(census);
1177
1167
  return {
1178
1168
  type: censusData.type,
1179
1169
  root: censusData.root,
1180
- size: censusData.size,
1181
1170
  uri: censusData.uri
1182
1171
  };
1183
1172
  }
1184
- return census;
1173
+ const { size, ...censusWithoutSize } = census;
1174
+ return censusWithoutSize;
1185
1175
  }
1186
1176
  /**
1187
1177
  * Gets user-friendly process information by transforming raw contract data
@@ -1389,7 +1379,22 @@ class ProcessOrchestrationService {
1389
1379
  ballotMode,
1390
1380
  signature
1391
1381
  });
1392
- const maxVoters = config.maxVoters ?? censusConfig.size;
1382
+ let maxVoters;
1383
+ if (config.maxVoters !== void 0) {
1384
+ maxVoters = config.maxVoters;
1385
+ } else if ("isPublished" in config.census && config.census.isPublished) {
1386
+ if ("participants" in config.census) {
1387
+ maxVoters = config.census.participants.length;
1388
+ } else {
1389
+ throw new Error(
1390
+ "maxVoters is required when using OnchainCensus, CspCensus, or PublishedCensus. It can only be auto-calculated for published MerkleCensus (OffchainCensus/OffchainDynamicCensus)."
1391
+ );
1392
+ }
1393
+ } else {
1394
+ throw new Error(
1395
+ "maxVoters is required. It can only be omitted when using a published MerkleCensus (OffchainCensus/OffchainDynamicCensus), in which case it defaults to the participant count."
1396
+ );
1397
+ }
1393
1398
  const census = {
1394
1399
  censusOrigin: censusConfig.type,
1395
1400
  censusRoot,
@@ -2019,7 +2024,7 @@ class VoteOrchestrationService {
2019
2024
  signature,
2020
2025
  voteId
2021
2026
  };
2022
- if (process.census.censusOrigin === CensusOrigin.CensusOriginCSP) {
2027
+ if (process.census.censusOrigin === CensusOrigin.CSP) {
2023
2028
  voteRequest.censusProof = censusProof;
2024
2029
  }
2025
2030
  await this.submitVoteRequest(voteRequest);
@@ -2135,7 +2140,7 @@ class VoteOrchestrationService {
2135
2140
  * Get census proof based on census origin type
2136
2141
  */
2137
2142
  async getCensusProof(censusOrigin, censusRoot, voterAddress, processId) {
2138
- if (censusOrigin === CensusOrigin.CensusOriginMerkleTree) {
2143
+ if (censusOrigin === CensusOrigin.OffchainStatic || censusOrigin === CensusOrigin.OffchainDynamic || censusOrigin === CensusOrigin.Onchain) {
2139
2144
  if (this.censusProviders.merkle) {
2140
2145
  const proof = await this.censusProviders.merkle({
2141
2146
  censusRoot,
@@ -2149,13 +2154,13 @@ class VoteOrchestrationService {
2149
2154
  root: censusRoot,
2150
2155
  address: voterAddress,
2151
2156
  weight,
2152
- censusOrigin: CensusOrigin.CensusOriginMerkleTree,
2157
+ censusOrigin,
2153
2158
  value: "",
2154
2159
  siblings: ""
2155
2160
  };
2156
2161
  }
2157
2162
  }
2158
- if (censusOrigin === CensusOrigin.CensusOriginCSP) {
2163
+ if (censusOrigin === CensusOrigin.CSP) {
2159
2164
  if (!this.censusProviders.csp) {
2160
2165
  throw new Error(
2161
2166
  "CSP voting requires a CSP census proof provider. Pass one via VoteOrchestrationService(..., { csp: yourFn })."
@@ -2729,7 +2734,7 @@ class DavinciSDK {
2729
2734
  * title: "My Election",
2730
2735
  * description: "A simple election",
2731
2736
  * census: {
2732
- * type: CensusOrigin.CensusOriginMerkleTree,
2737
+ * type: CensusOrigin.OffchainStatic,
2733
2738
  * root: "0x1234...",
2734
2739
  * size: 100,
2735
2740
  * uri: "ipfs://..."
@@ -2816,7 +2821,7 @@ class DavinciSDK {
2816
2821
  * title: "My Election",
2817
2822
  * description: "A simple election",
2818
2823
  * census: {
2819
- * type: CensusOrigin.CensusOriginMerkleTree,
2824
+ * type: CensusOrigin.OffchainStatic,
2820
2825
  * root: "0x1234...",
2821
2826
  * size: 100,
2822
2827
  * uri: "ipfs://your-census-uri"
@@ -3513,5 +3518,5 @@ class DavinciSDK {
3513
3518
  }
3514
3519
  }
3515
3520
 
3516
- 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 };
3521
+ export { BaseService, Census, CensusNotUpdatable, CensusOrchestrator, CensusOrigin, CircomProof, ContractServiceError, CspCensus, DavinciCrypto, DavinciSDK, ElectionMetadataTemplate, ElectionResultsTypeNames, MerkleCensus, OffchainCensus, OffchainDynamicCensus, OnchainCensus, OrganizationAdministratorError, OrganizationCreateError, OrganizationDeleteError, OrganizationRegistryService, OrganizationUpdateError, ProcessCensusError, ProcessCreateError, ProcessDurationError, ProcessOrchestrationService, ProcessRegistryService, ProcessResultError, ProcessStateTransitionError, ProcessStatus, ProcessStatusError, PublishedCensus, SmartContractService, TxStatus, VocdoniApiService, VocdoniCensusService, VocdoniSequencerService, VoteOrchestrationService, VoteStatus, assertCSPCensusProof, assertMerkleCensusProof, createProcessSignatureMessage, getElectionMetadataTemplate, isCSPCensusProof, isMerkleCensusProof, signProcessCreation, validateProcessId };
3517
3522
  //# sourceMappingURL=index.mjs.map