Build a Web3 Voting App: Step-by-Step Guide 🚀

Web3 can seem confusing at first, but it’s not here to replace Web2—it’s about enhancing what’s possible. In this guide, I’ll show you how to create a simple Web3 voting application, step-by-step. By the end, you’ll understand how decentralized apps (dApps) work, how smart contracts replace traditional backends, and how to connect everything to a user-friendly frontend.

Let’s get started!

Overview of What We’ll Build

We’re creating a policy voting app where:

  • Users can propose policies and vote on them.

  • Votes are recorded on the blockchain—making everything transparent and secure.

  • Only the admin can implement policies after they receive enough votes.

Key features include:

  • Metamask Integration to connect a wallet.

  • Smart contracts written in Solidity.

  • Frontend built with Next.js and Jotai (for state management).

Step 1: Setting Up the Development Environment

1.1 Install Metamask

Metamask is a crypto wallet that we’ll use to connect to the blockchain and send transactions.

  • Install the Metamask extension from your browser’s web store.

  • Create a new account or log in to an existing one.

1.2 Connect to the Polygon Testnet

To avoid using real money, we’ll use the Polygon zkEVM Cardona Testnet.

  1. Go to Polygon Testnet and connect it to Metamask.

  2. Add free test tokens by visiting the Polygon Faucet. Connect your Discord, copy your wallet address from Metamask, and paste it into the faucet.

Note: If the faucet fails the first time, try again after a few minutes.

Step 2: Writing and Deploying the Smart Contract

We’ll use Remix, an online IDE for writing Solidity smart contracts.

2.1 Create and Configure the Contract

  1. Open Remix and create a new file named PolicyGovernance.sol.

  2. Set the Solidity version at the top:

pragma solidity ^0.8.0;

  1. Define the contract:

contract PolicyGovernance {
address public admin;

struct Policy {
string title;
string description;
string category;
uint upvotes;
uint downvotes;
bool implemented;
uint createdAt;
}

mapping(uint => Policy) public policies;
mapping(address => bool) public hasVoted;
uint public totalPolicies;
}

  1. Add functionality:

    • Propose Policies

    • Vote on Policies

    • Implement Policies (admin only)

Full smart contract code available in the GitHub Repository.

2.2 Compile and Deploy the Contract

  1. Compile the contract by selecting Solidity Compiler in Remix.

  2. Under Deploy, connect to Metamask and select the Polygon Testnet.

  3. Deploy the contract and save the following:

    • Contract Address

    • ABI (from Remix > Artifacts folder)

Step 3: Building the Frontend with Next.js

We’ll use Next.js for the frontend and Jotai for state management. Make sure you’re familiar with these tools before proceeding.

3.1 Connecting to the Smart Contract

In the root layout file:

  1. Import Web3 and the contract JSON (ABI):

import Web3 from 'web3';
import ContractABI from '../artifacts/PolicyGovernance.json';

  1. Initialize Web3 and load the contract:

const web3 = new Web3(window.ethereum);
const contract = new web3.eth.Contract(ContractABI.abi, CONTRACT_ADDRESS);

  1. Add global state for accounts, contract, and admin status:

const [account, setAccount] = useState('');
const [isAdmin, setIsAdmin] = useState(false);

useEffect(() => {
const init = async () => {
const accounts = await web3.eth.requestAccounts();
setAccount(accounts[0]);

const admin = await contract.methods.admin().call();
setIsAdmin(admin === accounts[0]);
};
init();
}, []);

3.2 Displaying Policies and Voting

  • Fetch all policies from the contract using getTotalPolicies and getPolicy.

  • Map policies to a PolicyCard component:

const handleVote = async (policyId, isUpvote) => {
await contract.methods.voteOnPolicy(policyId, isUpvote).send({ from: account });
};

  • Check if the user has already voted and disable the vote button accordingly.

3.3 Proposing Policies

Use a form to submit new policies:

const proposePolicy = async (data) => {
await contract.methods.proposePolicy(data.title, data.description, data.category).send({
from: account,
});
};

3.4 Implementing Policies (Admin Only)

The admin can implement policies:

const implementPolicy = async (policyId) => {
await contract.methods.implementPolicy(policyId).send({ from: account });
};

Step 4: Adding Event Listeners

We’ll listen to smart contract events to update the frontend in real time:

contract.events.PolicyProposed().on('data', (event) => {
// Append the new policy to state
});

contract.events.PolicyVoted().on('data', (event) => {
// Update votes for the policy
});

contract.events.PolicyImplemented().on('data', (event) => {
// Mark the policy as implemented
});

Final Result

Once the app is complete, you can:

  1. Connect your Metamask wallet.

  2. Propose new policies with a title, description, and category.

  3. Vote on policies (one vote per user).

  4. Admin can implement policies after sufficient votes.

The app ensures transparency, immutability, and security using blockchain technology.

Conclusion

By building this simple Web3 voting app, you’ve learned how to:

  • Write and deploy smart contracts using Solidity and Remix.

  • Connect a blockchain backend to a Next.js frontend.

  • Integrate Metamask for seamless user interactions.

Web3 opens up exciting new possibilities—this is just the beginning. Experiment further and see how you can expand this project!

Full source code: GitHub Repository.

Thank you for reading, and happy coding!

Support

Thank you for reading! If you enjoyed this post and want to support my work, consider supporting me by subscribing to my newsletter or sharing this post with a friend.