Truffle+Vue+Web3 1.0创建一个以太坊Dapp

参考资料

开发环境

  • Windows10
  • web3 1.0

编写第一个Solidity智能合约

一个简单的例子是编写一个可以注册,保存社区成员信息和简单电子钱包功能的账户合约

pragma solidity ^0.4.24;
import "./SafeMath.sol";//开源的安全操作unit256的合约

contract Account{
    using SafeMath for uint256;
    //新成员创建事件
    event NewMember(string _name, string _avator);
    //成员信息结构
    struct Member {
        string name;//名字
        string avatar;//头像
        bool isExist;//是否注册
        uint256 balance;//可周转余额
    }
    //地址到成员信息的mapping
    mapping(address => Member) internal addressToMember;
    //限制调用的条件
    modifier onlyMemberOf(address _from){
        require(addressToMember[_from].isExist);
        _;
    }
    // 注册
    function registerMember(string _name, string _avatar) public {
        require(!isMemberOf());
        addressToMember[msg.sender] = Member(_name, _avatar, true, 0);
        emit NewMember(_name, _avatar);
    }

    // 判断是否注册
    function isMemberOf() public view returns (bool) {
        return addressToMember[msg.sender].isExist;
    }

    // 获取个人信息
    function getMemberInfo() public view onlyMemberOf(msg.sender)
      returns (string name, string avatar, uint256 balance) {
        return (addressToMember[msg.sender].name,
                addressToMember[msg.sender].avatar,
                addressToMember[msg.sender].balance);
    }
    //获取当前合约中的总余额
    function getTotalBalance() public view returns (uint256) {
        return address(this).balance;
    }
    //取出可周转余额
    function withdraw(uint256 amount) public onlyMemberOf(msg.sender)
      returns (uint256) {
        require(address(this).balance >= amount);
        addressToMember[msg.sender].balance = addressToMember[msg.sender].balance.sub(amount);
        msg.sender.transfer(amount);
        return addressToMember[msg.sender].balance;
    }
}

在Remix上部署合约

  • 安装Ganache
  • 使用Ganache本地测试:安装MetaMask Chrome插件, 选择Custom RPC创建RPC连接到http://127.0.0.1:7545
  • 或者要部署到私有节点,可以使用命令行,然后选择Custom RPC创建RPC连接到http://127.0.0.1:8545
    $ geth --identity "MY Etherum" --rpc --rpccorsdomain "*" --datadir data --port "8545" --rpcapi "db,eth,net,web3,personal" --networkid 666 console
    
  • 打开Remix在线编译,在compile选择合适的编译版本编译,在run选择Deploy部署得到合约地址
  • 可以在Remix上测试我们的合约(注册后getMemberInfo可以用户信息,isMemberOf为true)

  • 合约部署和测试成功后,将ABI和Deployed Contracts复制保存到本地文件夹(注意Ganache得到的地址是临时的,下次打开就会失效)

配置Truffle+Vue项目

  • 环境配置
    在windows下需要先安装node.js, 建议使用Git Bash或者PowerShell执行命令:
      $ npm install -g -production windows-build-tools
      $ npm install -g ganache-cli
      $ npm install -g truffle
      $ npm install -g vue-cli
    
  • Vue项目安装
      $ vue init webpack ecourse // vue init webpach + 你的项目名
      $ cd ecourse
      $ npm install --save element-ui vue-router vuex web3@1.0.0-beta.36 web3-net@1.0.0-beta.36
    
  • 添加文件及文件夹:① contracts放置.sol合约; ② store放置Vuex状态控制代码;③ util放置工具函数,util/constant放置上一步中编译好的合约地址和ABI,util/config放置一些配置

完善项目

具体的配置可以参考博客使用 Web3 和 Vue.js 来创建你的第一个以太坊 dAPP,这里我主要指出使用web3 1.0 标准的不同配置

  • getWeb3.js

    import Web3 from 'web3'
    let getWeb3 = new Promise(function (resolve, reject) {
    var web3js = window.web3;
    var web3Provider;
    if (typeof web3js !== 'undefined') {
        web3Provider = web3js.currentProvider;
    } else {
        web3Provider = new Web3.providers.HttpProvider('http://127.0.0.1:7545');
    }
    var web3 = new Web3(web3Provider);
    resolve({
        injectedWeb3: web3.eth.net.isListening(), // 新的api
        web3() {
            return web3
        }
    })
    })
    .then(result => {
        return new Promise(function (resolve, reject) {
          result.web3().eth.net.getId((err, networkId) => { // 新的api
              if(err) {
                reject(new Error('Unable to retrieve network ID'))
              } else {
                console.log('retrieve newworkId: ' + networkId)
                result = Object.assign({}, result, {networkId})
                resolve(result)
              }
          })
        })
    })
    .then(result => {
        return new Promise(function (resolve, reject) {
          result.web3().eth.getCoinbase((err, coinbase) => {
              if(err) {
              reject(new Error('Unable to retrieve coinbase'))
          } else {
          coinbase = result.web3().utils.toChecksumAddress(coinbase);
              console.log('retrieve coinbase: '+ coinbase);
              result = Object.assign({}, result, {coinbase});
              resolve(result)
          }})
        })
    });
    
    export default getWeb3
    
  • pollWeb3.js(web3 1.0 添加了新的api能够监听账户地址的变化,不需要使用setIntervel进行轮询)
    import Web3 from 'web3'
    import {store} from '../store/'
    let web3 = window.web3;
    web3 = new Web3(web3.currentProvider);
    web3.currentProvider.publicConfigStore.on('update', ({selectedAddress, networkVersion}) => {
    store.dispatch('pollWeb3', {
      coinbase: selectedAddress
    })
    });
    
  • getContract.js
    import Web3 from 'web3'
    import {address, ABI} from './constant/ecourse_abi'
    import {store} from '../store/'
    let getContract = new Promise(function(resolve, reject) {
    let web3 = new Web3(window.web3.currentProvider);
    let ecourseContractInstance =  new web3.eth.Contract(ABI, address);//新的api
    if (!ecourseContractInstance) {
      reject("no contract instance build")
    }
    resolve(ecourseContractInstance);
    });
    export default getContract
    

Dapp调用合约函数

  • App.vue(我们可以在入口文件注册web3和contract)
    async beforeCreate() {
      if(!this.$store.state.web3.web3Instance) {
      await this.$store.dispatch('registerWeb3');
      await this.$store.dispatch('getContractInstance');
      }
    },
    
  • web3 1.0调用函数使用methods
    // 为避免报错,可以在调用合约函数之前,进行一个判断
    if(typeof this.$store.state.contractInstance !== "function") {
      await this.$store.dispatch('getContractInstance');
    }
    // 一个调用函数的例子
    this.$store.state.contractInstance().methods.withdraw(this.formInline.balance)
      .send({from:this.$store.state.web3.coinbase, gas: 300000})
      .on('receipt', receipt => {
          this.$message('取款成功');
      })
      .on('error', error => {
          this.$message('取款失败');
      })
    // 另一个调用函数的例子
    this.$store.state.contractInstance().methods.getMemberInfo()
    .call({from: state.web3.coinbase}) //注意!!!!!from不能省略,因为metamask默认的msg.sender是accounts[0]
    .then(res => {
      console.log('account info: ' + res);
    })
    .catch(error => {
      console.log(error);
    })
    

其他

  • 之前没有学习过vue项目的,可以从一个最简单的Truffle PetShop项目学起,可以很快搭建并看到一个合约怎样调用。
  • 学习solidity的很好的网站:cryptozombies,大概两天可以把所有lesson过一遍,基本上编写合约就没什么问题了
  • 可以在truffle官网上找到很多框架,直接unbox使用,不过我觉得自己配置使用起来比较容易

我的项目地址:Github
有问题,可以随时提问


 上一篇
【博客分享】新的一年前端应该怎么学 【博客分享】新的一年前端应该怎么学
总结过去,展望未来 前端2018解读:吃瓜又一年 来自HackerRank的数据报告 JavaScript: 2018’s most popular language Languages developers want to lear
2019-02-06
下一篇 
Scrapy实战:爬取期刊遇到的坑与技术总结 Scrapy实战:爬取期刊遇到的坑与技术总结
参考博客及资料 【python2.7】爬取知网论文 python实现CNKI知网爬虫 《Python3网络爬虫开发实战》崔庆才 最近要写一个数据分析的项目,需要根据关键词爬取近十年期刊的主要信息,记录一下爬取过程中遇到的问题 分析我们要
2018-12-03
  目录