1.开发环境准备:ganache+webjs+nodejs
2.创建一个文件夹Dapp
mkdir Dapp
3.安装ganache-cli,要求(nodejs>=V8)
npm install -g ganache-cli
ganache是
开发和测试的本地内存区块链。它模拟了真实的以太坊网络的功能,包括由测试以太币资助的多个账户的可用性。
4.安装web3js
npm install web3 -save
5.安装truffle
npm install -g truffle
truffle是节省程序员写多余代码的框架,通过truffle命令可以直接生成开发框架
安装过程如果出错建议更新一下nodejs
安装完成通过truffle -V检查版本
6.在remix中编写智能合约,并编译通过(新版本,需要翻墙:
Remix - Ethereum IDE 旧版本:
Remix - Ethereum IDE)
pragma solidity >=0.4.22 < 0.8.0;
//选择编译器版本,需要确保正在这个区间,并且需要大于0.4.22,低于这个版本有的关键词不能识别
contract Vote { //创建vote投票合约
uint public candidatesCount; //定义候选人数量
mapping(address => bool) public voters; //将投票人地址,映射成一个布尔值,用以判断该账户知否已经投过
struct Candidate { //定义一个结构体,包含候选人ID,名字,投票数
uint id;
string name;
uint voteCount;
}
mapping(uint => Candidate) public candidates; //通过ID来访问候选人
function addCandidate(string memory _name) private{ //定义添加候选人函数,并声明为私有的
candidatesCount ++; //添加一个就将候选人总数加一
candidates[candidatesCount] = Candidate(candidatesCount,_name,0); //将整个结构体赋予对应的候选人
}
constructor () public { //构造函数,添加候选人
addCandidate("张三");
addCandidate("李四");
}
function vote(uint _candidateId) public { //投票函数,传入输入框候选人对应的ID
require(!voters[msg.sender]); //检查该账户是否已投过票,msg.sender为调用合约地址
require(_candidateId > 0 && _candidateId <= candidatesCount); //检查所投的候选人是否在候选人名单中
voters[msg.sender] = true; //将调用合约账户设为已投
candidates[_candidateId].voteCount ++; //将所投候选人投票数加一
emit votedEvent(_candidateId);//发出事件
}
event votedEvent ( //定义事件,用以刷新界面
uint indexed _candidateId
);
}
7.在Dapp中创建模板工程
truffle unbox pet-port
此过程可能会出错,多试几次,大部分是因为网络问题
8.运行ganache-cli
ganache-cli
运行成功会生成10个合约账户以及对应的私钥,同时还有连接的本地端口
9.修改truffle-config.js
连接的端口和地址需要与ganache最终的一致
10.创建合约
vim Election.sol
将remix中的智能合约粘贴在这
11.编写前端页面,用以显示投票,修改src/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>投票 DApp </title>
<link href="css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container" style="width: 650px;">
<div class="row">
<div class="col-lg-12">
<h1 class="text-center">投票 DApp</h1>
<hr />
<br />
<div id="loader">
<p class="text-center">Loading...</p>
</div>
<div id="content" style="display: none;">
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">候选人</th>
<th scope="col">得票数</th>
</tr>
</thead>
<tbody id="candidatesResults">
</tbody>
</table>
<hr />
<form onSubmit="App.castVote(); return false;">
<div class="form-group">
<label for="candidatesSelect">选择候选人</label>
<select class="form-control" id="candidatesSelect">
</select>
</div>
<button type="submit" class="btn btn-primary">投票</button>
<hr />
</form>
<p id="accountAddress" class="text-center"></p>
</div>
</div>
</div>
</div>
<script src="js/jquery.min.js"></script>
<script src="js/bootstrap.min.js"></script>
<script src="js/web3.min.js"></script>
<script src="js/truffle-contract.js"></script>
<script src="js/app.js"></script>
</body>
</html>
12.修改src/js/app.js代码
App = {
web3Provider: null,
contracts: {},
account: '0x0',
hasVoted: false,
init: async function() {
return await App.initWeb3();
},
initWeb3: async function() {
if (window.ethereum) {
App.web3Provider = window.ethereum;
try {
await window.ethereum.enable();
} catch (error) {
console.error("User denied account access")
}
} else if (window.web3) {
App.web3Provider = window.web3.currentProvider;
}
else {
App.web3Provider = new Web3.providers.HttpProvider('http://localhost:8545');
}
web3 = new Web3(App.web3Provider);
return App.initContract();
},
initContract: function() {
$.getJSON("Election.json", function(election) {
App.contracts.Election = TruffleContract(election);
App.contracts.Election.setProvider(App.web3Provider);
App.listenForEvents();
return App.render();
});
},
listenForEvents: function() {
App.contracts.Election.deployed().then(function(instance) {
instance.votedEvent({}, {
fromBlock: 0,
toBlock: 'latest'
}).watch(function(error, event) {
console.log("event triggered", event)
App.render();
});
});
},
render: function() {
var electionInstance;
var loader = $("#loader");
var content = $("#content");
loader.show();
content.hide();
web3.eth.getCoinbase(function(err, account) {
if (err === null) {
App.account = account;
$("#accountAddress").html("Your Account: " + account);
}
});
App.contracts.Election.deployed().then(function(instance) {
electionInstance = instance;
return electionInstance.candidatesCount();
}).then(function(candidatesCount) {
var candidatesResults = $("#candidatesResults");
candidatesResults.empty();
var candidatesSelect = $('#candidatesSelect');
candidatesSelect.empty();
for (var i = 1; i <= candidatesCount; i++) {
electionInstance.candidates(i).then(function(candidate) {
var id = candidate[0];
var name = candidate[1];
var voteCount = candidate[2];
// Render candidate Result
var candidateTemplate = "<tr><th>" + id + "</th><td>" + name + "</td><td>" + voteCount + "</td></tr>"
candidatesResults.append(candidateTemplate);
// Render candidate ballot option
var candidateOption = "<option value='" + id + "' >" + name + "</ option>"
candidatesSelect.append(candidateOption);
});
}
return electionInstance.voters(App.account);
}).then(function(hasVoted) {
if(hasVoted) {
$('form').hide();
}
loader.hide();
content.show();
}).catch(function(error) {
console.warn(error);
});
},
castVote: function() {
var candidateId = $('#candidatesSelect').val();
App.contracts.Election.deployed().then(function(instance) {
return instance.vote(candidateId, { from: App.account });
}).then(function(result) {
// Wait for votes to update
$("#content").hide();
$("#loader").show();
}).catch(function(err) {
console.error(err);
});
}
};
$(function() {
$(window).load(function() {
App.init();
});
});
12.更新依赖
npm install
13.编译部署合约
truffle compile
truffle migrate
14.需要在谷歌浏览器中安装钱包插件,
Releases · MetaMask/metamask-extension · GitHub直接下载对应的版本,如果出错,选不了网络,可以下载其他版本地址如下
提取码:0w3n
解压添加到谷歌浏览器插件
15.在插件中导入账户,将ganache中生成的账户私钥输入
16.运行合约
npm run dev
17.在浏览器地址输入127.0.0.1:3000,会自动调出钱包,需要对钱包下一步即可