DEV Community

Cover image for Decentralized Voting App (dApp) Tutorial. Web3 and Blockchain Project.
Sourin🥑 | Web 3.0 & Blockchain🚀
Sourin🥑 | Web 3.0 & Blockchain🚀

Posted on • Edited on

Decentralized Voting App (dApp) Tutorial. Web3 and Blockchain Project.

Decentralized applications (DApps) are transforming various industries, bringing security, transparency, and trust to the world. One field where the potential of decentralized technology truly shines is in voting systems. By leveraging blockchain technology, decentralized voting application can be created that ensures the integrity of the voting process, removes the need for intermediaries, and empowers every individual with their voting rights.

In this comprehensive tutorial, we will create our own decentralized voting application from scratch. This guide will provide you with the knowledge and tools you need to get started.

Prerequisite-Knowledge: JavaScript, React.js, Solidity, Ethereum

But, do not worry if you don’t have the above mentioned knowledges. You can just follow along.

Step 1: We will create our Solidity smart contract

// SPDX-License-Identifier: MIT
pragma solidity 0.8.11;

contract Election {
    struct Candidate {
        uint256 id;
        string name;
        uint256 voteCount;
    }

    address public owner;

    mapping(uint256 => Candidate) public candidates;
    mapping(address => bool) public voters;

    uint256 public candidataCount;

    constructor() {
        owner = msg.sender;
        addCandidate("Ryle");
        addCandidate("Max");
    }

    function addCandidate(string memory _name) public{
        require(msg.sender == owner, "Only owner can add candidates");
        candidataCount++;
        candidates[candidataCount] = Candidate(candidataCount, _name, 0);
    }

    function vote(uint256 _candidateId) public {
        require(!voters[msg.sender], "You have already voted");
        require(
            _candidateId <= candidataCount && _candidateId >= 1,
            "Invalid candidate Id"
        );
        voters[msg.sender] = true;
        candidates[_candidateId].voteCount++;
    }
}

Enter fullscreen mode Exit fullscreen mode

Step 2: We will deploy our smart contract on test network. We will get ABI and Contract Address when we deploy our smart contracts.

Step 3: Now we will create our Next.js Application in our local machine by npx create-next-app my-app.

Step 4: **In the **index.js write this file

import Head from "next/head";
import Image from "next/image";
import styles from "../styles/Home.module.css";
import React, { useState, useEffect } from "react";
import { ethers } from "ethers";
import { contractABI, contractAddress } from "./Engine";
import Navbar from "./Navbar";

export default function Home() {
  const [candidatesUseState, setCandidatesUseState] = useState([]);
  const [voters, setVoters] = useState([]);
  const [account, setCurrentAccount] = useState();
  const [walletAddress, setWalletAddress] = useState("");
  const [votedOrNot, setVotedOrNot] = useState();
  const [candidateId, setCandidateId] = useState();

  const checkIfWalletIsConnected = async () => {
    try {
      const { ethereum } = window;

      if (!ethereum) {
        console.log("Please install metamask!!!");
      } else {
        // console.log("We have the ethereum object", ethereum);
      }

      const accounts = await ethereum.request({ method: "eth_accounts" });
      if (accounts.length) {
        const account = accounts[0];
        // console.log("Authorized account has found", account);
        setCurrentAccount(account);
        console.log("Connected");
      } else {
        setCurrentAccount("");
        console.log("No authorized account has found!");
      }
    } catch (error) {
      console.error(error);
    }
  };

  const connectWallet = async () => {
    try {
      const { ethereum } = window;

      if (!ethereum) {
        alert("Metamask has found!");
        return;
      }

      const accounts = await ethereum.request({
        method: "eth_requestAccounts",
      });

      // console.log("Connected", accounts[0]);
      // setCurrentAccount(accounts[0]);
    } catch (err) {
      console.error(err.message);
    }
  };

  const getCandidates = async (candidateId) => {
    const provider = new ethers.providers.Web3Provider(window.ethereum);

    const connection = new ethers.Contract(
      contractAddress,
      contractABI,
      provider
    );

    const candidatesCount = Number(await connection.candidataCount());
    console.log(candidatesCount);

    for (var i = 1; i <= candidatesCount; i++) {
      const candidate = await connection.candidates(i);

      const id = candidate[0];
      const name = candidate[1];
      const voteCount = candidate[2];

      const item = {
        id: Number(id),
        name: name.toString(),
        voteCount: voteCount.toNumber(),
      };
      setCandidatesUseState((prev) => [...prev, item]);
    }
    // console.log(candidatesUseState);
  };

  const checkVotingStatus = async (voter) => {
    try {
      const provider = new ethers.providers.Web3Provider(window.ethereum);

      const connection = new ethers.Contract(
        contractAddress,
        contractABI,
        provider
      );

      const hasVoted = await connection.voters(voter);
      console.log(voter, "hasVoted: ", hasVoted);
      setVotedOrNot(hasVoted);
    } catch (error) {
      console.error(error);
    }
  };

  const changeHandler = (e) => {
    setWalletAddress(e.target.value);
  };

  const handleButtonClick = async () => {
    await checkVotingStatus(walletAddress);
  };

  const vote = async (candidateId) => {
    try {
      const provider = new ethers.providers.Web3Provider(window.ethereum);
      const signer = await provider.getSigner();

      const connection = new ethers.Contract(
        contractAddress,
        contractABI,
        signer
      );

      const vote = await connection.vote(candidateId);
    } catch (error) {
      console.error(error);
    }
  };

  const handleChange2 = (e) => {
    setCandidateId(e.target.value);
  };

  const buttonClick2 = () => {
    vote(candidateId);
  };

  useEffect(() => {
    checkIfWalletIsConnected();
    connectWallet();
    getCandidates();
  }, []);

  return (
    <div className="">
      <Head>
        <title>Voting Dapp ~ heysourin</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/icon.png" />
      </Head>

      <main className="">
        <Navbar account={account} connectWallet={connectWallet} />

        <h1 className="font-bold text-3xl m-5">Candidates:</h1>
        <div className="flex flex-row">
          <table className="w-full border-collapse mx-10">
            <thead>
              <tr>
                <th className="py-2 px-4 border">Candidate Id</th>
                <th className="py-2 px-4 border">Candidate Name</th>
                <th className="py-2 px-4 border">Vote Count</th>
              </tr>
            </thead>
            <tbody>
              {candidatesUseState.map((candidate, i) => (
                <tr key={i}>
                  <td className="py-2 px-4 border">{candidate.id}</td>
                  <td className="py-2 px-4 border">{candidate.name}</td>
                  <td className="py-2 px-4 border">{candidate.voteCount}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>

        {/* @note CHECK IF VOTED*/}
        <div>
          <h2 className="font-bold text-3xl mt-10 ml-5">Check Voted or Not:</h2>
          <div className="flex flex-row mx-10">
            <input
              type="text"
              className="border border-gray-300 px-4 m-4"
              placeholder="Enter wallet address"
              value={walletAddress || ""}
              onChange={changeHandler}
            />
            <button
              onClick={handleButtonClick}
              className="bg-gradient-to-r from-orange-600 to-yellow-500 text-white font-bold py-2 px-4 rounded m-4"
            >
              Check if voted
            </button>
            <div className="mt-6">
              {votedOrNot ? (
                <p>You have already voted, can not vote anymore!</p>
              ) : (
                <p>You have not voted yet!</p>
              )}
            </div>
          </div>
        </div>

        <div>
          <h2 className="font-bold text-3xl mt-8 ml-5">Vote: </h2>
          <div className="flex flex-row mx-10">
            <input
              type="text"
              className=" w-24 mx-3 my-4 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
              pattern="\d{0,2}"
              maxLength="2"
              placeholder="Candidate Id"
              value={candidateId || ""}
              onChange={handleChange2}
              required
            />
            <button
              onClick={buttonClick2}
              className="bg-gradient-to-r from-purple-700 to-blue-500 text-white font-bold py-2 px-4 rounded m-4"
            >
              Vote
            </button>
            <div className="mt-6"></div>
          </div>
        </div>
      </main>

      <footer className="flex items-center justify-between bg-gradient-to-r from-gray-900 to-gray-700 p-6 text-white">
        <a
          href="https://github.com/heysourin"
          target="_blank"
          className="text-left"
        >
          My Github
        </a>
        <a
          href="https://linkedin.com/in/heysourin"
          target="_blank"
          className="text-left"
        >
          My LinkenIn
        </a>
        <span className="text-right">
          <a
            href="https://github.com/heysourin/Voting-dApp-on-Avalanche-Network"
            className="text-white"
          >
            Source Code
          </a>
        </span>
      </footer>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

The above file has Navbar component import. Create a file Navbar.jsx in the pages folder. Code:

import { React } from 'react'

const Navbar = ({ account, connectWallet }) => {
  return (
    <nav className="flex items-center justify-between flex-wrap bg-gradient-to-r from-gray-900 to-gray-700 p-6">
      <div className="flex items-center flex-shrink-0 text-white mr-6">
        <span className="font-bold text-3xl tracking-tight">
          Web3 Voting DAPP
        </span>
      </div>
      <div className="flex">
        {account ? (
          <button
            type="button"
            className='className="bg-gradient-to-r from-blue-500 to-blue-700 text-white py-2 px-4 rounded-m font-bold'
          >
            {account.slice(0, 6) + '...' + account.slice(38, 42)}
          </button>
        ) : (
          <button
            type="button"
            className='className="bg-blue-500 bg-blue-700 text-white font-bold py-2 px-4 rounded"'
            onClick={connectWallet}
          >
            Connect Wallet
          </button>
        )}
      </div>
    </nav>
  )
}

export default Navbar

Enter fullscreen mode Exit fullscreen mode

Now you are good to go :)

GitHub repo to refer.

If you need any help, feel free to connect.

Top comments (0)