quickstart
Create a poll, cast a ballot, run a tally. No account. No chain transaction.
1. create a poll
Visit /create. Enter:
- your Bitcoin address (the creator)
- the question and at least two options
- a deadline (UTC)
- a weight mode (
sats,sats_days, orone_per_address) - thresholds:
min_satsandmin_daysthat a voter must clear for their ballot to count
Click sign & publish. Your wallet prompts once for a BIP-322 signature over
the poll's content-addressed id. The client publishes to Nostr kind 30080 across
four default relays. You land on the poll page at /p/<poll_id>.
2. cast a ballot
Anyone with a Bitcoin address can vote. Open the poll URL. Pick an option. Click
sign & publish ballot. Your wallet prompts once. The client publishes to
Nostr kind 30081 (replaceable per voter per poll — you can change your vote
until deadline if the poll's tiebreak is latest).
3. verify the tally
The poll page recomputes the tally live against the current UTXO snapshot from mempool.space. To verify independently:
curl https://vote.ochk.io/api/tally?poll=<poll_id>
You get a JSON object — the same shape any conforming implementation produces.
The web UI is convenience; the curl output is the authoritative result.
Disputes are resolvable.
the library
npm i @orangecheck/vote-core
import { pollId, tally } from '@orangecheck/vote-core';
const result = await tally({
poll,
ballots,
utxosAt: (addr, snapshot) => fetchMyUtxos(addr, snapshot),
});
Pure function. Same inputs → byte-identical output.
what you'll need
- a Bitcoin wallet that can sign BIP-322 messages: UniSat, Xverse, Leather, OKX, or Phantom. Or paste a signature from any offline signer.
- a Bitcoin address with some UTXOs if you want your ballot to carry weight. The threshold the poll sets is the bar.
Nothing else. No signup. No KYC. No sats move.