Skip to main content

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 methodchoices 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 ballotNonce stored
  • no encSecret stored
  • 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_v generated 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