votingClient
Initialize with
initializeFromShyConfig— see the Web SDK overview for the common setup pattern.
Read methods
await client.getAllPolls()
// → Poll[]
await client.getPoll(pollId)
// → Poll
await client.getTally(pollId)
// → Tally | null — null until poll closes
await client.getVotes(pollId)
// → VoteRecord[] (List 1 — no identity)
await client.getVoterCount(pollId)
// → number
Ballot build and submit
buildBallot(args)
Constructs a transaction envelope without submitting.
const envelope = await client.buildBallot({
pollId: 'proposal-42',
choice: 'yes', // string for plurality; string[] for approval/ranked
personId: 'didit-journey-id', // provider-specific identity input
identityInput: { ... }, // alternative to personId
proofHash: '...' // optional — pre-computed identity proof hash
})
// → { txJson, ballotId, ballotNonce, encSecret? }
txJson is a JSON-serialized TxTypeBallotCast (type 2) payload ready for submitBallot. ballotNonce is the 32-byte random hex that the caller must retain as the private receipt — it is the only value that enables post-close self-verification. ballotId = H(ballotNonce) is the direction-free public identifier.
submitBallot(txJson)
await client.submitBallot(envelope.txJson)
POSTs to {api.base_url}/ballots. Returns the server response.
castBallot(args) — combined
await client.castBallot({
pollId: 'proposal-42',
choice: 'yes',
personId: 'didit-journey-id'
})
Combines buildBallot + submitBallot + receipt persistence in one call. If a managed receipt store is configured and posture is recoverable, saves the private receipt automatically.
Receipt methods
// Offline verification against the public vote list
await client.verifyReceipt(hexNonce, expectedChoice, votes)
// → { verified: boolean, ballotId, matchedChoice }
// Read receipt from managed store
await client.getPrivateReceipt(pollId)
// → { ballotNonce, choice, pollId } | null
// Save receipt to managed store
await client.savePrivateReceipt(pollId, receipt)
// Post-close Sybil-audit signal — confirm your identity_hash appears in List 2
await client.confirmReceipt(pollId)
verifyReceipt recomputes ballotId = H(hexNonce) and scans the public votes list for a matching record. It does not contact the server — the full vote list is the only dependency.
Posture
const signals = await client.readBrowserRuntimeSignals(manifest, options?)
// → { playIntegrity, deviceAttestation, hostileNetwork, ... }
const posture = client.resolveEffectivePosture(manifest, signals)
// → "recoverable" | "write_only"
When posture resolves to "write_only", castBallot suppresses receipt persistence and returns only { ballotId } — no nonce, no choice, no linkage material retained on device.
Voting methods
| Voting method | choices format |
|---|---|
yes_no | ["yes"] or ["no"] |
plurality | ["option_id"] |
approval | ["opt_a", "opt_b"] |
ranked_choice | ["first", "second", "third"] in preference order |
Write-only posture in hostile jurisdictions
For coercion-resistant deployments, set default_posture: "coercion_resistant" and all three write_only_on_* fallbacks to true. After castBallot returns in write-only mode:
- device retains only
ballotId(publicly visible on-chain, direction-free) - no
ballotNoncestored - no
encSecretstored - a party seizing the device cannot derive vote direction
Deployment guide
Civic deployment (recoverable posture)
The standard configuration for domestic advisory referenda and civic participation.
Identity model:
- IDV provider: Didit (biometric) —
identity_hash = SHA-256(voter_pub_key ‖ poll_id) sk_vgenerated on device and discarded after signing — IDV cannot forge- Recovery: biometric re-authentication on any device; no password or seed phrase
- Receipt store: CockroachDB-backed off-chain runtime; operator read-only
Provisioning:
cd <deployment>/deploy/signing && terragrunt apply
shyware-abci \
--config shyconfig.json \
--db-path /opt/<deployment>/data \
--addr :26658
See Infrastructure provisioning for the full Terragrunt module.
Hostile-regime deployment (coercion_resistant posture)
For sovereign referenda in adversarial jurisdictions where the operator cannot be assumed neutral.
shyconfig:
{
"contract_version": "shyvoting-v1",
"deployment": {
"default_posture": "coercion_resistant",
"runtime_fallbacks": {
"write_only_on_missing_play_integrity": true,
"write_only_on_hostile_network": true,
"write_only_on_untrusted_device_attestation": true
}
}
}
Ballot-identifier export record: in write-only posture the client generates an anonymous CSV of ballot_id values — no vote direction, no identity — which the voter retains outside the hostile context for independent self-verification against the public canonical ledger. A seized device yields only the publicly visible ballot_id.
Jurisdictional gap: the off-chain receipt store is hosted in a rule-of-law jurisdiction that discloses linkage records only under a valid court order. A hostile government has no standing to obtain such an order in a non-cooperative foreign jurisdiction. The receipt store's reconcile surface is structurally inaccessible by any legal mechanism available to the hostile state.
OFAC / export classification: this architecture satisfies the anti-surveillance predicate for deployment under humanitarian personal-communications authorizations such as 31 C.F.R. § 560.540. The canonical ledger exposes no participant identity; the receipt store responds only to lawful process in jurisdictions where the sanctioned government has no standing. Export classification is supported by 15 C.F.R. § 734.7 — open protocol, no-cost distribution to end users.
Infrastructure: CometBFT multi-validator cluster, jurisdictionally distributed. Provisioning:
cd <deployment>/deploy/signing && terragrunt apply
cd <deployment>/deploy/validator && \
HETZNER_NETWORK_ID=<id> HETZNER_SSH_PUBLIC_KEY=<key> \
CLOUDFLARE_TUNNEL_TOKEN=<token> terragrunt apply