Syed Ahmed Ali ShahSoftware developer · Pakistan
Live

On-Chain Voting dApp

Contract
Solidity · Hardhat
Frontend
React · ethers.js

Overview

Solidity (Hardhat) voting contract with a React + ethers.js frontend styled like an official paper ballot.

Problem

Most blockchain voting demos look like crypto dashboards, which is exactly wrong for something meant to feel trustworthy to someone who has never touched a wallet. The goal was a ballot that felt like an official paper ballot, with the Web3 machinery handled quietly in the background.

Architecture

A minimal Solidity voting contract, deliberately kept small so the interesting work could happen in the frontend. Hardhat handles local contract testing and deployment scripting; ethers.js bridges contract calls to a React UI styled like an official paper ballot rather than a typical Web3 dashboard. Reads and writes are deliberately separated: poll data is fetched through a public RPC node, while only the vote transaction itself requires a connected wallet.

Technical Decisions

  • Keeping the contract minimal on purpose — the design goal was UX and trust, not contract complexity.
  • Enforcing one-vote-per-address inside the contract rather than the frontend. The contract keeps a mapping(address => bool) public hasVoted, and the first line of vote() is require(!hasVoted[msg.sender], "You have already voted"). A frontend check can always be bypassed; an on-chain check cannot — someone calling the contract directly through Etherscan hits exactly the same wall.
  • Hiding vote counts and result bars until after you have voted, or until voting closes. This is a design decision rather than a technical one: people should vote on their own judgment rather than being nudged by seeing the current leader first.
  • Routing reads through a public RPC node instead of the wallet provider, so the ballot loads for anyone — including visitors with no wallet installed at all.

Challenges

The hard part was never the visual design — it was managing state across three unreliable sources of truth at once: the blockchain (slow and asynchronous), the wallet (can be rejected, on the wrong network, or simply absent), and React's own UI state.

Confirming a vote needed two-stage messaging. The Cast Vote button stays disabled until a candidate is selected, then shows "Confirm in your wallet…" the moment the transaction is submitted, followed by "Waiting for the transaction to be mined…" once it is approved in MetaMask. There is a real gap of several seconds between approving a transaction and it becoming final, and without that distinction the UI just looks frozen.

Rejected signatures needed handling too. Cancelling the MetaMask popup makes ethers.js throw, so the app catches that and surfaces it as a plain-language status message rather than a silent failure or a raw error dump.

The bug that took longest to spot was subtler: reading poll data originally required a wallet to be present at all. The candidate list simply never loaded for anyone browsing without MetaMask — on mobile Safari, for instance, the ballot appeared empty with no error. The fix was separating concerns properly: reads through a public RPC node, wallet required only for the vote transaction.

What I Learned

Putting the double-vote check on-chain is the solid part of this design, but it is worth being honest about what it actually guarantees: one vote per wallet address, not one vote per person. Someone with two wallets can vote twice. Closing that gap needs off-chain identity verification, which is a substantially harder problem than this project set out to solve — and arguably the real unsolved problem in blockchain voting generally.

The broader lesson was that in a dApp, most of the engineering difficulty lives in the gap between user intent and on-chain finality, not in the contract. This project is the direct descendant of my final-year university project — same core idea of votes as immutable on-chain records — with a much cleaner execution once I was not also learning Solidity for the first time while building it.

Links

Related Reading & Listening

Need something similar?

Building or debugging a Solidity project?

Tell me about the contract or the problem you're running into.

View Solidity & Foundry

Download resume

Enter the access key I shared with you.

Don't have a key? Request my resume.