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