visit
While products like GenCoin originated with the purpose of providing a new way to perform financial transactions, the underlying concepts are more powerful than just currency-related tasks. In fact, the distributed nature in how blockchain is designed ties directly to the heart of Web3.
Web3 provides an alternative for the web2 reality where control is centralized into a handful of technology providers such as Google, Apple, and Amazon. Web3 creates a permissionless datastore where no one person or corporation controls or owns the data, yet that data is still guaranteed to be true. The data is stored on the public ledgers of a blockchain network. So instead of one entity owning the data, multiple nodes (computers running the blockchain) store the data and come to a consensus about whether the data is valid or not.
This flow has been fully detailed by the team at :
Now, let’s build something from the ground up.
After creating a free account at , I created a new project called jvc-homeowners-ballot
:
The new project contains the following details, which I will reference later:
On my local machine, I created a matching folder, called jvc-homeowners-ballot
and then initialized Truffle using the following CLI command:
truffle init
├── contracts
│ └── Migrations.sol
├── migrations
│ └── 1_initial_migration.js
├── test
└── truffle-config.js
npm install --save @truffle/hdwallet-provider
ganache
ganache v7.0.1 (@ganache/cli: 0.1.2, @ganache/core: 0.1.2)
Starting RPC server
Available Accounts
==================
(0) 0x2B475e4fd7F600fF1eBC7B9457a5b58469b9EDDb (1000 ETH)
(1) 0x5D4BB40f6fAc40371eF1C9B90E78F82F6df33977 (1000 ETH)
(2) 0xFaab2689Dbf8b7354DaA7A4239bF7dE2D97e3A22 (1000 ETH)
(3) 0x8940fcaa55D5580Ac82b790F08500741326836e0 (1000 ETH)
(4) 0x4c7a1b7EB717F98Fb0c430eB763c3BB9212F49ad (1000 ETH)
(5) 0x22dFCd5df8d4B19a42cB14E87219fea7bcA7C92D (1000 ETH)
(6) 0x56882f79ecBc2D68947C6936D4571f547890D07c (1000 ETH)
(7) 0xD257AFd8958c6616bf1e61f99B2c65dfd9fEE95A (1000 ETH)
(8) 0x4Bb2EE0866578465E3a2d3eCCC41Ea2313372B20 (1000 ETH)
(9) 0xdf267AeFeAfE4b7053ca10c3d661a8CB24E98236 (1000 ETH)
Private Keys
==================
(0) 0x5d58d27b0f294e3222bbd99a3a1f07a441ea4873de6c3a2b7c40b73186eb616d
(1) 0xb9e52d6cfb2c074fa6a6578b946e3d00ea2a332bb356d0b3198ccf909a97fdc8
(2) 0xc52292ce17633fe2724771e81b3b4015374d2a2ea478891dab74f2028184edeb
(3) 0xbc7b0b4581592e48ffb4f6420228fd6b3f954ac8cfef778c2a875
(4) 0xc63310ccdd9b8c2da6d80c886bef4077359bb97e435fb4fe83fcbec529a536fc
(5) 0x90bc16b1520b66a02835530020e43048198195239ac9880b940d7b2a48b0b32c
(6) 0x4fb227297dafb879e148d44cf4872611819412cdd1620ad028ec7c189a53e973
(7) 0xf0d4dbe2f9970991ccc94a137cfa7cf284c09d0838db0ce25e76c9ab9f4316d9
(8) 0x495fbc6a16ade5647d82c6ad12821667f95d8b3c376dc290ef86c0d926f50fea
(9) 0x434f5618a3343c5e3b0b4dbeaf3f41c62777d91c3314b83f74e194be6c09416b
HD Wallet
==================
Mnemonic: immense salmon nominee toy jungle main lion universe seminar output oppose hungry
Base HD Path: m/44'/60'/0'/0/{account_index}
Default Gas Price
==================
2000000000
BlockGas Limit
==================
30000000
Call Gas Limit
==================
50000000
Chain Id
==================
1337
RPC Listening on 127.0.0.1:8545
Within my project folder, the truffle-config.js
file was updated to activate the following lines:
development: {
host: "127.0.0.1", // Localhost (default: none)
port: 8545, // Standard Ethereum port (default: none)
network_id: "*", // Any network (default: none)
},
truffle console
truffle(development)>
const HDWalletProvider = require('@truffle/hdwallet-provider');
const mnemonic = '12 words here';
const wallet = new HDWalletProvider(mnemonic, "//localhost:8545");
truffle(development)> wallet
HDWalletProvider {
walletHdpath: "m/44'/60'/0'/0/",
wallets: {
...
},
addresses: [
'0xa54b012b406c01dd99a6b18ef8b55a15681449af',
'0x6d507a70924ea3393ae1667fa88801650b9964ad',
'0x1237e0a8522a17e29044cde69b7b10b112544b0b',
'0x80b4adb18698cd47257be881684fff1e14836b4b',
'0x09867536371e43317081bed18203df4ca5f0490d',
'0x89f1eeb95b7a659d4748621c8bdbabc33ac47bbb',
'0x54ceb6f0d722dcb33152c953d5758a08045f254d',
'0x25d2a8716792b98bf9cce5781b712f00cf33227e',
'0x37b6364fb97028830bfeb0cb8d2b14e95e2efa05',
'0xe9f56031cb6208ddefcd3cdd5a1a41f7f3400af5'
],
...
Using the Ropsten Etherscan site, we can validate the transaction completed successfully:
The dotenv
dependency was added to the project using the following command:
npm install --save dotenv
Next, a new file called .env
was created at the root of the project and contained the following two lines:
INFURA_API_KEY=INSERT YOUR API KEY HERE (no quotations)
MNEMONIC="12 words here"
The INFURA_API_KEY
is the Project ID that was given when the jvc-homeowners-ballot
project was created.
Important note: Make sure the .env file is included in the .gitignore
file to avoid this secret information from being available to others with access to the repository.
The last preparation step is to update the truffle-config.js
file. First, we need to add the following lines at the top of the file:
require("dotenv").config();
const HDWalletProvider = require("@truffle/hdwallet-provider");
Next, we need to add the following network, which will leverage the dotenv
dependency added above:
ropsten: {
provider: () =>
new HDWalletProvider(
process.env.MNEMONIC,
`//ropsten.infura.io/v3/${process.env.INFURA_API_KEY}`
),
network_id: 3, // Ropsten's id
gas: 5500000, // Ropsten has a lower block limit than mainnet
confirmations: 2, // # of confs to wait between deployments. (default: 0)
timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50)
skipDryRun: true // Skip dry run before migrations? (default: false for public nets )
},
For the homeowner’s association election, we will use the following contract, which is called JvcHomeownerBallot.sol
and located in the contracts folder of the project:
// SPDX-License-Identifier: UNLICENSED (it is common practice to include an open source license or declare it unlicensed)
pragma solidity ^0.8.7; // tells the compiler which version to use
contract Homeowners {
// store the addresses of voters on the blockchain in these 2 arrays
address[] votedYes;
address[] votedNo;
function voteYes() public {
votedYes.push(msg.sender);
}
function voteNo() public {
votedNo.push(msg.sender);
}
function getYesVotes() public view returns (uint) {
return votedYes.length;
}
function getNoVotes() public view returns (uint) {
return votedNo.length;
}
}
.
├── JvcHomeownersBallot.sol
└── Migrations.sol
With the contract in place, we need to establish a way to deploy the contract. This is where the migrations folder comes into place. The following contents were added to a 2_deploy_contracts.js
file inside the migrations
folder:
const JvcHomeownersBallot = artifacts.require("JvcHomeownersBallot.sol");
module.exports = function(deployer) {
deployer.deploy(JvcHomeownersBallot);
};
truffle migrate --network ropsten
Compiling your contracts...
===========================
> Compiling ./contracts/JvcHomeownersBallot.sol
> Artifacts written to /Users/john.vester/projects/jvc/consensys/jvc-homeowners-ballot/build/contracts
> Compiled successfully using:
- solc: 0.8.11+commit.d7f03943.Emscripten.clang
Network up to date.
truffle(development)> truffle migrate --network ropsten
Compiling your contracts...
===========================
> Compiling ./contracts/JvcHomeownersBallot.sol
> Compiling ./contracts/Migrations.sol
> Artifacts written to /Users/john.vester/projects/jvc/consensys/jvc-homeowners-ballot/build/contracts
> Compiled successfully using:
- solc: 0.8.11+commit.d7f03943.Emscripten.clang
Starting migrations...
======================
> Network name: 'ropsten'
> Network id: 3
> Block gas limit: 8000000 (0x7a1200)
1_initial_migration.js
======================
Deploying 'Migrations'
----------------------
> transaction hash: 0x5f227f26a31a3667a689be2d7fa6121a21153eb219873f6fc9aecede221b3b82
> Blocks: 5 Seconds: 168
> contract address: 0x9e6008B354ba4b9f91ce7b8D95DBC6130324024f
> block number: 11879583
> block timestamp: 1643257600
> account: 0xa54b012B406C01dd99A6B18eF8b55A15681449Af
> balance: 1.573649230299520359
> gas used: 250142 (0x3d11e)
> gas price: 2.506517682 gwei
> value sent: 0 ETH
> total cost: 0.000626985346010844 ETH
Pausing for 2 confirmations...
------------------------------
> confirmation number: 1 (block: 11879584)
> confirmation number: 2 (block: 11879585)
> Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost: 0.000626985346010844 ETH
2_deploy_contracts.js
=====================
Deploying 'JvcHomeownersBallot'
-------------------------------
> transaction hash: 0x1bf86b0eddf625366f65a996e633db589cfcef1a4d6a4d6c92a5c1f4e63c767f
> Blocks: 0 Seconds: 16
> contract address: 0xdeCef6474c95E5ef3EFD313f617Ccb126236910e
> block number: 11879590
> block timestamp: 1643257803
> account: 0xa54b012B406C01dd99A6B18eF8b55A15681449Af
> balance: 1.5730216
> gas used: 159895 (0x27097)
> gas price: 2.507502486 gwei
> value sent: 0 ETH
> total cost: 0.00040093710999897 ETH
Pausing for 2 confirmations...
------------------------------
> confirmation number: 1 (block: 11879591)
> confirmation number: 2 (block: 11879592)
> Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost: 0.00040093710999897 ETH
Summary
=======
> Total deployments: 2
> Final cost: 0.0009814 ETH
- Blocks: 0 Seconds: 0
- Saving migration to chain.
- Blocks: 0 Seconds: 0
- Saving migration to chain.
At this point we have deployed the JvcHomeownersBallot
smart contract to the Ropsten network. The smart contract can be verified using the following URL and providing the contract address in the “Deploying JvcHomeownersBallot” logs:
For the prior steps, I used a folder called jvc-homeowners-ballot
. On that same level, I will create a React application called jvc-homeowners-ballot-client
using the React CLI:
npx create-react-app jvc-homeowners-ballot-client
Next, I changed directories into the newly-created folder and executed the following to install the web3
dependency into the React application:
cd jvc-homeowners-ballot-client
npm install web3
With the core React application ready, a contract application binary interface (ABI) needs to be established to allow our Dapp to communicate with contracts on the Ethereum ecosystem. Based upon the contents of the JvcHomeownerBallot.sol
smart contract file, I navigated to the build/contracts
folder and opened the JvcHomeownersBallet.json
file and used the values for the “abi” property for the jvcHomeOwnersBallot
constant of the abi.js
file as shown below:
export const jvcHomeownersBallot = [
{
"inputs": [],
"name": "voteYes",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "voteNo",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "getYesVotes",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
},
{
"inputs": [],
"name": "getNoVotes",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
}
];
This file was placed into a newly-created abi
folder inside the src
folder of the React application.
Now, the React Apps.js
file needs to be updated. Let’s first start with the top of the file, which needs to be configured as shown below:
import React, { useState } from "react";
import { jvcHomeownersBallot } from "./abi/abi";
import Web3 from "web3";
import "./App.css";
const web3 = new Web3(Web3.givenProvider);
const contractAddress = "0xdeCef6474c95E5ef3EFD313f617Ccb126236910e";
const storageContract = new web3.eth.Contract(jvcHomeownersBallot, contractAddress);
The contactAddress
can be found a number of ways. In this case, I used the results in the truffle - migrate CLI command. Another option is to use the site.
At this point, standard React development can take over. The finished App.js
file will look like this:
import React, { useState } from "react";
import { jvcHomeownersBallot } from "./abi/abi";
import Web3 from "web3";
import Nav from "./components/Nav.js";
import "./App.css";
import { makeStyles } from "@material-ui/core/styles";
import Button from "@material-ui/core/Button";
import {CircularProgress, Grid, Typography} from "@material-ui/core";
const useStyles = makeStyles((theme) => ({
root: {
"& > *": {
margin: theme.spacing(1),
},
},
}));
const web3 = new Web3(Web3.givenProvider);
const contractAddress = "0xdeCef6474c95E5ef3EFD313f617Ccb126236910e";
const storageContract = new web3.eth.Contract(jvcHomeownersBallot, contractAddress);
function App() {
const classes = useStyles();
const [voteSubmitted, setVoteSubmitted] = useState("");
const [yesVotes, setYesVotes] = useState(0);
const [noVotes, setNoVotes] = useState(0);
const [waiting, setWaiting] = useState(false);
const getVotes = async () => {
const postYes = await storageContract.methods.getYesVotes().call();
setYesVotes(postYes);
const postNo = await storageContract.methods.getNoVotes().call();
setNoVotes(postNo);
};
const voteYes = async () => {
setWaiting(true);
const accounts = await window.ethereum.enable();
const account = accounts[0];
const gas = (await storageContract.methods.voteYes().estimateGas()) * 1.5;
const post = await storageContract.methods.voteYes().send({
from: account,
gas,
});
setVoteSubmitted(post.from);
setWaiting(false);
};
const voteNo = async () => {
setWaiting(true);
const accounts = await window.ethereum.enable();
const account = accounts[0];
const gas = (await storageContract.methods.voteNo().estimateGas() * 1.5);
const post = await storageContract.methods.voteNo().send({
from: account,
gas,
});
setVoteSubmitted(post.from);
setWaiting(false);
};
return (
<div className={classes.root}>
<Nav ></Nav>
<div className="main">
<div className="card">
<Typography variant="h3" gutterBottom>
JVC Homeowners Ballot
</Typography>
<Typography gutterBottom>
How do you wish to vote?
</Typography>
<span className="buttonSpan">
<Button
id="yesButton"
className="button"
variant="contained"
color="primary"
type="button"
onClick={voteYes}>Vote Yes</Button>
<div className="divider"></div>
<Button
id="noButton"
className="button"
color="secondary"
variant="contained"
type="button"
onClick={voteNo}>Vote No</Button>
<div className="divider"></div>
</span>
{waiting && (
<div>
<CircularProgress ></CircularProgress>
<Typography gutterBottom>
Submitting Vote ... please wait
</Typography>
</div>
)}
{!waiting && voteSubmitted && (
<Typography gutterBottom>
Vote Submitted: {voteSubmitted}
</Typography>
)}
<span className="buttonSpan">
<Button
id="getVotesButton"
className="button"
color="default"
variant="contained"
type="button"
onClick={getVotes}>Get Votes</Button>
</span>
{(yesVotes > 0 || noVotes > 0) && (
<div>
<Typography variant="h5" gutterBottom>
Current Results
</Typography>
<Grid container spacing={1}>
<Grid item xs={6}>
<div className="resultsAnswer resultsHeader">Vote</div>
</Grid>
<Grid item xs={6}>
<div className="resultsValue resultsHeader"># of Votes</div>
</Grid>
<Grid item xs={6}>
<div className="resultsAnswer">Yes</div>
</Grid>
<Grid item xs={6}>
<div className="resultsValue">{yesVotes}</div>
</Grid>
<Grid item xs={6}>
<div className="resultsAnswer">No</div>
</Grid>
<Grid item xs={6}>
<div className="resultsValue">{noVotes}</div>
</Grid>
</Grid>
</div>
)}
</div>
</div>
</div>
);
}
export default App;
yarn start
At this point, three options are available:
This video can also be found on .
“Focus your time on delivering features/functionality that extends the value of your intellectual property. Leverage frameworks, products, and services for everything else.” - J. Vester
Also published on: