The Blockchain-Based Voting System is a secure and transparent approach to conducting elections using blockchain technology. This project aims to ensure the integrity, security, and transparency of the voting process by leveraging the decentralized nature of blockchain networks.
- Security: Utilizes cryptographic techniques and decentralization to prevent tampering and ensure the integrity of the voting process.
- Transparency: All voting transactions are recorded on the blockchain, providing an auditable and transparent trail of every vote cast.
- Immutability: Once a vote is recorded on the blockchain, it becomes immutable and cannot be altered, ensuring the reliability of the voting data.
- Accessibility: Enables voters to securely cast their votes from anywhere with an internet connection, promoting inclusivity and accessibility.
- Anonymity: Protects the identity of voters while ensuring their votes are accurately recorded and counted, preserving privacy.
- Ubuntu install in your machine.
- Node.js (version 18 and above) installed on your machine
- Hardhat for Ethereum smart contract development
- Account on Alchemy or Infura for API
- Metamask account and their browser extension installed on your browser
Web3 wallet::Web3 wallets are a key component of the joing decentralized internet, as they provide users with the tools and capabilities to take control of their digital identities, assets, and participation in the evolving Web3 ecosystem
- A crypto wallet has three main components: Public Key: This links to an address where you can send and receive transactions.
- Private Key: Must be kept secret and undisclosed. This is used for signing new transactions and enables access to funds.
- Seed Phrase: Used to generate multiple private keys. Act as a root key, giving access to the rest of the keys and addresses in a user’s wallet. This can also create new private keys.
-
Hot Wallets are generally referred to as software wallets because they are hosted on devices that have access to the internet and cryptocurrency network. They are more convenient than other types of wallets due to their ability to store, send, receive, and view tokens. Hot wallets are considered the highest in utility when it comes to Web3 wallets.
-
Cold wallets tend to be a safer alternative to store cryptocurrencies because of no connection to the internet. This is because there is a physical medium to store the keys offline. This method makes cold wallets increasingly resistant to hackers, which is known as cold storage. Metamask::MetaMask is a web3 crypto wallet that allows users to store and swap cryptocurrencies, interact with the Ethereum blockchain ecosystem, and host a growing array of decentralized applications (dApps) Sepolia::The Sepolia testnet is a Proof-of-Stake (PoS) testnet that developers can use to deploy and test their smart contracts for free. RPC provider::RPC stand for Remote Procedure Call.An RPC allows you to communicate and interact with that blockchain. The graph::The Graph is an indexing protocol for organizing and accessing data from blockchains and storage networks. It allows developers to search, find, publish, and use the public data they need to build decentralized applications.
- MetaMask
- Coinbase Wallet
- Trust Wallet
- Zerion
- ZenGo
- Argent
- Rainbow
- Exodus
- Phantom
- OKX Wallet
- Alchemy
- Syndica
- Ankr
- Quicknode
- GenesysGO
- Infura
- Lava Network
- GetBlock
- Blast
- RockX
- Testnet: Sepolia
- Web3 API: Alchemy
- Frontend: ReactJS.
- Backend: Solidity
- Unit Testing: chai
- web Interface: ether.js
- Create a new repository
mkdir voting-dapp && cd voting-dapp
- Then initialize an npm project and fill required details.
npm init
- Create a file package.json and paste following code.
nano package.json
{
"name": "blockchain-based-voting-system",
"version": "0.1.0",
"private": true,
"dependencies": {
"@nomicfoundation/hardhat-toolbox": "^2.0.2",
"@nomiclabs/hardhat-ethers": "^2.2.2",
"dotenv": "^16.0.3",
"hardhat": "^2.13.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"ethers": "^5.7.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4",
"@babel/plugin-proposal-private-property-in-object": "^7.21.11"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
- Now we can install Hardhat:
npm install --save-dev hardhat
- Then initialize an hardhat project,select create a JavaScript project and fill other details.
npx hardhat init
- Now edit hardhat.config.js file with following lines.
nano hardhat.config.js
Use this line if you have alchemy API.
require("@nomicfoundation/hardhat-toolbox");
// Ensure your configuration variables are set before executing the script
const { vars } = require("hardhat/config");
// Go to https://alchemy.com, sign up, create a new App in
// its dashboard, and add its key to the configuration variables
const ALCHEMY_API_KEY = vars.get("ALCHEMY_API_KEY");
// Add your Sepolia account private key to the configuration variables
// To export your private key from Coinbase Wallet, go to
// Settings > Developer Settings > Show private key
// To export your private key from Metamask, open Metamask and
// go to Account Details > Export Private Key
// Beware: NEVER put real Ether into testing accounts
const SEPOLIA_PRIVATE_KEY = vars.get("SEPOLIA_PRIVATE_KEY");
module.exports = {
solidity: "0.8.11",
networks: {
sepolia: {
url: `https://eth-sepolia.g.alchemy.com/v2/${ALCHEMY_API_KEY}`,
accounts: [SEPOLIA_PRIVATE_KEY]
}
}
};
- Set all value of variable.
- Go to contracts folder and edit filename(lock.sol to Voting.sol) and paste following code.
cd contracts
Voting.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Voting {
struct Candidate {
string name;
uint256 voteCount;
}
Candidate[] public candidates;
address owner;
mapping(address => bool) public voters;
uint256 public votingStart;
uint256 public votingEnd;
constructor(string[] memory _candidateNames, uint256 _durationInMinutes) {
for (uint256 i = 0; i < _candidateNames.length; i++) {
candidates.push(Candidate({
name: _candidateNames[i],
voteCount: 0
}));
}
owner = msg.sender;
votingStart = block.timestamp;
votingEnd = block.timestamp + (_durationInMinutes * 1 minutes);
}
modifier onlyOwner {
require(msg.sender == owner);
_;
}
function addCandidate(string memory _name) public onlyOwner {
candidates.push(Candidate({
name: _name,
voteCount: 0
}));
}
function vote(uint256 _candidateIndex) public {
require(!voters[msg.sender], "You have already voted.");
require(_candidateIndex < candidates.length, "Invalid candidate index.");
candidates[_candidateIndex].voteCount++;
voters[msg.sender] = true;
}
function getAllVotesOfCandiates() public view returns (Candidate[] memory){
return candidates;
}
function getVotingStatus() public view returns (bool) {
return (block.timestamp >= votingStart && block.timestamp < votingEnd);
}
function getRemainingTime() public view returns (uint256) {
require(block.timestamp >= votingStart, "Voting has not started yet.");
if (block.timestamp >= votingEnd) {
return 0;
}
return votingEnd - block.timestamp;
}
}
- Then compile the project.
npx hardhat compile
- After compile your project you have to check error in your project.For that go inside the test folder and edit the code in lock.js file.
const {
time,
loadFixture,
} = require("@nomicfoundation/hardhat-toolbox/network-helpers");
const { anyValue } = require("@nomicfoundation/hardhat-chai-matchers/withArgs");
const { expect } = require("chai");
describe("Lock", function () {
// We define a fixture to reuse the same setup in every test.
// We use loadFixture to run this setup once, snapshot that state,
// and reset Hardhat Network to that snapshot in every test.
async function deployOneYearLockFixture() {
const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60;
const ONE_GWEI = 1_000_000_000;
const lockedAmount = ONE_GWEI;
const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS;
// Contracts are deployed using the first signer/account by default
const [owner, otherAccount] = await ethers.getSigners();
const Lock = await ethers.getContractFactory("Lock");
const lock = await Lock.deploy(unlockTime, { value: lockedAmount });
return { lock, unlockTime, lockedAmount, owner, otherAccount };
}
describe("Deployment", function () {
it("Should set the right unlockTime", async function () {
const { lock, unlockTime } = await loadFixture(deployOneYearLockFixture);
expect(await lock.unlockTime()).to.equal(unlockTime);
});
it("Should set the right owner", async function () {
const { lock, owner } = await loadFixture(deployOneYearLockFixture);
expect(await lock.owner()).to.equal(owner.address);
});
it("Should receive and store the funds to lock", async function () {
const { lock, lockedAmount } = await loadFixture(
deployOneYearLockFixture
);
expect(await ethers.provider.getBalance(lock.target)).to.equal(
lockedAmount
);
});
it("Should fail if the unlockTime is not in the future", async function () {
// We don't use the fixture here because we want a different deployment
const latestTime = await time.latest();
const Lock = await ethers.getContractFactory("Lock");
await expect(Lock.deploy(latestTime, { value: 1 })).to.be.revertedWith(
"Unlock time should be in the future"
);
});
});
describe("Withdrawals", function () {
describe("Validations", function () {
it("Should revert with the right error if called too soon", async function () {
const { lock } = await loadFixture(deployOneYearLockFixture);
await expect(lock.withdraw()).to.be.revertedWith(
"You can't withdraw yet"
);
});
it("Should revert with the right error if called from another account", async function () {
const { lock, unlockTime, otherAccount } = await loadFixture(
deployOneYearLockFixture
);
// We can increase the time in Hardhat Network
await time.increaseTo(unlockTime);
// We use lock.connect() to send a transaction from another account
await expect(lock.connect(otherAccount).withdraw()).to.be.revertedWith(
"You aren't the owner"
);
});
it("Shouldn't fail if the unlockTime has arrived and the owner calls it", async function () {
const { lock, unlockTime } = await loadFixture(
deployOneYearLockFixture
);
// Transactions are sent using the first signer by default
await time.increaseTo(unlockTime);
await expect(lock.withdraw()).not.to.be.reverted;
});
});
describe("Events", function () {
it("Should emit an event on withdrawals", async function () {
const { lock, unlockTime, lockedAmount } = await loadFixture(
deployOneYearLockFixture
);
await time.increaseTo(unlockTime);
await expect(lock.withdraw())
.to.emit(lock, "Withdrawal")
.withArgs(lockedAmount, anyValue); // We accept any value as `when` arg
});
});
describe("Transfers", function () {
it("Should transfer the funds to the owner", async function () {
const { lock, unlockTime, lockedAmount, owner } = await loadFixture(
deployOneYearLockFixture
);
await time.increaseTo(unlockTime);
await expect(lock.withdraw()).to.changeEtherBalances(
[owner, lock],
[lockedAmount, -lockedAmount]
);
});
});
});
});
- Then run test cmd for test.
npx hardhat test
- Create a folder(scripts) and in that create a file(depoly.js).Also add this line in file.
mkdir scripts && cd scripts
nano depoly.js
async function main() {
const Voting = await ethers.getContractFactory("Voting");
// Start deployment, returning a promise that resolves to a contract object
const Voting_ = await Voting.deploy(["Ratnesh Kumar", "Rajnish", "Chandan", "Pushpendra"], 90);
console.log("Contract address:", Voting_.address);
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});
- After that deploy you project with following cmd you will get a contract address paste it in your .env file.
npx hardhat run --network sepolia scripts/deploy.js
- Now we create a react app using following cmd:
npx create-react-app voting-system
- Now start your server to see your depolyment with following cmd:
npm start
if every thing is alright you will see this
- Now install ethers.js library for you project.
npm install [email protected]
- Now stop your server with ctrl + c and move src and public folder from voting-system to your root folder.
- Also edit app.css,app.js with this lines.
App.css
.login-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
background-color: #f1f1f1;
}
.welcome-message {
font-size: 2rem;
text-align: center;
margin-bottom: 2rem;
}
.login-button {
background-color: #007bff;
color: #fff;
font-size: 1.2rem;
border: none;
border-radius: 0.3rem;
padding: 1rem 2rem;
cursor: pointer;
transition: background-color 0.3s ease;
}
.login-button:hover {
background-color: #0069d9;
}
.connected-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
background-color: #f1f1f1;
}
.connected-header {
font-size: 2rem;
text-align: center;
margin-bottom: 2rem;
}
.connected-account {
font-size: 1.2rem;
text-align: center;
margin-bottom: 2rem;
}
.connected-button {
background-color: #007bff;
color: #fff;
font-size: 1.2rem;
border: none;
border-radius: 0.3rem;
padding: 1rem 2rem;
cursor: pointer;
transition: background-color 0.3s ease;
margin-bottom: 2rem;
}
.connected-button:hover {
background-color: #0069d9;
}
.connected-contract-address {
font-size: 1.2rem;
text-align: center;
}
.candidates-table {
border-collapse: collapse;
width: 50%;
text-align:center;
}
.candidates-table th, .candidates-table td {
padding: 8px;
text-align: left;
border-bottom: 1px solid #ddd;
}
.candidates-table th {
background-color: #f2f2f2;
}
input[type=number] {
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
font-size: 16px;
margin-bottom: 10px;
}
App.js
import { useState, useEffect } from 'react';
import {ethers} from 'ethers';
import {contractAbi, contractAddress} from './Constant/constant';
import Login from './Components/Login';
import Finished from './Components/Finished';
import Connected from './Components/Connected';
import './App.css';
function App() {
const [provider, setProvider] = useState(null);
const [account, setAccount] = useState(null);
const [isConnected, setIsConnected] = useState(false);
const [votingStatus, setVotingStatus] = useState(true);
const [remainingTime, setremainingTime] = useState('');
const [candidates, setCandidates] = useState([]);
const [number, setNumber] = useState('');
const [CanVote, setCanVote] = useState(true);
useEffect( () => {
getCandidates();
getRemainingTime();
getCurrentStatus();
if (window.ethereum) {
window.ethereum.on('accountsChanged', handleAccountsChanged);
}
return() => {
if (window.ethereum) {
window.ethereum.removeListener('accountsChanged', handleAccountsChanged);
}
}
});
async function vote() {
const provider = new ethers.providers.Web3Provider(window.ethereum);
await provider.send("eth_requestAccounts", []);
const signer = provider.getSigner();
const contractInstance = new ethers.Contract (
contractAddress, contractAbi, signer
);
const tx = await contractInstance.vote(number);
await tx.wait();
canVote();
}
async function canVote() {
const provider = new ethers.providers.Web3Provider(window.ethereum);
await provider.send("eth_requestAccounts", []);
const signer = provider.getSigner();
const contractInstance = new ethers.Contract (
contractAddress, contractAbi, signer
);
const voteStatus = await contractInstance.voters(await signer.getAddress());
setCanVote(voteStatus);
}
async function getCandidates() {
const provider = new ethers.providers.Web3Provider(window.ethereum);
await provider.send("eth_requestAccounts", []);
const signer = provider.getSigner();
const contractInstance = new ethers.Contract (
contractAddress, contractAbi, signer
);
const candidatesList = await contractInstance.getAllVotesOfCandiates();
const formattedCandidates = candidatesList.map((candidate, index) => {
return {
index: index,
name: candidate.name,
voteCount: candidate.voteCount.toNumber()
}
});
setCandidates(formattedCandidates);
}
async function getCurrentStatus() {
const provider = new ethers.providers.Web3Provider(window.ethereum);
await provider.send("eth_requestAccounts", []);
const signer = provider.getSigner();
const contractInstance = new ethers.Contract (
contractAddress, contractAbi, signer
);
const status = await contractInstance.getVotingStatus();
console.log(status);
setVotingStatus(status);
}
async function getRemainingTime() {
const provider = new ethers.providers.Web3Provider(window.ethereum);
await provider.send("eth_requestAccounts", []);
const signer = provider.getSigner();
const contractInstance = new ethers.Contract (
contractAddress, contractAbi, signer
);
const time = await contractInstance.getRemainingTime();
setremainingTime(parseInt(time, 16));
}
function handleAccountsChanged(accounts) {
if (accounts.length > 0 && account !== accounts[0]) {
setAccount(accounts[0]);
canVote();
} else {
setIsConnected(false);
setAccount(null);
}
}
async function connectToMetamask() {
if (window.ethereum) {
try {
const provider = new ethers.providers.Web3Provider(window.ethereum);
setProvider(provider);
await provider.send("eth_requestAccounts", []);
const signer = provider.getSigner();
const address = await signer.getAddress();
setAccount(address);
console.log("Metamask Connected : " + address);
setIsConnected(true);
canVote();
} catch (err) {
console.error(err);
}
} else {
console.error("Metamask is not detected in the browser")
}
}
async function handleNumberChange(e) {
setNumber(e.target.value);
}
return (
<div className="App">
{ votingStatus ? (isConnected ? (<Connected
account = {account}
candidates = {candidates}
remainingTime = {remainingTime}
number= {number}
handleNumberChange = {handleNumberChange}
voteFunction = {vote}
showButton = {CanVote}/>)
:
(<Login connectWallet = {connectToMetamask}/>)) : (<Finished />)}
</div>
);
}
export default App;
- Create a new folder(Components) in src folder and add this files. Connected.jsx
import React from "react";
const Connected = (props) => {
return (
<div className="connected-container">
<h1 className="connected-header">You are Connected to Metamask</h1>
<p className="connected-account">Metamask Account: {props.account}</p>
<p className="connected-account">Remaining Time: {props.remainingTime}</p>
{ props.showButton ? (
<p className="connected-account">You have already voted</p>
) : (
<div>
<input type="number" placeholder="Entern Candidate Index" value={props.number} onChange={props.handleNumberChange}></input>
<br />
<button className="login-button" onClick={props.voteFunction}>Vote</button>
</div>
)}
<table id="myTable" className="candidates-table">
<thead>
<tr>
<th>Index</th>
<th>Candidate name</th>
<th>Candidate votes</th>
</tr>
</thead>
<tbody>
{props.candidates.map((candidate, index) => (
<tr key={index}>
<td>{candidate.index}</td>
<td>{candidate.name}</td>
<td>{candidate.voteCount}</td>
</tr>
))}
</tbody>
</table>
</div>
)
}
export default Connected;
Finished.jsx
import React from "react";
const Finished = (props) => {
return (
<div className="login-container">
<h1 className="welcome-message">Voting is Finished</h1>
</div>
)
}
export default Finished;
Login.jsx
import React from "react";
const Login = (props) => {
return (
<div className="login-container">
<h1 className="welcome-message">Welcome to decentralized voting application</h1>
<button className="login-button" onClick = {props.connectWallet}>Login Metamask</button>
</div>
)
}
export default Login;
- Create a new folder(Constant) in src folder and add constant.js file.
const contractAddress = "your contract address";
const contractAbi = your abi ;
//This abi is in your artigacts/contracts/Voting.sol/Voting.json file
export {contractAbi, contractAddress};
- Your project is done.Its time to run your project.
- Before run your project you have to bulid your project .
npm run build
- Now start your server and see your output in your local host(10.0.2.15:3000)
npm install -g serve && serve -s build
- Clone this project repository.
git clone https://github.com/16ratneshkumar/blockchain-voting-system.git
- Navigate to the project directory:
cd blockchain-voting-system
- Now we can install Hardhat:
npm install --save-dev hardhat
- Now create .env file with this details.
npx hardhat vars set ALCHEMY_API_KEY <value>
npx hardhat vars set SEPOLIA_PRIVATE_KEY <value>
- Then compile the smart contract
npx hardhat compile
- After that deploy you project with following cmd you will get a contract address paste it in your .env file.
npx hardhat run --network sepolia scripts/deploy.js
- go to src/Constant folder and edit constant.js file with your Contract Address and Contract Abi.
const contractAddress = "your contract address";
const contractAbi = your abi ;
//This abi is in your artigacts/contracts/Voting.sol/Voting.json file
export {contractAbi, contractAddress};
- Your project is done.Its time to run your project.
- Before run your project you have to bulid your project .
npm run build
- Now start your server and see your output in your local host(10.0.2.15:3000)
npm install -g serve && serve -s build
- Install and configure Metamask on your browser.
- Connect Metamask to the Ethereum network and authorize the application to interact with your account.
- Access the online interface to securely cast your vote during the specified voting period.
- Each vote is encrypted and recorded on the blockchain, ensuring its immutability and integrity.
- After each vote, the system transparently counts and tallies the votes, producing election results that can be publicly verified.
Thank You