前面我们基于 Embark Demo1 介绍了 Embark 框架,今天使用 Embark 来实实在在开发一个 DApp:从零开发开发一个投票 DApp。

之前我们也使用 Truffle 开发过投票 DApp2,大家可以自行对比两个框架的优劣。

通过本文可以学习到:

  1. 使用 Embark 创建项目 2. 利用 EmbarkJS 与合约交互 3.Embark 如果部署合约到主网(利用 Infura 节点)

本文使用的 Embark 版本是 5.2.3

创建 Embark 项目

    > 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 开发过投票 DApp3,合约的代码完全一样,就不在解释。

Embark 合约编译部署

Embark 合约部署的配置在 config/contracts.js, 在 deploy 字段加入 Election 合约:

        deploy: {      Election: {      }    }

现在运行 embark run , Embark 会自动编译及部署 Election.solconfig/blockchain.js 配置的 development 网络。

embark run 等价 embark run development

blockchain.jsdevelopment 网络是使用 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 Artifacts

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 对象,下面看看怎么使用这个对象。

修改前端 index.html

在使用 embark new embark-election 创建项目时, 前端目录 app/ 下生成了一个 index.html

    ### Welcome to Embark!

这里有一个地方需要注意一下,第 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.cssapp/js/index.js 则会编译为 js/app.js,其他的配置类似。

我猜测 embark 这样统一 css 及 js 代码,可能是为了在 IPFS 之类的去中心化存储上访问起来更方便,在 IPFS 上传整个目录时,只能以相对路径去访问资源。欢迎留言和我交流。

接下来修改前端部分的代码,主要是在 index.htmlbody 加入一个 table 显示候选人,以及加入一个投票框,代码如下 (节选):

                #      | 候选人      | 得票数      
    ---|---|---  



    选择候选人

前端,我们使用了 bootstrap css ,把文件拷贝到 app/css 目录下,接下来,看看关键的一步:前端如何与与合约交互。

使用 Artifacts 与合约交互

EmbarkJS 连接 Web3

创建项目时生成的 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 有几个好处:

  1. 可以在 config/blockchain.js 配置于 DApp 交互的账号 accounts。2. 可以更友好的的看到交易记录。

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 = "" + id + "| " + name + "| " + voteCount + "  
    ";            candidatesResults.append(candidateTemplate);  
                // Render candidate ballot option            var candidateOption = "" + name + "";            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 界面,如图:

[教程] 使用 Embark 开发投票 DApp

当我们的 DApp 在测试环境通过后,就可以部署到以太坊的主网。

利用 Infura 部署到主网

要部署到主网,需要在 blockchain.js 中添加一个主网网络,这里以测试网 Ropsten 网络为例:

    ropsten: {    endpoint: "https://ropsten.infura.io/v3/d3fe47c...4f",    accounts: [      {        mnemonic: " 你的助记词 ",        hdpath: "m/44'/60'/0'/0/",        numAddresses: "1"      }    ]  }

如果我们没有自己的主网节点,可以使用 endpoint 来指向以个外部节点,最常用的就是 Infura[4]。

添加好配置之后,使用 build 命令来构建主网发布版本:

    embark build ropsten  # 最后是网络参数

所有的文件在生成在 dist 目录下,把他们部署到线上服务器就完成了部署。也可以使用 embark upload ropsten 上传到 IPFS。

References

[1] Embark Demo: https://learnblockchain.cn/article/566
[2] Truffle 开发过投票 DApp: https://learnblockchain.cn/2019/04/10/election-dapp
[3] Truffle 开发过投票 DApp: https://learnblockchain.cn/2019/04/10/election-dapp
[4] Infura: https://infura.io/

[教程] 使用 Embark 开发投票 DApp

来源链接:mp.weixin.qq.com