@vocdoni/davinci-sdk 0.0.7 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.umd.js CHANGED
@@ -219,34 +219,12 @@
219
219
  }
220
220
  }
221
221
 
222
- var CensusType = /* @__PURE__ */ ((CensusType2) => {
223
- CensusType2["PLAIN"] = "plain";
224
- CensusType2["WEIGHTED"] = "weighted";
225
- CensusType2["CSP"] = "csp";
226
- return CensusType2;
227
- })(CensusType || {});
228
222
  class Census {
229
- constructor(type, censusOrigin) {
223
+ constructor(censusOrigin) {
230
224
  this._censusId = null;
231
225
  this._censusRoot = null;
232
226
  this._censusURI = null;
233
- this._size = null;
234
- this._type = type;
235
- if (censusOrigin !== void 0) {
236
- this._censusOrigin = censusOrigin;
237
- } else {
238
- switch (type) {
239
- case "plain" /* PLAIN */:
240
- case "weighted" /* WEIGHTED */:
241
- this._censusOrigin = CensusOrigin.OffchainStatic;
242
- break;
243
- case "csp" /* CSP */:
244
- this._censusOrigin = CensusOrigin.CSP;
245
- break;
246
- default:
247
- throw new Error(`Unknown census type: ${type}`);
248
- }
249
- }
227
+ this._censusOrigin = censusOrigin;
250
228
  }
251
229
  get censusId() {
252
230
  return this._censusId;
@@ -257,12 +235,6 @@
257
235
  get censusURI() {
258
236
  return this._censusURI;
259
237
  }
260
- get type() {
261
- return this._type;
262
- }
263
- get size() {
264
- return this._size;
265
- }
266
238
  get isPublished() {
267
239
  return this._censusRoot !== null && this._censusURI !== null;
268
240
  }
@@ -272,91 +244,41 @@
272
244
  get censusOrigin() {
273
245
  return this._censusOrigin;
274
246
  }
275
- }
276
-
277
- class PlainCensus extends Census {
278
- /**
279
- * Creates a new PlainCensus
280
- * @param censusOrigin - The census origin (defaults to OffchainStatic for backward compatibility)
281
- */
282
- constructor(censusOrigin) {
283
- super(CensusType.PLAIN, censusOrigin);
284
- this._participants = /* @__PURE__ */ new Set();
285
- }
286
247
  /**
287
- * Add participant(s) with automatic weight=1
288
- * @param addresses - Single address or array of addresses
248
+ * Check if this census requires publishing via the Census API
249
+ * Merkle censuses (OffchainStatic, OffchainDynamic) need to be published
250
+ * Onchain and CSP censuses are ready immediately upon construction
289
251
  */
290
- add(addresses) {
291
- const toAdd = Array.isArray(addresses) ? addresses : [addresses];
292
- for (const address of toAdd) {
293
- this.validateAddress(address);
294
- this._participants.add(address.toLowerCase());
295
- }
296
- }
297
- /**
298
- * Remove participant by address
299
- */
300
- remove(address) {
301
- this._participants.delete(address.toLowerCase());
302
- }
303
- /**
304
- * Get all participants as CensusParticipant array (for API)
305
- * All participants have weight="1"
306
- */
307
- get participants() {
308
- return Array.from(this._participants).map((key) => ({
309
- key,
310
- weight: "1"
311
- // Everyone has weight=1 in plain census
312
- }));
313
- }
314
- /**
315
- * Get addresses only
316
- */
317
- get addresses() {
318
- return Array.from(this._participants);
319
- }
320
- validateAddress(address) {
321
- if (!address || typeof address !== "string") {
322
- throw new Error("Address is required and must be a string");
323
- }
324
- if (!/^(0x)?[0-9a-fA-F]{40}$/i.test(address)) {
325
- throw new Error(`Invalid Ethereum address format: ${address}`);
326
- }
327
- }
328
- /**
329
- * Internal method called after publishing
330
- * @internal
331
- */
332
- _setPublishedData(root, uri, size, censusId) {
333
- this._censusRoot = root;
334
- this._censusURI = uri;
335
- this._size = size;
336
- if (censusId) this._censusId = censusId;
252
+ get requiresPublishing() {
253
+ return this._censusOrigin === CensusOrigin.OffchainStatic || this._censusOrigin === CensusOrigin.OffchainDynamic;
337
254
  }
338
255
  }
339
256
 
340
- class WeightedCensus extends Census {
341
- /**
342
- * Creates a new WeightedCensus
343
- * @param censusOrigin - The census origin (defaults to OffchainStatic for backward compatibility)
344
- */
257
+ class MerkleCensus extends Census {
345
258
  constructor(censusOrigin) {
346
- super(CensusType.WEIGHTED, censusOrigin);
259
+ super(censusOrigin);
347
260
  this._participants = /* @__PURE__ */ new Map();
348
261
  }
349
262
  /**
350
- * Add participant(s) with custom weights
351
- * Weight can be provided as string, number, or bigint - will be converted to string internally
352
- * @param participant - Single participant or array of participants with custom weights
263
+ * Add participant(s) - supports both plain addresses and weighted participants
264
+ * @param data - Can be:
265
+ * - string: single address (weight=1)
266
+ * - string[]: array of addresses (weight=1 for all)
267
+ * - {key: string, weight: string|number|bigint}: single weighted participant
268
+ * - Array of weighted participants
353
269
  */
354
- add(participant) {
355
- const toAdd = Array.isArray(participant) ? participant : [participant];
356
- for (const p of toAdd) {
357
- this.validateParticipant(p);
358
- const weightString = this.normalizeWeight(p.weight);
359
- this._participants.set(p.key.toLowerCase(), weightString);
270
+ add(data) {
271
+ if (typeof data === "string") {
272
+ this.addAddress(data, "1");
273
+ } else if (Array.isArray(data)) {
274
+ if (data.length === 0) return;
275
+ if (typeof data[0] === "string") {
276
+ data.forEach((addr) => this.addAddress(addr, "1"));
277
+ } else {
278
+ data.forEach((p) => this.addParticipant(p));
279
+ }
280
+ } else {
281
+ this.addParticipant(data);
360
282
  }
361
283
  }
362
284
  /**
@@ -386,6 +308,21 @@
386
308
  getWeight(address) {
387
309
  return this._participants.get(address.toLowerCase());
388
310
  }
311
+ /**
312
+ * Internal method to add a plain address with a given weight
313
+ */
314
+ addAddress(address, weight) {
315
+ this.validateAddress(address);
316
+ this._participants.set(address.toLowerCase(), weight);
317
+ }
318
+ /**
319
+ * Internal method to add a weighted participant
320
+ */
321
+ addParticipant(participant) {
322
+ this.validateAddress(participant.key);
323
+ const weight = this.normalizeWeight(participant.weight);
324
+ this._participants.set(participant.key.toLowerCase(), weight);
325
+ }
389
326
  /**
390
327
  * Normalizes weight from string, number, or bigint to string
391
328
  */
@@ -410,32 +347,66 @@
410
347
  }
411
348
  throw new Error(`Invalid weight type. Must be string, number, or bigint.`);
412
349
  }
413
- validateParticipant(participant) {
414
- if (!participant.key || typeof participant.key !== "string") {
415
- throw new Error("Participant key (address) is required");
416
- }
417
- if (!/^(0x)?[0-9a-fA-F]{40}$/i.test(participant.key)) {
418
- throw new Error(`Invalid Ethereum address format: ${participant.key}`);
350
+ /**
351
+ * Validates Ethereum address format
352
+ */
353
+ validateAddress(address) {
354
+ if (!address || typeof address !== "string") {
355
+ throw new Error("Address is required and must be a string");
419
356
  }
420
- if (participant.weight === void 0 || participant.weight === null) {
421
- throw new Error("Participant weight is required");
357
+ if (!/^(0x)?[0-9a-fA-F]{40}$/i.test(address)) {
358
+ throw new Error(`Invalid Ethereum address format: ${address}`);
422
359
  }
423
360
  }
424
361
  /**
425
362
  * Internal method called after publishing
426
363
  * @internal
427
364
  */
428
- _setPublishedData(root, uri, size, censusId) {
365
+ _setPublishedData(root, uri, censusId) {
429
366
  this._censusRoot = root;
430
367
  this._censusURI = uri;
431
- this._size = size;
432
368
  if (censusId) this._censusId = censusId;
433
369
  }
434
370
  }
435
371
 
372
+ class OffchainCensus extends MerkleCensus {
373
+ constructor() {
374
+ super(CensusOrigin.OffchainStatic);
375
+ }
376
+ }
377
+
378
+ class OffchainDynamicCensus extends MerkleCensus {
379
+ constructor() {
380
+ super(CensusOrigin.OffchainDynamic);
381
+ }
382
+ }
383
+
384
+ class OnchainCensus extends Census {
385
+ /**
386
+ * Creates an OnchainCensus
387
+ * @param contractAddress - The address of the smart contract (e.g., ERC20, ERC721)
388
+ * @param uri - The URI pointing to census data source (e.g., subgraph endpoint)
389
+ */
390
+ constructor(contractAddress, uri) {
391
+ super(CensusOrigin.Onchain);
392
+ if (!/^(0x)?[0-9a-fA-F]{40}$/i.test(contractAddress)) {
393
+ throw new Error("Contract address is missing or invalid");
394
+ }
395
+ if (!uri || uri.trim() === "") {
396
+ throw new Error("URI is required for onchain census");
397
+ }
398
+ this._contractAddress = contractAddress;
399
+ this._censusRoot = "0x0000000000000000000000000000000000000000000000000000000000000000";
400
+ this._censusURI = uri;
401
+ }
402
+ get contractAddress() {
403
+ return this._contractAddress;
404
+ }
405
+ }
406
+
436
407
  class CspCensus extends Census {
437
- constructor(publicKey, cspURI, size) {
438
- super(CensusType.CSP, CensusOrigin.CSP);
408
+ constructor(publicKey, cspURI) {
409
+ super(CensusOrigin.CSP);
439
410
  if (!/^(0x)?[0-9a-fA-F]+$/.test(publicKey)) {
440
411
  throw new Error("Public key is missing or invalid");
441
412
  }
@@ -448,7 +419,6 @@
448
419
  this._cspURI = cspURI;
449
420
  this._censusRoot = publicKey;
450
421
  this._censusURI = cspURI;
451
- this._size = size;
452
422
  }
453
423
  get publicKey() {
454
424
  return this._publicKey;
@@ -461,17 +431,14 @@
461
431
  class PublishedCensus extends Census {
462
432
  /**
463
433
  * Creates a PublishedCensus from existing census data
464
- * @param type - The census type (PLAIN, WEIGHTED, or CSP)
434
+ * @param censusOrigin - The census origin (OffchainStatic, OffchainDynamic, Onchain, or CSP)
465
435
  * @param root - The census root
466
436
  * @param uri - The census URI
467
- * @param size - The census size (number of participants)
468
- * @param censusOrigin - The census origin (optional - defaults based on type if not provided)
469
437
  */
470
- constructor(type, root, uri, size, censusOrigin) {
471
- super(type, censusOrigin);
438
+ constructor(censusOrigin, root, uri) {
439
+ super(censusOrigin);
472
440
  this._censusRoot = root;
473
441
  this._censusURI = uri;
474
- this._size = size;
475
442
  }
476
443
  }
477
444
 
@@ -480,7 +447,7 @@
480
447
  this.censusService = censusService;
481
448
  }
482
449
  /**
483
- * Publishes a PlainCensus or WeightedCensus
450
+ * Publishes a MerkleCensus (OffchainCensus, OffchainDynamicCensus, or OnchainCensus)
484
451
  * Creates a working census, adds participants, and publishes it
485
452
  */
486
453
  async publish(census) {
@@ -496,23 +463,26 @@
496
463
  census._setPublishedData(
497
464
  publishResponse.root,
498
465
  publishResponse.uri,
499
- publishResponse.participantCount,
500
466
  censusId
501
467
  );
502
468
  }
503
469
  /**
504
470
  * Gets census data for process creation
505
- * Throws if census is not published
471
+ * Throws if census is not ready (published for Merkle/CSP, or constructed for Onchain)
506
472
  */
507
473
  getCensusData(census) {
508
- if (!census.isPublished) {
509
- throw new Error("Census must be published before creating a process");
474
+ if (census.requiresPublishing && !census.isPublished) {
475
+ throw new Error("Merkle census must be published before creating a process");
510
476
  }
477
+ if (!census.censusRoot || !census.censusURI) {
478
+ throw new Error("Census data is incomplete");
479
+ }
480
+ const contractAddress = "contractAddress" in census ? census.contractAddress : void 0;
511
481
  return {
512
482
  type: census.censusOrigin,
513
483
  root: census.censusRoot,
514
484
  uri: census.censusURI,
515
- size: census.size
485
+ contractAddress
516
486
  };
517
487
  }
518
488
  }
@@ -1036,7 +1006,9 @@
1036
1006
  const contractCensus = {
1037
1007
  censusOrigin: BigInt(census.censusOrigin),
1038
1008
  censusRoot: census.censusRoot,
1039
- censusURI: census.censusURI
1009
+ contractAddress: census.contractAddress ?? "0x0000000000000000000000000000000000000000",
1010
+ censusURI: census.censusURI,
1011
+ onchainAllowAnyValidRoot: census.onchainAllowAnyValidRoot ?? false
1040
1012
  };
1041
1013
  return this.sendTx(
1042
1014
  this.contract.newProcess(
@@ -1067,7 +1039,9 @@
1067
1039
  const contractCensus = {
1068
1040
  censusOrigin: BigInt(census.censusOrigin),
1069
1041
  censusRoot: census.censusRoot,
1070
- censusURI: census.censusURI
1042
+ contractAddress: census.contractAddress ?? "0x0000000000000000000000000000000000000000",
1043
+ censusURI: census.censusURI,
1044
+ onchainAllowAnyValidRoot: census.onchainAllowAnyValidRoot ?? false
1071
1045
  };
1072
1046
  return this.sendTx(
1073
1047
  this.contract.setProcessCensus(processID, contractCensus).catch((e) => {
@@ -1190,26 +1164,19 @@
1190
1164
  */
1191
1165
  async handleCensus(census) {
1192
1166
  if ("isPublished" in census) {
1193
- if (census instanceof PlainCensus || census instanceof WeightedCensus) {
1194
- if (!census.isPublished) {
1195
- const censusBaseURL = this.apiService.census?.["axios"]?.defaults?.baseURL;
1196
- if (!censusBaseURL || censusBaseURL === "" || censusBaseURL === "undefined") {
1197
- throw new Error(
1198
- 'Census API URL is required to publish PlainCensus or WeightedCensus. Please provide "censusUrl" when initializing DavinciSDK, or use a pre-published census.'
1199
- );
1200
- }
1201
- await this.censusOrchestrator.publish(census);
1167
+ if (census.requiresPublishing && !census.isPublished) {
1168
+ const censusBaseURL = this.apiService.census?.["axios"]?.defaults?.baseURL;
1169
+ if (!censusBaseURL || censusBaseURL === "" || censusBaseURL === "undefined") {
1170
+ throw new Error(
1171
+ 'Census API URL is required to publish Merkle censuses (OffchainCensus, OffchainDynamicCensus). Please provide "censusUrl" when initializing DavinciSDK, or use a pre-published census.'
1172
+ );
1202
1173
  }
1174
+ await this.censusOrchestrator.publish(census);
1203
1175
  }
1204
- const censusData = this.censusOrchestrator.getCensusData(census);
1205
- return {
1206
- type: censusData.type,
1207
- root: censusData.root,
1208
- size: censusData.size,
1209
- uri: censusData.uri
1210
- };
1176
+ return this.censusOrchestrator.getCensusData(census);
1211
1177
  }
1212
- return census;
1178
+ const { size, ...censusWithoutSize } = census;
1179
+ return censusWithoutSize;
1213
1180
  }
1214
1181
  /**
1215
1182
  * Gets user-friendly process information by transforming raw contract data
@@ -1417,11 +1384,31 @@
1417
1384
  ballotMode,
1418
1385
  signature
1419
1386
  });
1420
- const maxVoters = config.maxVoters ?? censusConfig.size;
1387
+ let maxVoters;
1388
+ if (config.maxVoters !== void 0) {
1389
+ maxVoters = config.maxVoters;
1390
+ } else if ("isPublished" in config.census && config.census.isPublished) {
1391
+ if ("participants" in config.census) {
1392
+ maxVoters = config.census.participants.length;
1393
+ } else {
1394
+ throw new Error(
1395
+ "maxVoters is required when using OnchainCensus, CspCensus, or PublishedCensus. It can only be auto-calculated for published MerkleCensus (OffchainCensus/OffchainDynamicCensus)."
1396
+ );
1397
+ }
1398
+ } else {
1399
+ throw new Error(
1400
+ "maxVoters is required. It can only be omitted when using a published MerkleCensus (OffchainCensus/OffchainDynamicCensus), in which case it defaults to the participant count."
1401
+ );
1402
+ }
1421
1403
  const census = {
1422
1404
  censusOrigin: censusConfig.type,
1423
1405
  censusRoot,
1424
- censusURI: censusConfig.uri
1406
+ contractAddress: censusConfig.contractAddress,
1407
+ // Only set for onchain censuses
1408
+ censusURI: censusConfig.uri,
1409
+ // For onchain censuses (ERC20 token snapshots), allow any valid merkle root
1410
+ // For other census types, require the specific censusRoot
1411
+ onchainAllowAnyValidRoot: censusConfig.type === CensusOrigin.Onchain
1425
1412
  };
1426
1413
  return {
1427
1414
  processId,
@@ -3546,7 +3533,6 @@
3546
3533
  exports.CensusNotUpdatable = CensusNotUpdatable;
3547
3534
  exports.CensusOrchestrator = CensusOrchestrator;
3548
3535
  exports.CensusOrigin = CensusOrigin;
3549
- exports.CensusType = CensusType;
3550
3536
  exports.CircomProof = CircomProof;
3551
3537
  exports.ContractServiceError = ContractServiceError;
3552
3538
  exports.CspCensus = CspCensus;
@@ -3554,12 +3540,15 @@
3554
3540
  exports.DavinciSDK = DavinciSDK;
3555
3541
  exports.ElectionMetadataTemplate = ElectionMetadataTemplate;
3556
3542
  exports.ElectionResultsTypeNames = ElectionResultsTypeNames;
3543
+ exports.MerkleCensus = MerkleCensus;
3544
+ exports.OffchainCensus = OffchainCensus;
3545
+ exports.OffchainDynamicCensus = OffchainDynamicCensus;
3546
+ exports.OnchainCensus = OnchainCensus;
3557
3547
  exports.OrganizationAdministratorError = OrganizationAdministratorError;
3558
3548
  exports.OrganizationCreateError = OrganizationCreateError;
3559
3549
  exports.OrganizationDeleteError = OrganizationDeleteError;
3560
3550
  exports.OrganizationRegistryService = OrganizationRegistryService;
3561
3551
  exports.OrganizationUpdateError = OrganizationUpdateError;
3562
- exports.PlainCensus = PlainCensus;
3563
3552
  exports.ProcessCensusError = ProcessCensusError;
3564
3553
  exports.ProcessCreateError = ProcessCreateError;
3565
3554
  exports.ProcessDurationError = ProcessDurationError;
@@ -3577,7 +3566,6 @@
3577
3566
  exports.VocdoniSequencerService = VocdoniSequencerService;
3578
3567
  exports.VoteOrchestrationService = VoteOrchestrationService;
3579
3568
  exports.VoteStatus = VoteStatus;
3580
- exports.WeightedCensus = WeightedCensus;
3581
3569
  exports.assertCSPCensusProof = assertCSPCensusProof;
3582
3570
  exports.assertMerkleCensusProof = assertMerkleCensusProof;
3583
3571
  exports.createProcessSignatureMessage = createProcessSignatureMessage;
@@ -149,7 +149,19 @@ interface BallotMode {
149
149
  interface CensusData {
150
150
  censusOrigin: CensusOrigin;
151
151
  censusRoot: string;
152
+ /**
153
+ * Contract address for onchain censuses (ERC20/ERC721 token contract).
154
+ * For offchain censuses, defaults to zero address.
155
+ * @default "0x0000000000000000000000000000000000000000"
156
+ */
157
+ contractAddress?: string;
152
158
  censusURI: string;
159
+ /**
160
+ * For onchain censuses, allows any valid Merkle root from the onchain source.
161
+ * For other census types, set to false to require the specific censusRoot.
162
+ * @default false
163
+ */
164
+ onchainAllowAnyValidRoot?: boolean;
153
165
  }
154
166
  interface EncryptionKey {
155
167
  x: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vocdoni/davinci-sdk",
3
- "version": "0.0.7",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -72,7 +72,7 @@
72
72
  },
73
73
  "dependencies": {
74
74
  "@ethereumjs/common": "^4.4.0",
75
- "@vocdoni/davinci-contracts": "0.0.31",
75
+ "@vocdoni/davinci-contracts": "0.0.35",
76
76
  "axios": "^1.8.4",
77
77
  "ethers": "^6.7.1",
78
78
  "snarkjs": "^0.7.5"