├── .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 |
150 |
151 |
152 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 | | Index |
166 | Candidate name |
167 | Candidate votes |
168 |
169 |
170 |
171 |
172 |
173 |
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 | }
--------------------------------------------------------------------------------