这是一篇Embark使用教程:通过本文可以学习到:1. 使用 Embark 创建项目 2. 利用 EmbarkJS 与合约交互 3. Embark 如果部署合约到主网(利用Infura节点)
前面我们基于Embark Demo 介绍了 Embark 框架,今天使用 Embark 来实实在在开发一个 DApp: 从零开发开发一个投票DApp。
之前我们也使用Truffle 开发过投票DApp,大家可以自行对比两个框架的优劣。
通过本文可以学习到:
本文使用的 Embark 版本是 5.2.3
> embark new embark-election
会在当前目录下生成一个 embark-election 目录,并创建好了相应的项目框架文件:如: app/
、contracts/
、config/
、embark.json
等。
我们需要在对应的目录中,添加相应的实现。
在contracts/
中添加合约Election.sol:
pragma solidity ^0.6.0;
contract Election {
// Model a Candidate
struct Candidate {
uint id;
string name;
uint voteCount;
}
mapping(address => bool) public voters;
mapping(uint => Candidate) public candidates;
// Store Candidates Count
uint public candidatesCount;
// voted event
event votedEvent (
uint indexed _candidateId
);
constructor () public {
addCandidate("Tiny 熊");
addCandidate("LearnBlockChain.cn");
}
function addCandidate (string memory _name) private {
candidatesCount ++;
candidates[candidatesCount] = Candidate(candidatesCount, _name, 0);
}
function vote (uint _candidateId) public {
// require that they haven't voted before
require(!voters[msg.sender]);
// require a valid candidate
require(_candidateId > 0 && _candidateId <= candidatesCount);
// record that voter has voted
voters[msg.sender] = true;
// update candidate vote Count
candidates[_candidateId].voteCount ++;
// trigger voted event
emit votedEvent(_candidateId);
}
}
之前有使用过Truffle开发过投票DApp,合约的代码完全一样,就不在解释。
Embark 合约部署的配置在 config/contracts.js
, 在 deploy
字段加入 Election 合约:
deploy: {
Election: {
}
}
现在运行 embark run
, Embark 会自动编译及部署Election.sol
到 config/blockchain.js
配置的 development
网络。
embark run
等价embark run development
。
blockchain.js
中 development
网络是使用 ganache-cli 启动的网络,其配置如下:
development: {
client: 'ganache-cli',
clientConfig: {
miningMode: 'dev'
}
}
embark启动后,我们可以在 COCKPIT 或 DashBoard 看到Election.sol
合约的部署日志,大概类似下面:
deploying Election with 351122 gas at the price of 1 Wei, estimated cost: 351122 Wei (txHash: 0x9da4dfb951149...d5c306dcabf300a4)
Election deployed at 0x10C257c76Cd3Dc35cA2618c6137658bFD1fFCcfA using 346374 gas (txHash: 0x9da4dfb951149ea4...d5c306dcabf300a4)
finished deploying contracts
Embark提供了一个 EmbarkJS的JavaScript库,来帮助开发者和合约进行交互。
在使用web3.js 时,和合约交互需要知道合约的ABI及地址来创建JS环境中对应的合约对象,一般代码是这样的:
// 需要ABI及地址创建对象
var myContract = new web3.eth.Contract([...ABI...], '0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe');
Embark 在编译部署后,每个合约会生成一个对应的构件Artifact
(可以在embarkArtifacts/contracts/
目录下找的这些文件),我们可以直接使用 Artifact
生成的合约对象调用合约。
一个构件通常会包含:合约的ABI、部署地址、启动代码及其他的配置数据。
查看一下Election.sol
对应的构件Election.js
代码就更容易理解:
import EmbarkJS from '../embarkjs';
let ElectionJSONConfig = {"contract_name":"Election","address":"0x10C257c76Cd3Dc35cA2618c6137658bFD1fFCcfA","code":"...", ... ,"abiDefinition":[...]};
let Election = new EmbarkJS.Blockchain.Contract(ElectionJSONConfig);
export default Election;
Election.js
最后一行导出了一个与合约同名的JavaScript 对象,下面看看怎么使用这个对象。
在使用embark new embark-election
创建项目时, 前端目录app/
下生成了一个 index.html
:
<html>
<head>
<title>Embark</title>
<link rel="stylesheet" href="css/app.css">
<script src="js/app.js"></script>
</head>
<body>
<h3>Welcome to Embark!</h3>
</body>
</html>
这里有一个地方需要注意一下,第5 6行引入了 css/app.css
, js/app.js
,而其实app/
下并没有这两个文件,这两个文件其实是按照 embark.json
配置的规程生成的。
embark.json
关于前端的配置如下:
"app": {
"css/app.css": ["app/css/**"],
"js/app.js": ["app/js/index.js"],
"images/": ["app/images/**"],
"index.html": "app/index.html"
},
"css/app.css": ["app/css/**"]
表示所有在app/css/
目录下的文件会被压缩到 dist目录的 css/app.css
,app/js/index.js
则会编译为js/app.js
,其他的配置类似。
我猜测embark 这样统一 css 及 js代码,可能是为了在IPFS之类的去中心化存储上访问起来更方便,在IPFS上传整个目录时,只能以相对路径去访问资源。欢迎留言和我交流。
接下来修改前端部分的代码,主要是在index.html
的body
加入一个table
显示候选人,以及加入一个投票框,代码如下(节选):
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">候选人</th>
<th scope="col">得票数</th>
</tr>
</thead>
<tbody id="candidatesResults">
</tbody>
</table>
<div class="form-group">
<label for="candidatesSelect">选择候选人</label>
<select class="form-control" id="candidatesSelect">
</select>
</div>
前端,我们使用了 bootstrap css ,把文件拷贝到app/css
目录下,接下来,看看关键的一步:前端如何与与合约交互。
创建项目时生成的app/js/index.js
生成了如下代码:
import EmbarkJS from 'Embark/EmbarkJS';
EmbarkJS.onReady((err) => {
// You can execute contract calls after the connection
});
这段代码里,EmbarkJS为我们准备了一个onReady回调函数,这是因为EmbarkJS会自动帮我们完成与web3节点的连接与初始化,当这些就绪后(调用onReady),前端就可以和链进行交互了。
大家也许会好奇EmbarkJS怎么知道我们需要连接那个节点呢?其实在config/contracts.js
有一个 dappConnection
配置项:
dappConnection: [
"$EMBARK",
"$WEB3", // 使用浏览器注入的web3, 如MetaMask等
"ws://localhost:8546",
"http://localhost:8545"
],
$EMBARK
: 是Embark在DApp和节点之前实现的一个代理,使用$EMBARK
有几个好处:
config/blockchain.js
配置于DApp交互的账号 accounts
。EmbarkJS 会从上到下,依次尝试 dappConnection
提供的连接,如果有一个可以连接上,就会停止尝试。
当 EmbarkJS 环境准备 onReady后,就可以使用构件Election.js
获取合约数据,如获取调用合约获取候选人数量:
import EmbarkJS from 'Embark/EmbarkJS';
import Election from '../../embarkArtifacts/contracts/Election.js';
EmbarkJS.onReady((err) => {
Election.methods.candidatesCount().call().then(count => console.log(" candidatesCount: " + count);
);
});
代码中直接使用构件导出的Election
对象,调用合约方法 Election.methods.candidatesCount().call()
, 调用合约方法与web3.js 一致。
了解了如何与合约交互,接下来渲染界面就简单了,我们把代码整理下,分别定义3个函数:
App.getAccount()
、App.render()
、App.onVote()
来获取当前账号(需要用来判断哪些账号投过票)、界面渲染、处理点击投标。
EmbarkJS.onReady((err) => {
App.getAccount();
App.render();
App.onVote();
});
App.getAccount()
的实现如下:
import "./jquery.min.js"
var App = {
account: null,
getAccount: function() {
web3.eth.getCoinbase(function(err, account) {
if (err === null) {
App.account = account;
console.log(account);
$("#accountAddress").html("Your Account: " + account);
}
})
},
}
在代码中,我们直接使用了web3对象,就是因为EmbarkJS帮我们进行了web3的初始化。
另外,我们引入jquery.min.js
来进行UI界面的渲染。
App.render()
的实现(主干)如下:
render: function () {
Election.methods.candidatesCount().call().then(
candidatesCount =>
{
var candidatesResults = $("#candidatesResults");
var candidatesSelect = $('#candidatesSelect');
for (var i = 1; i <= candidatesCount; i++) {
Election.methods.candidates(i).call().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);
});
}
});
}
App.onVote()
的实现(主干)如下:
onVote: function() {
$("#vote").click(function(e){
var candidateId = $('#candidatesSelect').val();
Election.methods.vote(candidateId).send()
.then(function(result) {
App.render();
}).catch(function(err) {
console.error(err);
});
});
}
使用 embark run
时,会为我们启动一个Geth 或 ganache-cli
的本地网络部署合约,以及在8000
端口上启用一个本地服务器来部署前端应用,我们在浏览器输入http://localhost:8000/
就可以看到DApp界面,如图:
当我们的DApp 在测试环境通过后,就可以部署到以太坊的主网。
要部署到主网,需要在blockchain.js
中添加一个主网网络,这里以测试网Ropsten网络为例:
ropsten: {
endpoint: "https://ropsten.infura.io/v3/d3fe47c...4f",
accounts: [
{
mnemonic: " 你的助记词 ",
hdpath: "m/44'/60'/0'/0/",
numAddresses: "1"
}
]
}
如果我们没有自己的主网节点,可以使用 endpoint 来指向以个外部节点,最常用的就是Infura。
添加好配置之后,使用build
命令来构建主网发布版本:
embark build ropsten # 最后是网络参数
所有的文件在生成在dist
目录下,把他们部署到线上服务器就完成了部署。
也可以使用embark upload ropsten
上传到IPFS。
本文配套代码已经到集市,可前往下载。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!