TypeScript SDK for the Vocdoni DaVinci voting protocol.
Requires Node.js 18 or newer and an Ethereum signer.
npm install @vocdoni/davinci-sdk ethers
yarn add @vocdoni/davinci-sdk ethers
pnpm add @vocdoni/davinci-sdk ethersEnd-to-end example: initialize the SDK, create a process with an off-chain census, submit a vote, and wait for it to settle.
import { DavinciSDK, OffchainCensus, VoteStatus } from '@vocdoni/davinci-sdk';
import { Wallet } from 'ethers';
// Substitute your own sequencer and census endpoints.
const SEQUENCER = 'https://sequencer-dev.davinci.vote';
const CENSUS = 'https://c3-dev.davinci.vote';
// Organizer SDK — needs a census URL because we're creating a census.
const organizer = new DavinciSDK({
signer: new Wallet(process.env.ORGANIZER_KEY!),
sequencerUrl: SEQUENCER,
censusUrl: CENSUS,
});
await organizer.init();
// Eligible voters.
const census = new OffchainCensus();
census.add([
'0x1234567890123456789012345678901234567890',
'0x2345678901234567890123456789012345678901',
'0x3456789012345678901234567890123456789012',
]);
const { processId } = await organizer.createProcess({
title: 'Community Decision',
description: 'Vote on our next community initiative.',
census,
timing: {
startDate: new Date(Date.now() + 60_000), // starts in one minute
duration: 86_400, // open for 24 hours
},
questions: [{
title: 'Which initiative should we prioritize?',
choices: [
{ title: 'Community Garden', value: 0 },
{ title: 'Tech Workshop', value: 1 },
{ title: 'Art Exhibition', value: 2 },
],
}],
});
// Voter SDK — no census URL needed for voting-only operations.
const voter = new DavinciSDK({
signer: new Wallet(process.env.VOTER_KEY!),
sequencerUrl: SEQUENCER,
});
await voter.init();
const { voteId } = await voter.submitVote({
processId,
choices: [1],
});
const final = await voter.waitForVoteStatus(processId, voteId, VoteStatus.Settled);
console.log('Vote settled:', final.status);- Process creation. The organizer registers a process on-chain with its census, timing, and ballot configuration.
- Vote submission. Eligible voters submit encrypted ballots through the sequencer.
- Vote processing. The sequencer aggregates and verifies votes using zk-SNARKs.
- Results. Final tallies become available once the process settles.
- Census — set of eligible voters, addressed either off-chain (Merkle tree) or on-chain (token contract).
- Ballot — structure describing the questions, choice ranges, and aggregation rules.
- Process — the on-chain container holding the census, ballot, timing, and lifecycle status.
- Proof — zero-knowledge attestation that a vote is valid without revealing its contents.
Resolves once the process is registered. Use this when you do not need to surface intermediate transaction status.
import { DavinciSDK, OffchainCensus } from '@vocdoni/davinci-sdk';
const sdk = new DavinciSDK({ signer, sequencerUrl, censusUrl });
await sdk.init();
const census = new OffchainCensus();
census.add(['0x...', '0x...']);
const result = await sdk.createProcess({
title: 'Election',
description: 'Detailed description of the election.',
census,
timing: {
startDate: new Date(Date.now() + 60_000),
duration: 86_400,
},
ballot: {
numFields: 1,
maxValue: '2',
minValue: '0',
uniqueValues: false,
costExponent: 1,
maxValueSum: '2',
minValueSum: '0',
},
questions: [{
title: 'What is your preferred option?',
description: 'Choose the option that best represents your view.',
choices: [
{ title: 'Option A', value: 0 },
{ title: 'Option B', value: 1 },
{ title: 'Option C', value: 2 },
],
}],
});
console.log('Process ID:', result.processId);OffchainCensus is published automatically as part of createProcess, and maxVoters is set to the participant count. For other census types, see Advanced configuration.
For common voting modes, pass an electionPreset instead of a raw BallotMode. The SDK derives the ballot mode using questions[0].choices.length as the field count. ballot and electionPreset are mutually exclusive — provide one or the other.
await sdk.createProcess({
electionPreset: { type: 'rating', maxValue: 5 },
questions: [{
title: 'Rate each candidate',
choices: [
{ title: 'Alice', value: 0 },
{ title: 'Bob', value: 1 },
{ title: 'Carol', value: 2 },
],
}],
/* census, timing, ... */
});The six supported presets — single_choice, multiple_choice, approval, rating, ranking, quadratic — follow the canonical DAVINCI ballot protocol. Each variant carries its own typed options (e.g. rating requires maxValue, quadratic requires budget). See the JSDoc on ElectionPreset for the exact parameter mapping per type.
When a process is created with electionPreset, the preset is also stored in the off-chain election metadata (under meta.electionPreset). getProcess(processId) reads it back and exposes it as info.electionPreset alongside the raw info.ballot. Processes created with a raw BallotMode carry no preset on the way out — only info.ballot is populated.
Returns an async iterator of TxStatus events for the underlying transaction. Use this in UI flows that need to display submission, mining, and revert states.
import { TxStatus } from '@vocdoni/davinci-sdk';
const stream = sdk.createProcessStream(config);
for await (const event of stream) {
switch (event.status) {
case TxStatus.Pending:
console.log('Transaction submitted:', event.hash);
break;
case TxStatus.Completed:
console.log('Process created:', event.response.processId);
break;
case TxStatus.Failed:
console.error('Transaction failed:', event.error);
break;
case TxStatus.Reverted:
console.error('Transaction reverted:', event.reason);
break;
}
}Use createProcess for scripts and short flows. Use createProcessStream when the caller needs per-event UI feedback.
const result = await sdk.submitVote({
processId: '0x...',
choices: [1, 0],
});
console.log('Vote ID:', result.voteId);
console.log('Status:', result.status);const status = await sdk.getVoteStatus(processId, voteId);
console.log('Status:', status.status);VoteStatus is one of pending, verified, aggregated, processed, settled, or error.
Polls until the vote reaches target or the timeout elapses.
import { VoteStatus } from '@vocdoni/davinci-sdk';
const final = await sdk.waitForVoteStatus(
processId,
voteId,
VoteStatus.Settled,
300_000, // 5 minute timeout
5_000, // 5 second poll interval
);// Has this address already cast a vote in this process?
const voted = await sdk.hasAddressVoted(processId, voterAddress);
// Is this address allowed to vote, and with what weight?
const info = await sdk.isAddressAbleToVote(processId, voterAddress);
console.log('Key:', info.key, 'weight:', info.weight);Other census types
Off-chain Merkle census whose participants can be updated after process creation.
import { OffchainDynamicCensus } from '@vocdoni/davinci-sdk';
const census = new OffchainDynamicCensus();
census.add([
{ key: '0x...', weight: 10 },
{ key: '0x...', weight: 20 },
]);
await sdk.createProcess({ census, /* ... */ });Census whose membership is attested by an external Credential Service Provider.
import { CspCensus } from '@vocdoni/davinci-sdk';
const census = new CspCensus(
'0x1234567890abcdef', // CSP root (public key)
'https://csp-server.example',
);
await sdk.createProcess({
census,
maxVoters: 1000, // required for CSP census
// ...
});Reuse a census already published to the network.
import { PublishedCensus, CensusOrigin } from '@vocdoni/davinci-sdk';
const census = new PublishedCensus(
CensusOrigin.OffchainStatic,
'0xroot...',
'ipfs://uri...',
);
await sdk.createProcess({
census,
maxVoters: 100,
// ...
});Token-gated census backed by an ERC-20 or ERC-721 contract, with off-chain holder data sourced from a subgraph.
import { OnchainCensus } from '@vocdoni/davinci-sdk';
const census = new OnchainCensus(
'0xTokenContract...',
'https://api.studio.thegraph.com/query/12345/token-holders/v1.0.0',
);
await sdk.createProcess({
census,
maxVoters: 10_000,
// ...
});The contract address is validated on construction; no publishing step is required.
Custom contract addresses
By default the SDK fetches the ProcessRegistry address from the sequencer's /info endpoint during init(). Override with addresses.processRegistry when running against a non-default deployment.
const sdk = new DavinciSDK({
signer,
sequencerUrl,
addresses: {
processRegistry: '0xYourRegistryAddress...',
},
});Custom vote randomness
Pass a hex-encoded randomness value to submitVote to override the SDK's default entropy source. Used primarily for deterministic test fixtures.
const result = await sdk.submitVote({
processId,
choices: [1],
randomness: '0xabc...',
});Direct service access
The high-level DavinciSDK composes three lower-level services. Each is reachable when you need an endpoint the facade does not surface.
// Sequencer REST client
const process = await sdk.api.sequencer.getProcess(processId);
// Census REST client
const proof = await sdk.api.census.getCensusProof(root, address);
// CSP client (lazy-initialized)
const csp = await sdk.getCSP();Manual census configuration
createProcess also accepts a plain census descriptor when you already have a published root and URI.
import { CensusOrigin } from '@vocdoni/davinci-sdk';
await sdk.createProcess({
census: {
type: CensusOrigin.OffchainStatic,
root: '0xabc...',
uri: 'ipfs://...',
},
maxVoters: 100,
// ...
});submitVote, createProcess, and the process lifecycle methods throw Error instances whose message describes the failure. Discriminate by inspecting the message.
try {
await sdk.submitVote({ processId, choices: [1, 2, 3] });
} catch (err) {
const msg = (err as Error).message;
if (msg.includes('already voted')) { /* the voter has already submitted */ }
else if (msg.includes('not accepting votes')) { /* voting period is closed */ }
else if (msg.includes('out of range')) { /* a choice is outside the configured range */ }
else { throw err; }
}Common error categories:
- Process errors — process not found, not accepting votes, invalid configuration.
- Vote errors — already voted, invalid choices, proof generation failed.
- Network errors — connection failures, transaction failures.
- Validation errors — invalid parameters, out-of-range values.
A runnable end-to-end script lives under examples/script/. It exercises the full lifecycle — process creation, vote submission, and settlement — against a configurable sequencer and census service.
Machine-readable documentation for AI coding tools (Cursor, Claude Code, Cline, ChatGPT, custom agents) lives at the repo root as llms.txt (index) and llms-full.txt (full bundle). Configure your tool to load the raw URL:
- https://raw.githubusercontent.com/vocdoni/davinci-sdk/main/llms.txt
- https://raw.githubusercontent.com/vocdoni/davinci-sdk/main/llms-full.txt
Browsable source for the same content is under docs/ai/. For a Claude Code plugin installation, see the @vocdoni/skills marketplace.
See CONTRIBUTING.md for development setup, branching, and PR conventions.
Code quality tools:
- TypeScript — full type safety
- ESLint — linting and style
- Prettier — formatting
- Vitest — unit and integration test runner
- Husky — pre-commit hooks
Released under the GNU Affero General Public License v3.0 (AGPL-3.0).
AGPL-3.0 is a copyleft license: derivative works and network-deployed applications must make corresponding source available under the same terms.
- Protocol whitepaper — https://whitepaper.vocdoni.io
- API documentation — https://github.com/vocdoni/davinci-node/tree/main/api
- Discord — https://chat.vocdoni.io
- Telegram — https://t.me/vocdoni_community
- Twitter — https://twitter.com/vocdoni
- AI documentation —
docs/ai/ - Website — https://vocdoni.io