- Setup Environment
- Smart Contract
- Deployment Of Smart Contract (Ethereum)
- Deployment Of Smart Contract (Ganache)
- Integration With Web Application
- Programmers Tips
Node Js is an open-source, cross-platform, JavaScript runtime environment that executes JavaScript code outside of a web browser.
Developers could use JavaScript to write command line tools and for server-side scripting—running scripts server-side to produce dynamic web page content before the page is sent to the user's web browser.
-
node -v
-
Verify your node installation on Command Prompt
Git is a version control system designed to handle everything from small to very large projects with speed and efficiency.
-
git --version
-
Verify your git installation on Command Prompt or Terminal on Visual Studio Code
Download Visual Studio Code
Visual Studio Code for me is a universal, all in one code editor/IDE that allow developer to build, debug, test, control version, and etc.
Now, for starters open your terminal in Visual Studio Code. It's on View -> terminal
However, my method is just use your keyboard. ctrl + shift + backtick
you can find backtick on the left side of your 1 on your keyboard.
Now before we proceed with Truffle Suite Installation, I suggest you to create new folder to store everything within it.
It's a good practice to have so you could find your project seamlessly and less messy environment.
For Example:
Now, if let's say i want to create a new project i will:
- Open Terminal
- Create new folder by typing
mkdir nameOfYourFolder - Enter that folder by typing
cd nameOfYourFolder - Open and display it on my VSCode by typing
code .
Once you type code . and press enter, it'll popup a new windows.
This newly windows are the Display of your project within nameOfYourFolder.
Everything you build right now will be within this project.
If you're not implementing this practice, it may be a hassle for you to locate a certain file, folder, and overall development of your project.
Now, we can download our Truffle Suite
Truffle Suite is a development environment, testing framework, and asset pipeline for Ethereum, aiming to make life as an Ethereum developer easier.
It provides a suite of tools that allow developers to write smart contracts with the Solidity programming language, test them, deploy them, and build decentralized applications (dApps) on top of them.
-
npm install -g truffle
-
Verify truffle installation
truffle -v
-
Setup truffle environment on our project
truffle init
- Contracts - This file contain all of our smart contract
- Migrations - This file is where we write script to deploy the smart contract
- Test - This file is for test/debug our smart contract
truffle-config.js- This file contain our truffle configuration
Ethereum is a decentralized platform that runs smart contracts. These smart contracts are executed on all nodes in the Ethereum network. To interact with the Ethereum network (i.e., to read from or write to the blockchain), your application needs to connect to an Ethereum node.
Now, running your own Ethereum node can be resource-intensive. It requires downloading and synchronizing the entire Ethereum blockchain, which can take a lot of time and storage space. It also requires maintenance to stay synchronized with the network.
This is where Alchemy comes in. Alchemy hosts Ethereum nodes for you and provides a simple API to interact with them.
This means you can focus on building your application without worrying about maintaining an Ethereum node.
When you use Alchemy, your application sends API requests to Alchemy's servers. Alchemy's servers then interact with the Ethereum network on your behalf. They execute the necessary operations (like reading from or writing to the blockchain) and then return the results to your application.
In the context of deploying a smart contract, instead of setting up your own Ethereum node to deploy the contract, you can use Alchemy's API. You provide your Alchemy API key (which identifies your project) and the smart contract you want to deploy. Alchemy then deploys the contract to the Ethereum network for you.
-
HDWalletProvider - used to create a connection to the Infura Ethereum node, allowing you to interact with the Ethereum network without running your own Ethereum node.
-
Dotenv - It helps to keep sensitive data out of your codebase, which is particularly important when your code is stored in a public repository.
-
Install HDWalletProvider and dotenv on our terminal
npm install @truffle/hdwallet-provider
npm install dotenv
-
Uncomment code in our
truffle.config.js -
Make sure change network name to sepolia
-
Create your API (Name it Web3Lab)
-
Choose Sepolia network
-
Copy your Endpoint and paste in on our
truffle.config.js -
Create
.envfile to store our PROJECT_ID and MNEMONIC -
Refactor your API on
truffle.config.js -
You can get your MNEMONIC in your metamask. In MetaMask, you can find your account's mnemonic in the settings. Here's how you can find it:
- Click on the MetaMask extension icon in your browser.
- Click on the circle icon in the top-right corner.
- Click on "Settings".
- Scroll down and click on "Security & Privacy".
- Click on "Reveal Secret Recovery Phrase".
-
Now, every ethereum network has different network_id. Think of it like an address (Alamat). Since we're using sepolia as our ethereum network, we have to change our network_id.
- Change your
network_idto 11155111
- Change your
- Create one file within
Contractsfolder nameMigration.sol// SPDX-License-Identifier: MIT pragma solidity >=0.4.22 <0.9.0; contract Migrations { address public owner = msg.sender; uint public last_completed_migration; modifier restricted() { require( msg.sender == owner, "This function is restricted to the contract's owner" ); _; } function setCompleted(uint completed) public restricted { last_completed_migration = completed; } }
The Migrations.sol file in the Truffle Suite is a special contract that helps manage the deployment of other contracts to the Ethereum network.
Its purpose is to track and handle the deployment of smart contracts in a systematic way.
Order of Deployment:
- Truffle uses migration scripts to deploy contracts in a specific order.
- The migration scripts are written in JavaScript and are executed sequentially.
- The Migrations.sol contract is deployed first because it's specified in the initial migration script (usually named 1_initial_migration.js), and subsequent contracts are deployed in the order defined in subsequent migration scripts.
Contract Deployment History:
- Migrations.sol maintains a record of the deployment history.
- Each time you run a migration, a new instance of the Migrations contract is deployed with an incremented migration number.
- This ensures that contracts are not redeployed unnecessarily.
Network Independence:
- Since Ethereum networks can be decentralized and there may be multiple nodes, Migrations.sol helps ensure that each node has the same understanding of the deployed contract state.
- It provides a standardized way to manage and record the deployment process.
Migration Scripting:
- Migration scripts are JavaScript files that specify the deployment steps.
- They typically use the Truffle artifacts and web3.js (or ethers.js) to interact with the Ethereum network.
- These scripts are placed in the migrations folder and executed in order.
- If you don't include
Migrations.solin your contract folder, you won't be able to use the Truffle migration process. - Truffle relies on this contract to keep track of deployed contracts and their versions.
- Omitting it may result in an incomplete or disorganized deployment process.
- Create one file within
migrationfolder name1_migration.jsconst Migrations = artifacts.require("Migrations"); module.exports = function (deployer) { deployer.deploy(Migrations); };
Migrations folder in a Truffle project contains JavaScript files that serve as migration scripts.
These scripts are responsible for deploying smart contracts to the Ethereum network in a controlled and organized manner.
Each migration script corresponds to a specific deployment step and is executed sequentially.
Example script name:
- 1_initial_migration.js - This script deploys the
Migrations.solcontract, initializing the migration system. - 2_election.js - This script would deploy the next contract in the deployment order.
- 3_add_functionality.js - Another script that might deploy a contract or add functionality to existing contracts.
The purpose of organising deployment steps into separate migration scripts is to maintain a clear and ordered deployment process.
It ensures that each step is executed only once, avoiding unnecessary redeployments, and it helps in tracking the deployment history.
Election.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
contract Election {
address public electionLeader;
uint256 public candidatesCount = 0;
constructor(){
electionLeader = msg.sender; // electionLeader is the one who deploy the contract
}
modifier onlyByElectionLeader{ // Modifier to restrict access to certain functions in the contract
require(msg.sender == electionLeader, "Only election leader can register voters.");
_;
}
struct Voter {
bool voted; // To track if a voter has voted
uint256 vote; // To store the vote cast by the voter
}
mapping(address => Voter) public voters; // Mapping to store the voter details
struct Candidate {
string name; // Name of the candidate
uint256 voteCount; // Number of votes the candidate has received
}
Candidate[] public candidates; // Array to store the candidates
function addCandidate(string memory _name) public onlyByElectionLeader{
candidatesCount++;
candidates.push(Candidate(_name, 0));
}
function registerVoters(address _voter) public onlyByElectionLeader{
voters[_voter].voted = false;
}
function castVote(uint256 _candidateIndex) public {
require(!voters[msg.sender].voted, "You have already voted.");
require(candidatesCount > 0, "Invalid candidate index.");
voters[msg.sender].voted = true;
voters[msg.sender].vote = _candidateIndex;
candidates[_candidateIndex].voteCount += 1;
}
function winningCandidate() public view returns (string memory winnerName) {
uint256 winningVoteCount = 0;
for (uint256 i = 0; i < candidates.length; i++) {
if (candidates[i].voteCount > winningVoteCount) {
winningVoteCount = candidates[i].voteCount;
winnerName = candidates[i].name;
}
}
}
}2_election.js
const Election = artifacts.require("Election");
module.exports = function (deployer) {
deployer.deploy(Election);
};By now i suppose your directory is like this:
Remember, migration.sol and 1_migration.js is compulsory for any smart contract deployment to the Ethereum network.
You may change CrowdFunding.sol and 2_crowdFunding.js for example if you want to deploy a smart contract about Election, you may name your smart contract to Election.sol and 2_election.js.
You must firstly fund your metamask using Infura Faucet or Alchemy Faucet
These two website provides free cryptocurrency to users for testing purposes. You may find another faucet to fund your wallet.
If these two website cannot fund your wallet, you can search for another faucet by typing in google "sepolia testnet faucet"
If you're wallet is 0 ETH, you cannot deploy the Smart Contract as it requires a gas to deploy it to the Ethereum Network.
Before that, change your metamask's network into test network:
Copy your wallet address of your Metamask account
Firstly, we have to make sure to check compiler's version on our truffle.config.js
Make Sure that the version is the same as in your .sol file or else you'll catch an error halfway through
Then we can compile our smart contract with the command:
truffle compile --resetThis command will compile all of our smart contract in the folder /contracts
Now, we can deploy our smart contract with this command:
truffle migrate --network sepolia --resetIf you're getting this output, congratulations. You just deploy your first ever smart contract on ethereum network!
You may come across some error during your deployment and that's okay. Try find a way to solve it.
TRY THIS WAY OF THINKING
For example, some of you may have some error stating insufficient balance to deploy your Smart Contract.
Problem:
- Insufficient balance
How we gonna solve it?
Solution:
-
Add more fund to your wallet. OR
-
Reduce complexity of your Smart Contract
The more complex your Smart Contract, the more gas it required to deploy.
What Is Ethereum Explorer?
Ethereum explorer, also known as a blockchain explorer, is a tool that allows you to explore the Ethereum blockchain. It provides a user-friendly way to view and analyze the transactions, blocks, and addresses that make up the blockchain.
You can verify whether your smart contract has been deploy or not on the ethereum network.
Just copy your Contract Address on your output,
and paste it on Etherscan
Make sure you choose sepolia testnet
Once you searched using your Contract Address, you will see as following:
As you can see at the bottom, it highlights the contract creation which is the deployment of your Smart Contract into the Ethereum Network and when the contract is deployed
Using Ethereum Explore is a good practice for Decentralized Application as it provide an insight regarding your smart contract deployment, any transaction occurs, block informations, as well as to interact with your Web Application.
Another method (for me), usually i'll check my json file
within networks:{}, you can check your network_id, contract address, and transaction hash. If your smart contract is not deployed to Ethereum Network, this information would not appear on our .json.
Download Ganache
Ganache is personal blockchain for Ethereum development that you can use to deploy contracts, develop applications, and run tests.
After you install Ganache you may proceed to Quickstart
Alright now you may see a list of Account with 100 ETH fund.
This is pretty same with Metamask where you have account.
However, for development purpose i find Metamask a hassle since you have to fund your account compare to Ganache where you have a 100 ETH funded account.
The hostname in Ganache determines which network interfaces on your machine the Ganache server is accessible from. Depending on your development needs, you might want to change this setting.
Choose the 192.168.0.10 - Wifi and restart your Ganache
Now your RPC Server is different
Here's what each option means:
- 0.0.0.0 - All Interfaces: This means Ganache is accessible from any network interface on your machine. This could be useful if you're testing from a different device on the same network, or if you're running a client in a different virtual machine.
- 192.168.0.10 - Wifi: This is the IP address of your Wifi network interface. If you set this as the hostname, only devices on the same Wifi network can access the Ganache server.
- 127.0.0.1 - Loopback Pseudo-Interface 1: This is the IP address of the loopback network interface. This means the Ganache server can only be accessed from the same machine it's running on. This is the most secure option, as it doesn't expose the server to the rest of the network.
- 172.29.176.1 - vEthernet (WSL (Hyper V firewall)): This is the IP address of a virtual Ethernet network interface, likely created by the Windows Subsystem for Linux (WSL) or Hyper-V. If you're running a client in WSL or a Hyper-V virtual machine, you might want to use this option.
We can use these account in Ganache in Metamask.
First, we have to establish the connection between Ganache and Metamask.
Add your Network based on this details:
- Network name: Ganache
- New RPC URL : HTTP://192.168.0.10:7545
- Chain ID : 1337
- Currency symbol: ETH
You may find your RPC URL on your Ganache interface
By now your Metamask already connect with your Ganache
Now to import your Ganache account your have to click on your account at the top and add account
Click Import Account
Now to get your Account's Private Key, open your ganache and copy your private key and paste it.
Now congratulations you already import your Ganache account into Metamask
Open your truffle.config.js and uncomment this part of code:
Change this section according to setting on Ganache:
After refactoring your configuration file, you can proceed with compile and deployment of your smart contract.
Compile:
truffle compile --resetThis command will compile all of our smart contract in the folder /contracts
Deploy:
truffle migrate --network ganache --resetTo check whether your smart contract is already deployed is on the Blocks section
Blocks shows each block as mined on the blockchain, along with gas used and transactions.
Here's a breakdown of what those blocks might represent:
-
Initialize Block (Block 0): The first block which often referred as genesis block or Block 0. This block doesn't contain any transactions (since you haven't deployed any contracts or made any transactions yet), but it's necessary to initialize the blockchain. All subsequent blocks (which will contain your transactions) will link back to this block.
-
Migrations Contract Deployment (Block 1): The first transaction is usually the deployment of the Migrations contract. Truffle uses this contract to keep track of which migrations have been run. The contract is deployed only once, when you run your migrations for the first time.
-
Migrations Contract Update (Block 2): After the Migrations contract is deployed, Truffle records that the first migration (the deployment of the Migrations contract itself) has been completed. It does this by calling a function on the Migrations contract, which creates a transaction.
-
Your Contract Deployment (Block 3): The next transaction is the deployment of your actual contract. This is the contract you've written and are deploying with your second migration script.
-
Migrations Contract Update (Block 4): After your contract is deployed, Truffle records that the second migration has been completed. Again, it does this by calling a function on the Migrations contract, which creates another transaction.
Another method (for me), usually i'll check my json file
within networks:{}, you can check your network_id, contract address, and transaction hash. If your smart contract is not deployed to Ganache Network, this information would not appear on our .json.
Now I believe you have a new folder names build/contracts which contains your .json file
You can see there's a massive line of code there.
Here's the thing, whenever you deploy your smart contract on the ethereum network, truffle frameworks will automatically generate a new json file.
The more complex and complicated your Smart Contract, the longer line of code you will get.
This json file is really crucial for Web Development integration with our Smart Contract.
Web Application + Smart Contract = Decentralized Application.
It contains important information about your contract:
- Application Binary Interface (ABI)
- Contract Address
Okay we've already know that we can use Contract Address to verify our smart contract deployment on Ethereum Explorer.
Well what about Application Binary Interface (ABI)?
ABI is a representation of the contract's functions and variables in a format that JavaScript can understand.
Javascript compiler cannot read solidity code, due to that truffle suite generate a file that Javascript compiler can read.
So Javascript will undergo it's fetching method to obtain all of the variables, functions, and etc by reading the .json file to be operate successfully on our Web Application.
- Whenever you go through some errors/bugs, you must find a way to solve the bug. Don't worry about it, every developer/IT practitioner will go through this.
- My way of solving is I will copy the error I run, and paste it to google. Well of course you have to use GPT as well to solve it.
- Some of the website i used to solve my errors is Stack Overflow and Ethereum Stack Exchange as these two website will publish a solved error that someone has been go through before this.
- Remember, this may be a new technology and practice for you to explore and develop. So it supposed to be hard for a beginner. If it's easy then everyone would've been a great developer right now. If you only do what you can do, you will never be more than you are.
- I promise whenever you solve some error that has been interrupting your development, you will feel some sort of satisfaction.
- Responsive Web Design
- Setup Environment
- Web Development
So in this section i'm just gonna explain some frameworks we use to build a Decentralized Application (DApps).
Smart Contract (Back-End) + React JS (Front-End) = Decentralized Application (DApps)
Now we venturing a framework that are used to build a responsive web design.
I'm really sure that every one of you may heard React JS as it is the most popular Javascript framework in the world and is used by many well-known companies, such as Netflix, Maybank2u (MAE), Instagram, and etc.
If you're interested to be a Front-End Developer/ Full-Stack Developer, then this frameworks is the one you have to master.
React JS is a Javascript library that allow developer to build user interface. It's foundation is based on the concept of components, which are piece of code that are reusable throughout your project.
Think of components as the building blocks of your app, they're like little chunks of code that you can reuse throughout your project.
Need a button, a form, or a navigation bar? Each one can be a component!
Here's a little taste of what a React component looks like:
import React from 'react';
function HelloWorld(props) {
return (
<div>
Hello, {props.name}!
</div>
);
}
// To use it in your app
<HelloWorld name="John Doe" />Now this is what we call JSX.
JSX is a syntax extension for Javascript that let you write your HTML code and Javascript code logic all in one place.
All of React JS file is in .jsx file format.
Alright you must wondering why we can't use other framework and programming language other than React JS and Javascript right?
Maybe you wanna use Flask, Python's famous frameworks used to build user interface as well.
Here's my take on why i use React JS compare to other frameworks:
-
Javascript Ecosystem: Solidity, has a syntax that is somewhat similar to Javascript. This makes Javascript a prominent choice for Ethereum Developer. Plus, the Javascript ecosystem offers several libraries to interact with the blockchain, like Web3 JS and Ethers JS.
-
Component-Based Architecture: As i mentioned before, React is based on the concept of components, which are piece of code that are reusable throughout your project. It is a great fit for DApps. Each components can manage its own logic and state to interact with complex blockchain data.
-
Strong Community and Ecosystem: Since React JS is the most popular framework to build user interface, the community is gigantic as well, which means it's easy for you to find help and resources.
However, my most prominent reason is the Demand of Skills. React JS is the most famous framework to build UI. Which means most company integrate this framework in their system. Hence, this skills is demanding in the market. Not only in Malaysia, but worldwide. You master React, you'll be wanted universally.
Vite, simply said is a build tool and development server. It can be used by any frameworks or no frameworks at all (Vanilla).
However, it is recommended to integrate your project with the React-Powered Frameworks.
The reason i chose this build tool:
- It swiftly fast
- It provide a templates for starting React projects.
- Easy for documentation and alteration.
Even React official documentation state this:
You may use Next JS if you want, but since we don't need to build a server-site rendering or simply said all of the Back-End configuration since we already have our Back-End (Smart Contract). Next JS is like all in one Front-End + Back-End. However, if you want to build only Front-End, i suggest you use Vite.
To use React in Vite, you have to create a react application
Here's a code snippet:
npm create vite@latestConfigure your project like this:
For the Project name you may name it with anything you want but for me personally i always name it as client to indicate that it is on the client-side.
Now, i believe you have a new generated folder on your directory
This folder contains all of Front-End file. So in order to make a change (Download dependencies / run application), you have to access the folder first.
You can do so with this command:
cd yourFolderNameSo let's first download all of dependencies
npm installAfter download all of dependencies, you can run your React Application
npm run devYour VSCode will display a notification like this:
press Open in Browser and it will redirect you to your browser:
and Congratulations, you just created your first ever React Application.
We're not gonna go in-depth on Web Development but i suggest you do some self learning on React JS.
Let's first initialize our App.jsx:
import React from 'react';
const App = () => {
return (
<div>
<h1>Election DApp</h1>
<div>
<div>
<b>Current Account:</b>
<b>Election Leader: </b>
</div>
<h2>Candidates</h2>
<ul>
<li>Name of Candidate </li>
</ul>
</div>
<div>
<div>
<h3>Add Candidate</h3>
<input type="text"placeholder="Candidate Name"/>
<button >
Add Candidate
</button>
</div>
<div>
<h3>Register Voter</h3>
<input type="text" placeholder="Voter Address"
/>
<button>Register Voter</button>
</div>
<div>
<h3>Cast Vote</h3>
<input type="number" placeholder="Candidate Index"
/>
<button>Vote</button>
</div>
</div>
<div>
<h3>Winning Candidate</h3>
<button>Get Winning Candidate</button>
<p></p>
</div>
</div>
);
}
export default App;
You may run this code and display this interface using this command:
npm run dev
Now let's add some styles on our interface but first you may install on our dependencies Sass
npm install sassAfter you install sass, we can proceed with styling our interface:
App.jsx:
import React from 'react';
import './App.scss';
const App = () => {
return (
<div className="main-container">
<h1 className="title">Election DApp</h1>
<div className="main-content">
<div className="account">
<b>Current Account:</b>
<b>Election Leader: </b>
</div>
<h2>Candidates</h2>
<ul>
<li>Name of Candidate </li>
</ul>
</div>
<div className="main-process">
<div className="reg-candidate">
<h3>Add Candidate</h3>
<input
type="text"
placeholder="Candidate Name"
/>
<button className="button">
Add Candidate
</button>
</div>
<div className="reg-voter">
<h3>Register Voter</h3>
<input
type="text"
placeholder="Voter Address"
/>
<button className="button">Register Voter</button>
</div>
<div className="cast-vote">
<h3>Cast Vote</h3>
<input
type="number"
placeholder="Candidate Index"
/>
<button className="button">Vote</button>
</div>
</div>
<div className="winning-candidate">
<h3>Winning Candidate</h3>
<button>Get Winning Candidate</button>
<p></p>
</div>
</div>
);
}
export default App;App.scss:
.main-container {
display: flex;
flex-direction: column;
justify-content: start;
align-items: center;
gap: 20px;
height: 90vh;
width: 98vw;
.title {
text-align: center;
font-weight: bold;
width: 100%;
}
.main-content {
.account {
display: flex;
flex-direction: column;
}
}
.main-process {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
width: 100%;
box-sizing: border-box;
justify-content: center;
align-items: center;
.reg-candidate {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border: 1px solid black;
padding: 10px;
gap: 10px;
border-radius: 10px;
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
cursor: pointer;
transition: 0.3s;
&:hover {
transform: scale(1.05);
}
.text {
font-size: 20px;
font-weight: bold;
}
.button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
}
.reg-voter {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 10px;
border: 1px solid black;
padding: 10px;
border-radius: 10px;
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
cursor: pointer;
transition: 0.3s;
&:hover {
transform: scale(1.05);
}
.text {
font-size: 20px;
font-weight: bold;
}
.button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
}
.cast-vote {
display: flex;
flex-direction: column;
justify-content: center;
gap: 10px;
align-items: center;
border: 1px solid black;
padding: 10px;
border-radius: 10px;
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
cursor: pointer;
transition: 0.3s;
&:hover {
transform: scale(1.05);
}
.text {
font-size: 20px;
font-weight: bold;
}
.button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
}
}
.winning-candidate {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border: 1px solid black;
padding: 10px;
border-radius: 10px;
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
cursor: pointer;
transition: 0.3s;
&:hover {
transform: scale(1.05);
}
.icon {
font-size: 50px;
}
.text {
font-size: 20px;
font-weight: bold;
}
}
}Now your interface will look like this:
Cool right?
You may adjust your styling depending on your preferences. This is where coding + creativity comes in.
Web3 JS is a collection of libraries that allow you to interact with a local or remote Ethereum node using HTTP, IPC, or WebSocket.
It's the Ethereum compatible JavaScript API which implements the Generic JSON RPC spec.
We use this library to connect our Interface with our Smart Contract.
Remember we store our smart contract json file?
it's in build/contracts. However, we're gonna need this json file to interact with our Smart Contract.
So let's update our truffle.config.js
with this line of code, after we deploy our smart contract, it'll automatically generate the file on ./client/src/artifacts which ./client is our front-end folder hence we can interact with it on our interface.
Now this is the most prominent and difficult part.
Connecting your interface with your smart contract require a very attentive detail and changes you made.
However, let me paste the code first and explain later on.
App.jsx
import React, { useState, useEffect } from 'react';
import Web3 from "web3";
import ElectionABI from "/src/artifacts/Election.json";
import './App.scss';
const App = () => {
const [account, setAccount] = useState('');
const [loader, setLoader] = useState(true);
const [election, setElection] = useState(null);
const [electionLeader, setElectionLeader] = useState('');
const [candidates, setCandidates] = useState([]);
const [newCandidateName, setNewCandidateName] = useState('');
const [voterAddress, setVoterAddress] = useState('');
const [selectedCandidate, setSelectedCandidate] = useState('');
const [winnerName, setWinnerName] = useState('');
useEffect(() => {
loadWeb3();
loadBlockchainData();
}, []);
const loadWeb3 = async () => {
if (window.ethereum) {
window.web3 = new Web3(window.ethereum);
await window.ethereum.enable();
} else {
window.alert(
"Non-Ethereum browser detected. You should consider trying MetaMask!"
);
}
};
const loadBlockchainData = async () => {
setLoader(true);
const web3 = new Web3(window.ethereum);
const accounts = await web3.eth.getAccounts();
setAccount(accounts[0]);
const networkId = await web3.eth.net.getId();
const networkData = ElectionABI.networks[networkId];
if (networkData) {
const election = new web3.eth.Contract(ElectionABI.abi, networkData.address);
setElection(election);
const leader = await election.methods.electionLeader().call();
setElectionLeader(leader);
const candidateCount = await election.methods.candidatesCount().call()
const candidatesList = [];
for (let i = 0; i < candidateCount; i++) {
const candidate = await election.methods.candidates(i).call();
candidatesList.push(candidate);
}
console.log("Candidates:", candidatesList); // Debugging log
setCandidates(candidatesList);
setLoader(false);
} else {
window.alert('The smart contract is not deployed to current network');
}
};
const addCandidate = async () => {
await election.methods.addCandidate(newCandidateName).send({ from: account });
setNewCandidateName('');
loadBlockchainData();
};
const registerVoter = async () => {
await election.methods.registerVoters(voterAddress).send({ from: account });
setVoterAddress('');
alert('Voter registered successfully');
};
const castVote = async () => {
await election.methods.castVote(selectedCandidate).send({ from: account });
setSelectedCandidate('');
alert('Vote cast successfully');
loadBlockchainData();
};
const getWinningCandidate = async () => {
const name = await election.methods.winningCandidate().call();
setWinnerName(name);
console.log("Winning Candidate:", name); // Debugging log
};
if (loader) {
return (
<div>
Loading....
</div>
);
}
return (
<div className="main-container">
<h1 className="title">Election DApp</h1>
<div className="main-content">
<div className="account">
<b>Current Account:</b> {account}
<b>Election Leader: </b> {electionLeader}
</div>
<h2>Candidates</h2>
<ul>
{candidates.map((candidate, index) => (
<li key={index}>{candidate.name} - {candidate.voteCount.toString()} votes</li>
))}
</ul>
</div>
<div className="main-process">
<div className="reg-candidate">
<h3>Add Candidate</h3>
<input
type="text"
value={newCandidateName}
onChange={(e) => setNewCandidateName(e.target.value)}
placeholder="Candidate Name"
/>
<button className="button" onClick={addCandidate}>
Add Candidate
</button>
</div>
<div className="reg-voter">
<h3>Register Voter</h3>
<input
type="text"
value={voterAddress}
onChange={(e) => setVoterAddress(e.target.value)}
placeholder="Voter Address"
/>
<button className="button" onClick={registerVoter}>Register Voter</button>
</div>
<div className="cast-vote">
<h3>Cast Vote</h3>
<input
type="number"
value={selectedCandidate}
onChange={(e) => setSelectedCandidate(e.target.value)}
placeholder="Candidate Index"
/>
<button className="button" onClick={castVote}>Vote</button>
</div>
</div>
<div className="winning-candidate">
<h3>Winning Candidate</h3>
<button onClick={getWinningCandidate}>Get Winning Candidate</button>
<p>{winnerName}</p>
</div>
</div>
);
}
export default App;Looks huge and complicated right?
now let's go through one by one function that we add on our App.jsx
import React, { useState, useEffect } from 'react';
import Web3 from "web3";
import ElectionABI from "/src/artifacts/Election.json";
import './App.scss';There's 4 things we import:
- useState and useEffect : To initialize state variable and fetch blockchain data
- web3 : To interact with our Smart Contract
Election.jsonfileApp.scssfile
useState is a React Hook that lets you add React state to function components. It takes one argument which is the initial state, and it returns an array with two elements:
- current state
- function to update it.
const [account, setAccount] = useState('');
const [loader, setLoader] = useState(true);
const [election, setElection] = useState(null);
const [electionLeader, setElectionLeader] = useState('');
const [candidates, setCandidates] = useState([]);
const [newCandidateName, setNewCandidateName] = useState('');
const [voterAddress, setVoterAddress] = useState('');
const [selectedCandidate, setSelectedCandidate] = useState('');
const [winnerName, setWinnerName] = useState('');useEffect is React Hook as well but it's a function that lets you perform side effects in function components. Side effects could be data fetching of our smart contract data in this case.
useEffect(() => {
loadWeb3();
loadBlockchainData();
}, []);const loadWeb3 = async () => {
if (window.ethereum) {
window.web3 = new Web3(window.ethereum);
await window.ethereum.enable();
} else {
window.alert(
"Non-Ethereum browser detected. You should consider trying MetaMask!"
);
}
};This process does this following:
- Checks if the browser has MetaMask (a cryptocurrency wallet) installed.
- If MetaMask is installed, it sets up a connection to it.
- If MetaMask isn't installed, it shows an alert telling the user to consider installing MetaMask.
const loadBlockchainData = async () => {
setLoader(true);
const web3 = new Web3(window.ethereum);
const accounts = await web3.eth.getAccounts();
setAccount(accounts[0]);
const networkId = await web3.eth.net.getId();
const networkData = ElectionABI.networks[networkId];
if (networkData) {
const election = new web3.eth.Contract(ElectionABI.abi, networkData.address);
setElection(election);
const leader = await election.methods.electionLeader().call();
setElectionLeader(leader);
const candidateCount = await election.methods.candidatesCount().call()
const candidatesList = [];
for (let i = 0; i < candidateCount; i++) {
const candidate = await election.methods.candidates(i).call();
candidatesList.push(candidate);
}
console.log("Candidates:", candidatesList); // Debugging log
setCandidates(candidatesList);
setLoader(false);
} else {
window.alert('The smart contract is not deployed to current network');
}
};This process does this following:
- It starts a loading process.
- It connects to the Ethereum blockchain.
- It gets the user's Ethereum account and saves it.
- It gets the ID of the network the user is connected to.
- It checks if there's a smart contract for an election on this network.
- If there is:
- It connects to the election contract.
- It gets the current leader of the election and saves it.
- It counts how many candidates there are in the election.
- It gets information about each candidate and saves it.
- It stops the loading process.
- If there isn't a smart contract, it shows an alert saying so.
const addCandidate = async () => {
await election.methods.addCandidate(newCandidateName).send({ from: account });
setNewCandidateName('');
loadBlockchainData();
};This process does this following:
- It adds a new candidate to the election using the name stored in newCandidateName.
- It clears the newCandidateName field.
- It updates the blockchain data to reflect the new candidate.
const registerVoter = async () => {
await election.methods.registerVoters(voterAddress).send({ from: account });
setVoterAddress('');
alert('Voter registered successfully');
};This process does this following:
- It registers a new voter with the address stored in voterAddress.
- It clears the voterAddress field.
- It shows an alert saying the voter was registered successfully.
const castVote = async () => {
await election.methods.castVote(selectedCandidate).send({ from: account });
setSelectedCandidate('');
alert('Vote cast successfully');
loadBlockchainData();
};This process does this following:
- It casts a vote for the candidate selected by the user.
- It clears the selectedCandidate field.
- It shows an alert saying the vote was cast successfully.
- It updates the blockchain data to reflect the new vote.
const getWinningCandidate = async () => {
const name = await election.methods.winningCandidate().call();
setWinnerName(name);
console.log("Winning Candidate:", name); // Debugging log
};This process does this following:
- It gets the name of the winning candidate from the election.
- It saves the winner's name.
- It prints the winner's name for debugging purposes.
Address Info
This section (personally) is very crucial information to include into your Decentralized Application (DApps) as you know who's the Creator (Election Leader also the one who deploy the smart contract) and Current Address so you know who's the one that doing the transaction.
Candidates Info
Now this section is based on our Smart Contract:
It's a good decision to be made to include it as well on our Application.
Add Candidate
This section is based on our addCandidate() function on our smart contract
As you can see, it accept 1 argument which is string memory _name
Register Voters
This section is based on our registerVoters() function on our smart contract
As you can see, it accept 1 argument which is string memory _name, and it set the bool voted condition into false
Cast Vote
This section is based on our castVote() function on our smart contract
As you can see, it accept 1 argument which is uint256 _candidateIndex since our candidate is in array, and it
- set the
bool votedcondition intotrue - it records that
uint256 _candidateIndexis mapped to the person in candidate array (if vote 0, then person in 0 index will be voted) - add the
voteCountof the candidate
Winning Candidate
This section is just calling, which is there aint any transaction required to do so.
It check which candidate have the highest voteCount and display it.
Transaction will be made if you are executing a function.
This 3 section:
This process required a transaction to be executed.
How do you know there is a transaction?
This notification will pops out.
It pretty much like you want to confirm some banking transaction.
If you don't have enough funds in your wallet, you would not be able to execute this transaction thus please make sure your wallet have enough funds to do the transaction.


















































































