Voting is a community poll dApp built on a simple, strict premise: Rails is the view layer, Solana is the database. No vote is ever stored in the Rails DB. Polls, candidates, and tallies are all PDA accounts on-chain; Rails reads them, renders them, and prepares unsigned instructions — but the chain is the single source of truth.
It was built for Solana Bootcamp 2026 and has its own deep-dive writeup: Voting on Solana, built with Rails →. This page is the short version.
What it does
- A single curated poll: “Which dApp should we showcase next on solrengine.org?”
- All poll data, candidates, and votes live on-chain as PDA accounts
- Rails stores only users (wallet address + SIWS nonce)
- On-chain enforcement of voting windows (start/end timestamps)
- A JSON API so headless or agent-driven clients can vote too
The “Solana is the database” architecture
- Zero off-chain vote storage. Every count is read from chain via a single
getMultipleAccountsround-trip per page render — 5 RPC calls collapsed into 1. - YAML + chain split.
config/candidates.ymlholds display metadata; the on-chain PDAs hold the real data. Avoting:verifyrake task checks they still agree. - The signer is never trusted from the client. Rails always uses
current_user.wallet_addressfrom the SIWS session, never a wallet address sent by the browser. - End-to-end page load is ~170 ms on devnet, thanks to the batched reads, HTTP keep-alive in
solrengine-rpc, and a 20-second blockhash cache.
How a vote happens
1. POST /poll/vote with a candidate name
2. Rails builds a vote instruction — solrengine-programs derives both PDAs from the IDL seed specs
3. It fetches a fresh blockhash (20s cache) and returns JSON:
{ program_id, accounts, instruction_data (base64), blockhash, last_valid_block_height }
4. The browser signs via @solrengine/wallet-utils and sends to a devnet RPC
5. A Stimulus state machine polls /poll/confirm/:signature until confirmed
6. The validator has already incremented the candidate's on-chain vote count
Server-side, prepare_vote validates the candidate against the YAML allow-list,
enforces a 32-byte cap, and checks the poll is open — so the unsigned instruction Rails
hands back is always well-formed and authorized.
The SolRengine gems behind it
- auth — SIWS sign-in, nonce-bound and rate-limited
- rpc — batched
getMultipleAccountsreads + cached blockhashes - programs — PDA derivation and Borsh-encoded
voteinstructions from the IDL - tokens — supporting price/format helpers
It also uses solrengine-ui for the interface components.