A powerful, easy-to-use TypeScript SDK for building decentralized voting applications on the Vocdoni DaVinci protocol. Create secure, private, and verifiable elections with just a few lines of code.
npm install @vocdoni/davinci-sdk
# or
yarn add @vocdoni/davinci-sdkimport { DavinciSDK, PlainCensus, WeightedCensus } from '@vocdoni/davinci-sdk';
import { Wallet } from 'ethers';
// Initialize the SDK
const wallet = new Wallet('your-private-key');
const sdk = new DavinciSDK({
signer: wallet,
sequencerUrl: 'https://sequencer-dev.davinci.vote',
censusUrl: 'https://c3-dev.davinci.vote'
});
await sdk.init();
// 1. Create a census with eligible voters
const census = new PlainCensus(); // or WeightedCensus for custom voting power
census.add([
'0x1234567890123456789012345678901234567890',
'0x2345678901234567890123456789012345678901',
'0x3456789012345678901234567890123456789012'
]);
// 2. Create a voting process
const process = await sdk.createProcess({
title: "Community Decision",
description: "Vote on our next community initiative",
census: census,
timing: {
startDate: new Date("2024-12-01T10:00:00Z"),
duration: 86400 // 24 hours in seconds
},
questions: [{
title: "Which initiative should we prioritize?",
choices: [
{ title: "Community Garden", value: 0 },
{ title: "Tech Workshop", value: 1 },
{ title: "Art Exhibition", value: 2 }
]
}]
});
// 3. Submit a vote (using one of the census participants)
const voterWallet = new Wallet('voter-private-key'); // Must be one of the census participants
const voterSdk = new DavinciSDK({
signer: voterWallet,
sequencerUrl: 'https://sequencer-dev.davinci.vote'
// No censusUrl needed for voting-only operations
});
await voterSdk.init();
const vote = await voterSdk.submitVote({
processId: process.processId,
choices: [1] // Vote for "Tech Workshop"
});
// 4. Wait for vote confirmation
const finalStatus = await voterSdk.waitForVoteStatus(
vote.processId,
vote.voteId,
VoteStatus.Settled
);
console.log('Vote confirmed!', finalStatus);- Features
- Installation
- Core Concepts
- API Reference
- Examples
- Advanced Configuration
- Error Handling
- Testing
- Contributing
- Support
- 🔒 Privacy-First: Homomorphic encryption ensures vote privacy
- 🛡️ Secure: Built on battle-tested cryptographic primitives
- ⚡ Easy Integration: Simple, intuitive API for developers
- 🌐 Decentralized: No central authority controls the voting process
- 📱 Cross-Platform: Works in browsers, Node.js, and mobile apps
- 🔧 TypeScript: Full type safety and excellent developer experience
- 🎯 Flexible: Support for multiple question types and voting modes
- Node.js 16+ or modern browser environment
- An Ethereum wallet/signer (MetaMask, WalletConnect, etc.)
# Using npm
npm install @vocdoni/davinci-sdk ethers
# Using yarn
yarn add @vocdoni/davinci-sdk ethers
# Using pnpm
pnpm add @vocdoni/davinci-sdk ethers- Process Creation: Define voting parameters, questions, and census
- Vote Submission: Voters submit encrypted, anonymous votes
- Vote Processing: Votes are verified and aggregated using zk-SNARKs
- Results: Final results are computed and made available
- Census: List of eligible voters (Merkle tree or CSP-based)
- Ballot: Vote structure defining questions and possible answers
- Process: Container for all voting parameters and metadata
- Proof: Cryptographic evidence that a vote is valid
The SDK provides simple-to-use census classes that make voter management easy. Census objects are automatically published when creating a process - no manual steps required!
Everyone gets the same voting weight (weight = 1).
import { PlainCensus } from '@vocdoni/davinci-sdk';
const census = new PlainCensus();
census.add([
'0x1234567890123456789012345678901234567890',
'0xabcdefabcdefabcdefabcdefabcdefabcdefabcd',
'0x9876543210987654321098765432109876543210'
]);
// Use directly in process creation - SDK auto-publishes!
const process = await sdk.createProcess({
census: census, // ✨ Auto-published!
// ... rest of config
});Assign different voting weights to participants. Supports flexible weight types: string, number, or bigint.
import { WeightedCensus } from '@vocdoni/davinci-sdk';
const census = new WeightedCensus();
census.add([
{ key: '0x123...', weight: "1" }, // string
{ key: '0x456...', weight: 5 }, // number
{ key: '0x789...', weight: 100n }, // bigint
]);
// Auto-published when creating process
const process = await sdk.createProcess({
census: census,
// ... rest of config
});For external authentication systems.
import { CspCensus } from '@vocdoni/davinci-sdk';
const census = new CspCensus(
"0x1234567890abcdef", // Root hash (public key)
"https://csp-server.com", // CSP URL
1000 // Expected number of voters
);
const process = await sdk.createProcess({
census: census,
// ... rest of config
});For censuses already published to the network.
import { PublishedCensus, CensusType } from '@vocdoni/davinci-sdk';
const census = new PublishedCensus(
CensusType.WEIGHTED,
"0xroot...",
"ipfs://uri...",
100 // size
);
const process = await sdk.createProcess({
census: census,
// ... rest of config
});The SDK automatically publishes unpublished censuses when creating a process:
const census = new PlainCensus();
census.add(['0x123...', '0x456...']);
console.log(census.isPublished); // false
// SDK automatically publishes during process creation
const process = await sdk.createProcess({
census: census,
// ... config
});
console.log(census.isPublished); // true ✅
console.log(census.censusRoot); // Published root hash
console.log(census.censusURI); // Published URIWeightedCensus accepts weights as strings, numbers, or bigints for maximum flexibility:
const census = new WeightedCensus();
// String weights (recommended for very large numbers)
census.add({ key: '0x123...', weight: "999999999999" });
// Number weights (easy to use, good for reasonable values)
census.add({ key: '0x456...', weight: 100 });
// BigInt weights (for JavaScript bigint support)
census.add({ key: '0x789...', weight: 1000000n });
// Mix them all!
census.add([
{ key: '0xaaa...', weight: "1" },
{ key: '0xbbb...', weight: 5 },
{ key: '0xccc...', weight: 10n }
]);const census = new WeightedCensus();
// Add single participant
census.add({ key: '0x123...', weight: 5 });
// Add multiple participants
census.add([
{ key: '0x456...', weight: 10 },
{ key: '0x789...', weight: 15 }
]);
// Remove participant
census.remove('0x123...');
// Get participant weight
const weight = census.getWeight('0x456...'); // Returns: "10"
// Get all addresses
const addresses = census.addresses; // ['0x456...', '0x789...']
// Get all participants with weights
const participants = census.participants;
// [{ key: '0x456...', weight: '10' }, { key: '0x789...', weight: '15' }]
// Check if published
if (census.isPublished) {
console.log('Root:', census.censusRoot);
console.log('URI:', census.censusURI);
console.log('Size:', census.size);
}For advanced use cases, you can still provide census data manually:
const process = await sdk.createProcess({
census: {
type: CensusOrigin.CensusOriginMerkleTree,
root: "0xabc...",
size: 100,
uri: "ipfs://..."
},
// ... rest of config
});interface DavinciSDKConfig {
signer: Signer; // Ethereum signer (required)
sequencerUrl: string; // Sequencer API URL (required)
censusUrl?: string; // Census API URL (optional, only needed for census creation)
addresses?: { // Custom contract addresses (optional)
processRegistry?: string;
organizationRegistry?: string;
stateTransitionVerifier?: string;
resultsVerifier?: string;
sequencerRegistry?: string;
};
censusProviders?: CensusProviders; // Custom census proof providers (optional)
verifyCircuitFiles?: boolean; // Verify downloaded circuit files (default: true)
verifyProof?: boolean; // Verify generated proof before submission (default: true)
}Key Points:
-
sequencerUrl(required): The Vocdoni sequencer API endpoint- Dev:
https://sequencer-dev.davinci.vote - Staging:
https://sequencer1.davinci.vote - Production: (check latest docs)
- Dev:
-
censusUrl(optional): Only required if you're creating censuses from scratch. Not needed for voting-only operations. -
Contract Addresses: If not provided, the SDK automatically fetches them from the sequencer's
/infoendpoint during initialization. This is the recommended approach.
import { DavinciSDK } from '@vocdoni/davinci-sdk';
import { Wallet } from 'ethers';
// Development environment
const sdk = new DavinciSDK({
signer: new Wallet('your-private-key'),
sequencerUrl: 'https://sequencer-dev.davinci.vote',
censusUrl: 'https://c3-dev.davinci.vote'
});
await sdk.init();Automatic Contract Address Fetching:
The SDK automatically fetches contract addresses from the sequencer during init():
const sdk = new DavinciSDK({
signer: wallet,
sequencerUrl: 'https://sequencer-dev.davinci.vote'
// Contract addresses will be fetched automatically from sequencer
});
await sdk.init(); // Fetches and stores contract addressesconst processResult = await sdk.createProcess({
title: "Election Title",
description: "Detailed description of the election",
// Census configuration
census: {
type: CensusOrigin.CensusOriginMerkleTree,
root: "0x...",
size: 1000,
uri: "ipfs://..."
},
// Timing configuration
timing: {
startDate: new Date("2024-12-01T10:00:00Z"),
duration: 86400 // 24 hours
// Alternative: endDate: new Date("2024-12-02T10:00:00Z")
},
// Ballot configuration
ballot: {
numFields: 1,
maxValue: "2",
minValue: "0",
uniqueValues: false,
costFromWeight: false,
costExponent: 1,
maxValueSum: "2",
minValueSum: "0"
},
// Questions
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 created:', processResult.processId);For applications that need to show real-time transaction progress to users, use createProcessStream():
import { TxStatus } from '@vocdoni/davinci-sdk';
const stream = sdk.createProcessStream({
title: "Election Title",
// ... same configuration as above
});
// Monitor transaction status in real-time
for await (const event of stream) {
switch (event.status) {
case TxStatus.Pending:
console.log("📝 Transaction submitted:", event.hash);
// Update UI to show pending state
break;
case TxStatus.Completed:
console.log("✅ Process created:", event.response.processId);
console.log(" Transaction:", event.response.transactionHash);
// Update UI to show success
break;
case TxStatus.Failed:
console.error("❌ Transaction failed:", event.error);
// Update UI to show error
break;
case TxStatus.Reverted:
console.error("⚠️ Transaction reverted:", event.reason);
// Update UI to show revert reason
break;
}
}When to use each method:
- Use
createProcess()for simple scripts and when you don't need transaction progress updates - Use
createProcessStream()for UI applications where users need real-time feedback during transaction processing
const processInfo = await sdk.getProcess(processId);
console.log('Title:', processInfo.title);
console.log('Status:', processInfo.status);
console.log('Start date:', processInfo.startDate);
console.log('End date:', processInfo.endDate);
console.log('Questions:', processInfo.questions);const voteResult = await sdk.submitVote({
processId: "0x...",
choices: [1, 0], // Answers for each question
randomness: "optional-custom-randomness" // Optional
});
console.log('Vote ID:', voteResult.voteId);
console.log('Status:', voteResult.status);const status = await sdk.getVoteStatus(processId, voteId);
console.log('Current status:', status.status);
// Possible statuses: pending, verified, aggregated, processed, settled, errorimport { VoteStatus } from '@vocdoni/davinci-sdk';
const finalStatus = await sdk.waitForVoteStatus(
processId,
voteId,
VoteStatus.Settled, // Target status
300000, // 5 minute timeout
5000 // Check every 5 seconds
);const hasVoted = await sdk.hasAddressVoted(processId, voterAddress);
if (hasVoted) {
console.log('This address has already voted');
}import { DavinciSDK, CensusOrigin, VoteStatus } from '@vocdoni/davinci-sdk';
import { Wallet } from 'ethers';
async function completeVotingExample() {
// 1. Initialize SDK
const organizerWallet = new Wallet('organizer-private-key');
const sdk = new DavinciSDK({
signer: organizerWallet,
sequencerUrl: 'https://sequencer-dev.davinci.vote',
censusUrl: 'https://c3-dev.davinci.vote'
});
await sdk.init();
// 2. Create census with eligible voters
const censusId = await sdk.api.census.createCensus();
// Create voter wallets and add them to census
const voters = [];
for (let i = 0; i < 5; i++) {
const voterWallet = Wallet.createRandom();
voters.push(voterWallet);
}
const participants = voters.map(voter => ({
key: voter.address,
weight: "1"
}));
await sdk.api.census.addParticipants(censusId, participants);
// Publish the census
const publishResult = await sdk.api.census.publishCensus(censusId);
const censusSize = await sdk.api.census.getCensusSize(publishResult.root);
// 3. Create voting process
const process = await sdk.createProcess({
title: "Community Budget Allocation",
description: "Decide how to allocate our community budget",
census: {
type: CensusOrigin.CensusOriginMerkleTree,
root: publishResult.root,
size: censusSize,
uri: publishResult.uri
},
timing: {
startDate: new Date(Date.now() + 60000), // Start in 1 minute
duration: 3600 // 1 hour
},
questions: [{
title: "Which project should receive funding?",
choices: [
{ title: "Community Garden", value: 0 },
{ title: "Tech Education Program", value: 1 },
{ title: "Local Art Initiative", value: 2 }
]
}]
});
console.log(`Process created: ${process.processId}`);
// 4. Vote using one of the census participants
const voterWallet = voters[0]; // Use first voter from census
const voterSdk = new DavinciSDK({
signer: voterWallet,
sequencerUrl: 'https://sequencer-dev.davinci.vote'
// No censusUrl needed for voting-only operations
});
await voterSdk.init();
// Wait for process to start accepting votes
await new Promise(resolve => setTimeout(resolve, 65000));
const vote = await voterSdk.submitVote({
processId: process.processId,
choices: [1] // Vote for Tech Education Program
});
console.log(`Vote submitted: ${vote.voteId}`);
// 5. Wait for vote confirmation
const finalStatus = await voterSdk.waitForVoteStatus(
vote.processId,
vote.voteId,
VoteStatus.Settled
);
console.log('Vote confirmed with status:', finalStatus.status);
}import { DavinciSDK } from '@vocdoni/davinci-sdk';
import { BrowserProvider } from 'ethers';
async function browserVotingExample() {
// Connect to MetaMask
if (!window.ethereum) {
throw new Error('MetaMask not found');
}
const provider = new BrowserProvider(window.ethereum);
await provider.send("eth_requestAccounts", []);
const signer = await provider.getSigner();
// Initialize SDK
const sdk = new DavinciSDK({
signer,
sequencerUrl: 'https://sequencer.davinci.vote' // Production URL
});
await sdk.init();
// Submit vote
const vote = await sdk.submitVote({
processId: "0x...",
choices: [2]
});
console.log('Vote submitted from browser:', vote.voteId);
}Click to expand advanced configuration options
const sdk = new DavinciSDK({
signer: wallet,
sequencerUrl: 'https://your-custom-sequencer.com',
censusUrl: 'https://your-custom-census.com',
addresses: {
processRegistry: '0x...',
organizationRegistry: '0x...',
stateTransitionVerifier: '0x...',
resultsVerifier: '0x...'
}
});By default, the SDK automatically fetches contract addresses from the sequencer's /info endpoint:
const sdk = new DavinciSDK({
signer: wallet,
sequencerUrl: 'https://sequencer-dev.davinci.vote'
// Contract addresses fetched automatically during init()
});
await sdk.init(); // Fetches addresses from sequencerconst vote = await sdk.submitVote({
processId: "0x...",
choices: [1],
randomness: "your-custom-randomness-hex"
});const process = await sdk.createProcess({
title: "Advanced Election",
// ... basic config
ballot: {
numFields: 3, // Number of questions
maxValue: "5", // Maximum choice value
minValue: "0", // Minimum choice value
uniqueValues: true, // Require unique choices
costFromWeight: false, // Use weight for vote cost
costExponent: 1, // Cost calculation exponent
maxValueSum: "10", // Maximum sum of all choices
minValueSum: "3" // Minimum sum of all choices
}
});// Access underlying services for advanced operations
const processRegistry = sdk.processes;
const organizationRegistry = sdk.organizations;
const apiService = sdk.api;
const crypto = await sdk.getCrypto();
// Direct API calls
const processInfo = await sdk.api.sequencer.getProcess(processId);
const censusProof = await sdk.api.census.getCensusProof(root, address);The SDK provides detailed error messages for common scenarios:
try {
const vote = await sdk.submitVote({
processId: "0x...",
choices: [1, 2, 3]
});
} catch (error) {
if (error.message.includes('already voted')) {
console.log('User has already voted in this process');
} else if (error.message.includes('not accepting votes')) {
console.log('Voting period has not started or has ended');
} else if (error.message.includes('out of range')) {
console.log('Invalid choice values provided');
} else {
console.error('Unexpected error:', error.message);
}
}- Process Errors: Process not found, not accepting votes, invalid configuration
- Vote Errors: Already voted, invalid choices, proof generation failed
- Network Errors: Connection issues, transaction failures
- Validation Errors: Invalid parameters, out-of-range values
# Run all tests
npm test
# Run unit tests only
npm run test:unit
# Run integration tests only
npm run test:integration
# Run specific test suites
npm run test:contracts
npm run test:sequencer
npm run test:censusCreate a .env file in the test directory:
SEPOLIA_RPC=https://sepolia.infura.io/v3/your-key
PRIVATE_KEY=0x...
TIME_OUT=600000We welcome contributions! Please see our Contributing Guide for details.
# Clone the repository
git clone https://github.com/vocdoni/davinci-sdk.git
cd davinci-sdk
# Install dependencies
yarn install
# Run development build
yarn dev
# Run linting
yarn lint
# Format code
yarn format- TypeScript: Full type safety
- ESLint: Code linting and style enforcement
- Prettier: Code formatting
- Jest: Comprehensive testing suite
- Husky: Pre-commit hooks
This project is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0) - see the LICENSE file for details.
The AGPL-3.0 is a copyleft license that requires anyone who distributes your code or a derivative work to make the source available under the same terms. If your application is a web service, users interacting with it remotely must also be able to access the source code.
Please report issues on our GitHub Issues page.
For enterprise support and custom integrations, contact us at [email protected].
Built with ❤️ by the Vocdoni team
Website • Documentation • GitHub • Twitter