Skip to Content
SDKCreate Round

Create Voting Round

Use the SDK to create AMACI voting rounds through the Registry contract.

Minimal Example

import { MaciClient, MaciCircuitType } from '@dorafactory/maci-sdk'; const client = new MaciClient({ network: 'testnet' }); // Query available operators const operators = await client.indexer.getOperators(); const operator = operators.find(op => op.status === 'Active'); // Create voting round const round = await client.createAMaciRound({ signer: wallet, operator: operator.address, startVoting: new Date(), endVoting: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days later title: 'Community Proposal Vote', voteOptionMap: ['Option A', 'Option B', 'Option C'], circuitType: MaciCircuitType.QV, maxVoter: 100, // Voice credit allocation: everyone gets 100 credits voiceCreditMode: { unified: { amount: '100' } }, // Registration: static whitelist registrationMode: { sign_up_with_static_whitelist: { whitelist: { users: [ { addr: 'dora1...' }, { addr: 'dora1...' } ] } } }, deactivateEnabled: false }); console.log('Contract address:', round.contractAddress);

Core Parameters

ParameterTypeDescription
signerWalletSigning wallet (round creator)
operatorstringOperator address
startVotingDateVoting start time
endVotingDateVoting end time
titlestringVote title
voteOptionMapstring[]Voting option labels
circuitTypeMaciCircuitTypeQV (quadratic) or IP1V (one person one vote)
maxVoternumberMaximum voter count
voiceCreditModeVoiceCreditModeHow voting power is allocated (see below)
registrationModeRegistrationModeConfigRegistration method and access control (see below)
deactivateEnabledbooleanEnable the deactivate → AddNewKey flow (default false)

VoiceCreditMode

Defines how voting power (voice credits) is allocated to participants:

Unified Mode

All users receive the same fixed amount of voice credits:

voiceCreditMode: { unified: { amount: '100' } }

Dynamic Mode

Each user’s voice credits equal the amount embedded in their oracle-signed certificate. Used together with SignUpWithOracle registration mode:

voiceCreditMode: 'dynamic'

RegistrationModeConfig

A unified enum that controls who can register and how. This replaces the old separate whitelist, voiceCreditAmount, oracle_whitelist_pubkey, preDeactivateRoot, and preDeactivateCoordinator fields.

SignUpWithStaticWhitelist

Users call SignUp directly; only addresses in the pre-defined list are accepted:

registrationMode: { sign_up_with_static_whitelist: { whitelist: { users: [ { addr: 'dora1abc...' }, { addr: 'dora1def...' }, { addr: 'dora1ghi...' } ] } } }

Each entry in users also accepts an optional voice_credit_amount field, which overrides the global voiceCreditMode for that specific user. This allows per-user differentiated voting power within a static whitelist:

registrationMode: { sign_up_with_static_whitelist: { whitelist: { users: [ { addr: 'dora1abc...', voice_credit_amount: '200' }, // 200 credits { addr: 'dora1def...', voice_credit_amount: '100' }, // 100 credits { addr: 'dora1ghi...' } // falls back to voiceCreditMode ] } } }

SignUpWithOracle

Users call SignUp with a backend-signed certificate. No fixed address list needed:

registrationMode: { sign_up_with_oracle: { oracle_pubkey: 'ORACLE_PUBLIC_KEY_HEX' } } // Users sign up with certificate: await client.signup({ signer: wallet, contractAddress: amaciAddress, maciKeypair, certificate: oracleCertificate, amount: '150' // required when voiceCreditMode is Dynamic });

PrePopulated

Users must register via PreAddNewKey using a ZK proof. Direct SignUp is disabled. Used for fully anonymous voting where participants receive pre-generated keypairs:

registrationMode: { pre_populated: { pre_deactivate_root: preDeactivateData.root, pre_deactivate_coordinator: { x: platformCoordPubkey[0].toString(), y: platformCoordPubkey[1].toString() } } }

Round Type Matrix

The three parameters — registrationMode, voiceCreditMode, and deactivateEnabled — are independent switches that combine to define the complete behavior of an AMACI round. Understanding how they interact lets you choose the right configuration for your use case.

Configuration Reference Table

Round TyperegistrationModevoiceCreditModedeactivateEnabledTypical Use Case
Standard WhitelistSignUpWithStaticWhitelistUnifiedfalseDAO / token-gated vote with a fixed member list and equal voting power
Whitelist + Key RotationSignUpWithStaticWhitelistUnifiedtrueWhitelist round where members can later anonymize their identity via AddNewKey
Oracle-Gated, Equal CreditsSignUpWithOracleUnifiedfalseOpen registration verified by backend; all voters get the same credits
Oracle-Gated, Variable CreditsSignUpWithOracleDynamicfalseToken/stake-weighted voting; each voter’s credits come from the oracle certificate
Pre-keyed AnonymousPrePopulatedUnifiedfalseFully anonymous; platform pre-generates and distributes keypairs, users register with ZK proof

How deactivateEnabled Works

deactivateEnabled is an optional privacy upgrade that can be combined with SignUpWithStaticWhitelist or SignUpWithOracle. It unlocks a two-step key-rotation flow:

User signs up (visible on-chain) → User submits DeactivateMessage (10 DORA fee) → Operator processes deactivate batch → User calls AddNewKey with ZK proof (new identity, unlinked from original)

With this flow the operator sees the new identity registered but cannot link it back to the original address, providing on-chain anonymity even for whitelist-registered users.

// Round where members can later switch to an anonymous identity await client.createAMaciRound({ registrationMode: { sign_up_with_static_whitelist: { whitelist: { users: members.map(addr => ({ addr })) } } }, voiceCreditMode: { unified: { amount: '100' } }, deactivateEnabled: true, // enables the deactivate → AddNewKey flow // ... });

PrePopulated vs deactivateEnabled: true

Both achieve identity anonymization, but at different times:

  • PrePopulated — anonymization happens before the round starts: the platform pre-builds a deactivate tree and distributes keypairs, so users register anonymously from day one.
  • deactivateEnabled: true — anonymization happens during the round: users first sign up (identity visible), then rotate to a new keypair using a ZK proof.

Combining PrePopulated with deactivateEnabled: true is technically possible but rarely needed.

Valid Parameter Combinations

registrationModevoiceCreditMode: 'dynamic' allowed?deactivateEnabled: true useful?
SignUpWithStaticWhitelistNo (credits are fixed per user in whitelist)Yes
SignUpWithOracleYes (credits embedded in certificate)Yes
PrePopulatedNo (ZK-based entry, no oracle)Rarely needed

Voting Modes

Quadratic Voting (QV)

Votes squared equals credit cost, encouraging distributed voting power:

circuitType: MaciCircuitType.QV // User has 100 voice credits // 10 votes costs 100 credits (10²) // 5 votes for option A + 5 for option B = 50 credits

One Person One Vote (1P1V)

Votes directly equal credit consumption:

circuitType: MaciCircuitType.IP1V // User has 100 voice credits // 100 votes costs 100 credits

PrePopulated (Anonymous) Mode

Create a fully anonymous round by pre-generating keypairs and distributing them to users:

import { OperatorClient, genKeypair } from '@dorafactory/maci-sdk'; const operatorClient = new OperatorClient({ network: 'testnet' }); // 1. Generate keypairs for all participants const keypairs = Array.from({ length: 100 }, () => genKeypair()); // 2. Build the pre-deactivate tree const preDeactivateData = await operatorClient.buildPreDeactivateTree({ coordinatorKeypair: platformCoordinatorKeypair, userKeypairs: keypairs }); // 3. Create round in PrePopulated mode const round = await client.createAMaciRound({ signer: wallet, operator: selectedOperator.address, title: 'Anonymous Vote', voteOptionMap: ['Option A', 'Option B'], circuitType: MaciCircuitType.QV, maxVoter: 100, voiceCreditMode: { unified: { amount: '100' } }, registrationMode: { pre_populated: { pre_deactivate_root: preDeactivateData.root, pre_deactivate_coordinator: { x: platformCoordinatorKeypair.pubKey[0].toString(), y: platformCoordinatorKeypair.pubKey[1].toString() } } }, deactivateEnabled: false, startVoting: new Date(), endVoting: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) }); // 4. Distribute keypairs to users via a secure channel // Each user receives: { privateKey, publicKey, preDeactivateData }

Users then register anonymously without revealing their real identity.

Query Operators

// Get all operators const operators = await client.indexer.getOperators(); // Filter active ones const active = operators.filter(op => op.status === 'Active'); // View operator info console.log('Identity:', active[0].identity); console.log('Address:', active[0].address); console.log('Pubkey:', active[0].pubkey);

Operator list can also be viewed at https://vota.dorafactory.org/operators .

Fees

AMACI rounds involve three distinct fee types with different payers:

1. Round Creation Fee — paid by the round creator

A one-time fee paid when calling createAMaciRound. It covers the operator’s full processing cost for the entire round (message processing, ZK proof generation, tally, and gas). The amount is automatically calculated based on maxVoter and circuit type.

// Query the fee before creating const fee = await client.queryAMaciChargeFee({ maxVoter: 1000, maxOption: 10 }); console.log('Creation fee:', fee); // Fee is automatically included when creating the round const round = await client.createAMaciRound({ signer: wallet, /* ... */ });

2. Vote Message Fee — paid by each voter

Every voter pays 10 DORA per PublishMessage call. Since AMACI batches multiple vote messages into a single call, this is a per-call fee (not per-message).

// The SDK handles this fee automatically when calling vote() await client.vote({ signer: wallet, contractAddress, selectedOptions: [{ idx: 0, vc: 8 }], // ... }); // 10 DORA deducted from voter's wallet

3. Deactivate Message Fee — paid by the user, only when deactivateEnabled: true

When a user submits a PublishDeactivateMessage to rotate their identity, they pay 10 DORA per call. This fee only applies to rounds created with deactivateEnabled: true.

// Only relevant when deactivateEnabled: true await client.deactivate({ signer: wallet, contractAddress, maciKeypair }); // 10 DORA deducted from user's wallet

Fee Summary

FeeAmountPaid byWhen
Round creationVaries (based on maxVoter + circuit)Round creatorAt createAMaciRound
Vote message10 DORA / PublishMessage callVoterEach time vote() is called
Deactivate message10 DORA / PublishDeactivateMessage callUserEach time deactivate() is called (only if deactivateEnabled: true)

Use Case Examples

DAO Governance Voting

await client.createAMaciRound({ title: 'Protocol Upgrade Proposal #42', voteOptionMap: ['For', 'Against', 'Abstain'], circuitType: MaciCircuitType.IP1V, maxVoter: 500, voiceCreditMode: { unified: { amount: '1' } }, registrationMode: { sign_up_with_static_whitelist: { whitelist: { users: daoMembers.map(addr => ({ addr })) } } }, deactivateEnabled: false, // ...other params });

Quadratic Funding

await client.createAMaciRound({ title: 'Q1 2025 Public Goods Funding', voteOptionMap: [ 'Project A: Open Source Tools', 'Project B: Educational Content', 'Project C: Community Events' ], circuitType: MaciCircuitType.QV, maxVoter: 1000, voiceCreditMode: { unified: { amount: '1000' } }, registrationMode: { sign_up_with_static_whitelist: { whitelist: { users: eligibleVoters.map(addr => ({ addr })) } } }, deactivateEnabled: false, // ...other params });

Anonymous Oracle-Gated Vote

await client.createAMaciRound({ title: 'Product Feature Priority Survey', voteOptionMap: ['Feature A', 'Feature B', 'Feature C', 'Feature D'], circuitType: MaciCircuitType.QV, maxVoter: 1000, voiceCreditMode: 'dynamic', // Voice credits come from oracle certificate registrationMode: { sign_up_with_oracle: { oracle_pubkey: ORACLE_PUBKEY_HEX } }, deactivateEnabled: false, // ...other params });

FAQ

How to modify voting configuration?

Once created, configuration cannot be modified. Before voting starts and while num_signups == 0, you can call UpdateRegistrationConfig to update voiceCreditMode, registrationMode, or deactivateEnabled. After that, create a new round if changes are needed.

Can I add more whitelist addresses?

No. The whitelist is fixed at creation via SignUpWithStaticWhitelist. Use SignUpWithOracle instead if you need flexible access control.

Can voting period be extended?

No. Voting start and end times are set at creation and cannot be changed.

Where to get Operator address?

Query via client.indexer.getOperators(), or visit https://vota.dorafactory.org/operators .

Next Steps

Voting Process - Check Voting Guide to learn how users vote.

Query Data - Refer to Query API to learn how to query voting information.

Complete Examples - View Basic Examples for end-to-end process.

Last updated on