├── .gitignore ├── scripts └── deploy.js ├── hardhat.config.js ├── package.json ├── README.md ├── index.js ├── contracts └── Voting.sol ├── index.html ├── ListVoters.html ├── test └── Lock.js └── main.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | coverage 4 | coverage.json 5 | typechain 6 | typechain-types 7 | 8 | # Hardhat files 9 | cache 10 | artifacts 11 | 12 | -------------------------------------------------------------------------------- /scripts/deploy.js: -------------------------------------------------------------------------------- 1 | async function main() { 2 | const Voting = await ethers.getContractFactory("Voting"); 3 | 4 | // Start deployment, returning a promise that resolves to a contract object 5 | const Voting_ = await Voting.deploy(["Mark", "Mike", "Henry", "Rock"], 10); 6 | console.log("Contract address:", Voting_.address); 7 | 8 | 9 | } 10 | 11 | main() 12 | .then(() => process.exit(0)) 13 | .catch(error => { 14 | console.error(error); 15 | process.exit(1); 16 | }); -------------------------------------------------------------------------------- /hardhat.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @type import('hardhat/config').HardhatUserConfig 3 | */ 4 | 5 | require('dotenv').config(); 6 | require("@nomiclabs/hardhat-ethers"); 7 | 8 | const { API_URL, PRIVATE_KEY } = process.env; 9 | 10 | module.exports = { 11 | solidity: "0.8.11", 12 | defaultNetwork: "volta", 13 | networks: { 14 | hardhat: {}, 15 | volta: { 16 | url: API_URL, 17 | accounts: [`0x${PRIVATE_KEY}`], 18 | gas: 210000000, 19 | gasPrice: 800000000000, 20 | } 21 | }, 22 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "voting", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@nomicfoundation/hardhat-toolbox": "^2.0.2", 13 | "@nomiclabs/hardhat-ethers": "^2.2.2", 14 | "dotenv": "^16.0.3", 15 | "ethers": "^5.7.1", 16 | "express": "^4.18.2", 17 | "express-fileupload": "^1.4.0", 18 | "hardhat": "^2.13.0", 19 | "path": "^0.12.7" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Decentralized voting application on Ethereum Blockchain 2 | 3 | 4 | To run the code, you need to run the following commands. 5 | 6 | ```shell 7 | npm install 8 | ``` 9 | 10 | You first need to compile the contract and upload it to the blockchain network. Run the following commands to compile and upload the contract. 11 | 12 | 13 | ```shell 14 | npx hardhat compile 15 | npx hardhat run --network volta scripts/deploy.js 16 | ``` 17 | 18 | Once the contract is uploaded to the blockchain, copy the contract address and copy it in the .env file. 19 | You can also use another blockchain by writing the blockchain's endpoint in hardhat-config. 20 | 21 | Once you have pasted your private key and contract address in the .env file, simply run command 22 | 23 | ```shell 24 | node index.js 25 | ``` 26 | 27 | and go to http://localhost:3000 to interact with the decentralized voting application. 28 | 29 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const express = require('express'); 3 | const app = express(); 4 | const fileUpload = require('express-fileupload'); 5 | app.use( 6 | fileUpload({ 7 | extended:true 8 | }) 9 | ) 10 | app.use(express.static(__dirname)); 11 | app.use(express.json()); 12 | const path = require("path"); 13 | const ethers = require('ethers'); 14 | 15 | var port = 3000; 16 | 17 | const API_URL = process.env.API_URL; 18 | const PRIVATE_KEY = process.env.PRIVATE_KEY; 19 | const CONTRACT_ADDRESS = process.env.CONTRACT_ADDRESS; 20 | 21 | const {abi} = require('./artifacts/contracts/Voting.sol/Voting.json'); 22 | const provider = new ethers.providers.JsonRpcProvider(API_URL); 23 | 24 | const signer = new ethers.Wallet(PRIVATE_KEY, provider); 25 | 26 | const contractInstance = new ethers.Contract(CONTRACT_ADDRESS, abi, signer); 27 | 28 | app.get("/", (req, res) => { 29 | res.sendFile(path.join(__dirname, "index.html")); 30 | }) 31 | 32 | app.get("/index.html", (req, res) => { 33 | res.sendFile(path.join(__dirname, "index.html")); 34 | }) 35 | 36 | app.post("/vote", async (req, res) => { 37 | var vote = req.body.vote; 38 | console.log(vote) 39 | async function storeDataInBlockchain(vote) { 40 | console.log("Adding the candidate in voting contract..."); 41 | const tx = await contractInstance.addCandidate(vote); 42 | await tx.wait(); 43 | } 44 | const bool = await contractInstance.getVotingStatus(); 45 | if (bool == true) { 46 | await storeDataInBlockchain(vote); 47 | res.send("The candidate has been registered in the smart contract"); 48 | } 49 | else { 50 | res.send("Voting is finished"); 51 | } 52 | }); 53 | 54 | app.listen(port, function () { 55 | console.log("App is listening on port 3000") 56 | }); -------------------------------------------------------------------------------- /contracts/Voting.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | contract Voting { 5 | struct Candidate { 6 | string name; 7 | uint256 voteCount; 8 | } 9 | 10 | Candidate[] public candidates; 11 | address owner; 12 | mapping(address => bool) public voters; 13 | 14 | uint256 public votingStart; 15 | uint256 public votingEnd; 16 | 17 | constructor(string[] memory _candidateNames, uint256 _durationInMinutes) { 18 | for (uint256 i = 0; i < _candidateNames.length; i++) { 19 | candidates.push(Candidate({ 20 | name: _candidateNames[i], 21 | voteCount: 0 22 | })); 23 | } 24 | owner = msg.sender; 25 | votingStart = block.timestamp; 26 | votingEnd = block.timestamp + (_durationInMinutes * 1 minutes); 27 | } 28 | 29 | modifier onlyOwner { 30 | require(msg.sender == owner); 31 | _; 32 | } 33 | 34 | function addCandidate(string memory _name) public onlyOwner { 35 | candidates.push(Candidate({ 36 | name: _name, 37 | voteCount: 0 38 | })); 39 | } 40 | 41 | function vote(uint256 _candidateIndex) public { 42 | require(!voters[msg.sender], "You have already voted."); 43 | require(_candidateIndex < candidates.length, "Invalid candidate index."); 44 | 45 | candidates[_candidateIndex].voteCount++; 46 | voters[msg.sender] = true; 47 | } 48 | 49 | function getAllVotesOfCandiates() public view returns (Candidate[] memory){ 50 | return candidates; 51 | } 52 | 53 | function getVotingStatus() public view returns (bool) { 54 | return (block.timestamp >= votingStart && block.timestamp < votingEnd); 55 | } 56 | 57 | function getRemainingTime() public view returns (uint256) { 58 | require(block.timestamp >= votingStart, "Voting has not started yet."); 59 | if (block.timestamp >= votingEnd) { 60 | return 0; 61 | } 62 | return votingEnd - block.timestamp; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | To Do Decentralized Application 5 | 6 | 8 | Voting DAPP 9 | 132 | 133 | 134 | 135 | 139 | 140 |
Welcome to the Decentralized Voting Application
141 | 142 |
143 | 144 |

145 |
146 | 147 |
148 | Vote here 149 | 150 | 151 |

152 | 153 | 154 |

155 |

156 |
157 | 158 | -------------------------------------------------------------------------------- /ListVoters.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | To Do Decentralized Application 5 | 6 | 8 | Centered Form 9 | 131 | 132 | 133 | 134 | 138 | 139 |
140 | 141 |

142 |
143 | 144 |
145 |
146 | Add candidate here 147 | 148 | 149 |
150 |
151 | 152 |
153 |

154 |
155 | 156 |
157 | 158 |

159 |
160 | 161 |
162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 |
IndexCandidate nameCandidate votes
174 |
175 | 176 | 177 | -------------------------------------------------------------------------------- /test/Lock.js: -------------------------------------------------------------------------------- 1 | const { 2 | time, 3 | loadFixture, 4 | } = require("@nomicfoundation/hardhat-network-helpers"); 5 | const { anyValue } = require("@nomicfoundation/hardhat-chai-matchers/withArgs"); 6 | const { expect } = require("chai"); 7 | 8 | describe("Lock", function () { 9 | // We define a fixture to reuse the same setup in every test. 10 | // We use loadFixture to run this setup once, snapshot that state, 11 | // and reset Hardhat Network to that snapshot in every test. 12 | async function deployOneYearLockFixture() { 13 | const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60; 14 | const ONE_GWEI = 1_000_000_000; 15 | 16 | const lockedAmount = ONE_GWEI; 17 | const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS; 18 | 19 | // Contracts are deployed using the first signer/account by default 20 | const [owner, otherAccount] = await ethers.getSigners(); 21 | 22 | const Lock = await ethers.getContractFactory("Lock"); 23 | const lock = await Lock.deploy(unlockTime, { value: lockedAmount }); 24 | 25 | return { lock, unlockTime, lockedAmount, owner, otherAccount }; 26 | } 27 | 28 | describe("Deployment", function () { 29 | it("Should set the right unlockTime", async function () { 30 | const { lock, unlockTime } = await loadFixture(deployOneYearLockFixture); 31 | 32 | expect(await lock.unlockTime()).to.equal(unlockTime); 33 | }); 34 | 35 | it("Should set the right owner", async function () { 36 | const { lock, owner } = await loadFixture(deployOneYearLockFixture); 37 | 38 | expect(await lock.owner()).to.equal(owner.address); 39 | }); 40 | 41 | it("Should receive and store the funds to lock", async function () { 42 | const { lock, lockedAmount } = await loadFixture( 43 | deployOneYearLockFixture 44 | ); 45 | 46 | expect(await ethers.provider.getBalance(lock.address)).to.equal( 47 | lockedAmount 48 | ); 49 | }); 50 | 51 | it("Should fail if the unlockTime is not in the future", async function () { 52 | // We don't use the fixture here because we want a different deployment 53 | const latestTime = await time.latest(); 54 | const Lock = await ethers.getContractFactory("Lock"); 55 | await expect(Lock.deploy(latestTime, { value: 1 })).to.be.revertedWith( 56 | "Unlock time should be in the future" 57 | ); 58 | }); 59 | }); 60 | 61 | describe("Withdrawals", function () { 62 | describe("Validations", function () { 63 | it("Should revert with the right error if called too soon", async function () { 64 | const { lock } = await loadFixture(deployOneYearLockFixture); 65 | 66 | await expect(lock.withdraw()).to.be.revertedWith( 67 | "You can't withdraw yet" 68 | ); 69 | }); 70 | 71 | it("Should revert with the right error if called from another account", async function () { 72 | const { lock, unlockTime, otherAccount } = await loadFixture( 73 | deployOneYearLockFixture 74 | ); 75 | 76 | // We can increase the time in Hardhat Network 77 | await time.increaseTo(unlockTime); 78 | 79 | // We use lock.connect() to send a transaction from another account 80 | await expect(lock.connect(otherAccount).withdraw()).to.be.revertedWith( 81 | "You aren't the owner" 82 | ); 83 | }); 84 | 85 | it("Shouldn't fail if the unlockTime has arrived and the owner calls it", async function () { 86 | const { lock, unlockTime } = await loadFixture( 87 | deployOneYearLockFixture 88 | ); 89 | 90 | // Transactions are sent using the first signer by default 91 | await time.increaseTo(unlockTime); 92 | 93 | await expect(lock.withdraw()).not.to.be.reverted; 94 | }); 95 | }); 96 | 97 | describe("Events", function () { 98 | it("Should emit an event on withdrawals", async function () { 99 | const { lock, unlockTime, lockedAmount } = await loadFixture( 100 | deployOneYearLockFixture 101 | ); 102 | 103 | await time.increaseTo(unlockTime); 104 | 105 | await expect(lock.withdraw()) 106 | .to.emit(lock, "Withdrawal") 107 | .withArgs(lockedAmount, anyValue); // We accept any value as `when` arg 108 | }); 109 | }); 110 | 111 | describe("Transfers", function () { 112 | it("Should transfer the funds to the owner", async function () { 113 | const { lock, unlockTime, lockedAmount, owner } = await loadFixture( 114 | deployOneYearLockFixture 115 | ); 116 | 117 | await time.increaseTo(unlockTime); 118 | 119 | await expect(lock.withdraw()).to.changeEtherBalances( 120 | [owner, lock], 121 | [lockedAmount, -lockedAmount] 122 | ); 123 | }); 124 | }); 125 | }); 126 | }); 127 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | let WALLET_CONNECTED = ""; 2 | let contractAddress = "0x1ccB0A73939F3d224f3a7e5EdB85415dF90b04E8"; 3 | let contractAbi = [ 4 | { 5 | "inputs": [ 6 | { 7 | "internalType": "string[]", 8 | "name": "_candidateNames", 9 | "type": "string[]" 10 | }, 11 | { 12 | "internalType": "uint256", 13 | "name": "_durationInMinutes", 14 | "type": "uint256" 15 | } 16 | ], 17 | "stateMutability": "nonpayable", 18 | "type": "constructor" 19 | }, 20 | { 21 | "inputs": [ 22 | { 23 | "internalType": "string", 24 | "name": "_name", 25 | "type": "string" 26 | } 27 | ], 28 | "name": "addCandidate", 29 | "outputs": [], 30 | "stateMutability": "nonpayable", 31 | "type": "function" 32 | }, 33 | { 34 | "inputs": [ 35 | { 36 | "internalType": "uint256", 37 | "name": "", 38 | "type": "uint256" 39 | } 40 | ], 41 | "name": "candidates", 42 | "outputs": [ 43 | { 44 | "internalType": "string", 45 | "name": "name", 46 | "type": "string" 47 | }, 48 | { 49 | "internalType": "uint256", 50 | "name": "voteCount", 51 | "type": "uint256" 52 | } 53 | ], 54 | "stateMutability": "view", 55 | "type": "function" 56 | }, 57 | { 58 | "inputs": [], 59 | "name": "getAllVotesOfCandiates", 60 | "outputs": [ 61 | { 62 | "components": [ 63 | { 64 | "internalType": "string", 65 | "name": "name", 66 | "type": "string" 67 | }, 68 | { 69 | "internalType": "uint256", 70 | "name": "voteCount", 71 | "type": "uint256" 72 | } 73 | ], 74 | "internalType": "struct Voting.Candidate[]", 75 | "name": "", 76 | "type": "tuple[]" 77 | } 78 | ], 79 | "stateMutability": "view", 80 | "type": "function" 81 | }, 82 | { 83 | "inputs": [], 84 | "name": "getRemainingTime", 85 | "outputs": [ 86 | { 87 | "internalType": "uint256", 88 | "name": "", 89 | "type": "uint256" 90 | } 91 | ], 92 | "stateMutability": "view", 93 | "type": "function" 94 | }, 95 | { 96 | "inputs": [ 97 | { 98 | "internalType": "uint256", 99 | "name": "_candidateIndex", 100 | "type": "uint256" 101 | } 102 | ], 103 | "name": "getVotesOfCandiate", 104 | "outputs": [ 105 | { 106 | "internalType": "uint256", 107 | "name": "", 108 | "type": "uint256" 109 | } 110 | ], 111 | "stateMutability": "view", 112 | "type": "function" 113 | }, 114 | { 115 | "inputs": [], 116 | "name": "getVotingStatus", 117 | "outputs": [ 118 | { 119 | "internalType": "bool", 120 | "name": "", 121 | "type": "bool" 122 | } 123 | ], 124 | "stateMutability": "view", 125 | "type": "function" 126 | }, 127 | { 128 | "inputs": [ 129 | { 130 | "internalType": "uint256", 131 | "name": "_candidateIndex", 132 | "type": "uint256" 133 | } 134 | ], 135 | "name": "vote", 136 | "outputs": [], 137 | "stateMutability": "nonpayable", 138 | "type": "function" 139 | }, 140 | { 141 | "inputs": [ 142 | { 143 | "internalType": "address", 144 | "name": "", 145 | "type": "address" 146 | } 147 | ], 148 | "name": "voters", 149 | "outputs": [ 150 | { 151 | "internalType": "bool", 152 | "name": "", 153 | "type": "bool" 154 | } 155 | ], 156 | "stateMutability": "view", 157 | "type": "function" 158 | }, 159 | { 160 | "inputs": [], 161 | "name": "votingEnd", 162 | "outputs": [ 163 | { 164 | "internalType": "uint256", 165 | "name": "", 166 | "type": "uint256" 167 | } 168 | ], 169 | "stateMutability": "view", 170 | "type": "function" 171 | }, 172 | { 173 | "inputs": [], 174 | "name": "votingStart", 175 | "outputs": [ 176 | { 177 | "internalType": "uint256", 178 | "name": "", 179 | "type": "uint256" 180 | } 181 | ], 182 | "stateMutability": "view", 183 | "type": "function" 184 | } 185 | ]; 186 | 187 | const connectMetamask = async() => { 188 | const provider = new ethers.providers.Web3Provider(window.ethereum); 189 | await provider.send("eth_requestAccounts", []); 190 | const signer = provider.getSigner(); 191 | WALLET_CONNECTED = await signer.getAddress(); 192 | var element = document.getElementById("metamasknotification"); 193 | element.innerHTML = "Metamask is connected " + WALLET_CONNECTED; 194 | } 195 | 196 | const addVote = async() => { 197 | if(WALLET_CONNECTED != 0) { 198 | var name = document.getElementById("vote"); 199 | const provider = new ethers.providers.Web3Provider(window.ethereum); 200 | await provider.send("eth_requestAccounts", []); 201 | const signer = provider.getSigner(); 202 | const contractInstance = new ethers.Contract(contractAddress, contractAbi, signer); 203 | var cand = document.getElementById("cand"); 204 | cand.innerHTML = "Please wait, adding a vote in the smart contract"; 205 | const tx = await contractInstance.vote(name.value); 206 | await tx.wait(); 207 | cand.innerHTML = "Vote added !!!"; 208 | } 209 | else { 210 | var cand = document.getElementById("cand"); 211 | cand.innerHTML = "Please connect metamask first"; 212 | } 213 | } 214 | 215 | const voteStatus = async() => { 216 | if(WALLET_CONNECTED != 0) { 217 | var status = document.getElementById("status"); 218 | var remainingTime = document.getElementById("time"); 219 | const provider = new ethers.providers.Web3Provider(window.ethereum); 220 | await provider.send("eth_requestAccounts", []); 221 | const signer = provider.getSigner(); 222 | const contractInstance = new ethers.Contract(contractAddress, contractAbi, signer); 223 | const currentStatus = await contractInstance.getVotingStatus(); 224 | const time = await contractInstance.getRemainingTime(); 225 | console.log(time); 226 | status.innerHTML = currentStatus == 1 ? "Voting is currently open" : "Voting is finished"; 227 | remainingTime.innerHTML = `Remaining time is ${parseInt(time, 16)} seconds`; 228 | } 229 | else { 230 | var status = document.getElementById("status"); 231 | status.innerHTML = "Please connect metamask first"; 232 | } 233 | } 234 | 235 | const getAllCandidates = async() => { 236 | if(WALLET_CONNECTED != 0) { 237 | var p3 = document.getElementById("p3"); 238 | const provider = new ethers.providers.Web3Provider(window.ethereum); 239 | await provider.send("eth_requestAccounts", []); 240 | const signer = provider.getSigner(); 241 | const contractInstance = new ethers.Contract(contractAddress, contractAbi, signer); 242 | p3.innerHTML = "Please wait, getting all the candidates from the voting smart contract"; 243 | var candidates = await contractInstance.getAllVotesOfCandiates(); 244 | console.log(candidates); 245 | var table = document.getElementById("myTable"); 246 | 247 | for (let i = 0; i < candidates.length; i++) { 248 | var row = table.insertRow(); 249 | var idCell = row.insertCell(); 250 | var descCell = row.insertCell(); 251 | var statusCell = row.insertCell(); 252 | 253 | idCell.innerHTML = i; 254 | descCell.innerHTML = candidates[i].name; 255 | statusCell.innerHTML = candidates[i].voteCount; 256 | } 257 | 258 | p3.innerHTML = "The tasks are updated" 259 | } 260 | else { 261 | var p3 = document.getElementById("p3"); 262 | p3.innerHTML = "Please connect metamask first"; 263 | } 264 | } --------------------------------------------------------------------------------