1

以太坊实践整理(一)区块链基础知识
以太坊实践整理(二)Geth客户端
以太坊实践整理(三)Remix开发部署智能合约
以太坊实践整理(四)Truffle智能合约开发框架
以太坊实践整理(五)DApp开发全过程记录(上)
以太坊实践整理(五)DApp开发全过程记录(下)
以太坊实践整理(六)文件去中心化存储


说明:本篇所涉及的DAPP众筹需求及合约实现来自登链社区

通过前面的内容,我们大致了解了如何使用Ganache + Truffle开发智能合约,一个完整的应用还包括用户界面。我们把构建在智能合约之上的应用成为DAPP,即去中心化应用。

DAPP开发

常规互联网应用是前端请求中心化服务器,服务器同步响应数据。DAPP则是前端请求去中心化网络中的任意节点,节点收到交易请求后,广播到整个网络,在网络中达成共识从而完成交易。

在DAPP应用中,发送给节点的请求称为“交易”,需要关联钱包进行签名之后才能发送给节点;另外,交易因为需要等待网络共识,所以大多数是异步的,一般通过事件回调获取结果。

前端与智能合约的通信
开发DAPP应用,最重要的两部分就是前端应用及智能合约。智能合约运行在以太坊虚拟机(EVM)上,前端调用智能合约是通过向节点发起请求完成的。前端部分同互联网前端应用一样,可以使用任何自己擅长的前端框架如Vue或React来开发,然后通过web3.js函数库去调用智能合约。以太坊提供了与节点交互的JSON-RPC接口,Web3函数库是JSON-RPC的封装,主流语言都有Web3的实现,JavaScript是web3.js,java是web3j。

众筹项目需求分析

假设我准备协作一本书,但是不确定多少人愿意购买。于是,我发起一个众筹,如果在一个月内,能筹集到10个ETH,就进行写作,并且众筹参与用户每人赠送一本,如果未能筹到足够的资金,用户可以取回投入的资金。同时为了让用户积极参与,设置了一个阶梯价格,初始时,参与众筹的价格非常低(0.02ETH),每筹集满1个ETH时,价格上涨0.002ETH。
从需求可以归纳出合约三个对外的动作(函数):

  1. 用户汇款进合约,通过实现合约的退回函数来实现;
  2. 用户赎回汇款,这个函数需要在众筹未达标之后,由用户本人调用生效;
  3. 发起者提取资金,这个函数需要在众筹达标之后,由发起者调用。

除此之外,进一步梳理逻辑,发现还需要保存一些状态变量以及添加相应的逻辑:

  1. 记录用户众筹的金额,可以使用一个mapping类型来保存;
  2. 记录当前众筹的价格,价格可以使用一个mapping类型来保存;
  3. 记录合约众筹的截止时间,用uint类型来保存截止时间,可以在构造函数中使用当前时间加上30天作为截止时间;
  4. 记录众筹的受益者,用address类型记录,在构造函数中记录合约创作者;
  5. 记录当前众筹状态(是否已经关闭),如果众筹达标(创作者提取资金时应及时关闭状态)之后,就需要阻止用户参与。

创建前端应用

前端我们使用vue开发,如果对Vue.js不了解,建议先阅读Vue.js官方教程。

Vue CLI没安装的先安装:

npm install -g @vue/cli

创建crowdfunding前端工程:

vue create crowdfunding

实现众筹合约

编写智能合约

truffle初始化

cd crowdfunding
truffle init

在contracts下创建Crowdfunding.sol:

pragma solidity >=0.6.0 <0.7.0;
contract Crowdfunding {
    // 创作者
    address public author;
    // 参与金额
    mapping(address => uint) public joined;
    // 众筹目标
    uint constant Target = 10 ether;
    // 众筹截止时间
    uint public endTime;
    // 记录当前众筹价格
    uint public price = 0.02 ether;
    // 作者提取资金之后,关闭众筹
    bool public closed = false;
    // 部署合约时调用,初始化作者及众筹结束时间
    constructor() public {
        author = msg.sender;
        endTime = now + 30 days;
    }
    // 更新价格,这是一个内部函数
    function updatePrice() internal {
        uint rise = address(this).balance / 1 ether * 0.002 ether;
        price = 0.02 ether + rise;
    }
    // 用户向合约转账时,触发的回调函数
    receive() external payable {
        require(now < endTime && !closed , "众筹已结束");
        require(joined[msg.sender] == 0, "你已经参与过众筹");
        require(msg.value >= price, "出价太低了");
        joined[msg.sender] = msg.value;
        updatePrice();
    }
    // 作者提取资金
    function withdrawFund() external {
        require(msg.sender == author, "你不是作者");
        require(address(this).balance >= Target, "未达到众筹目标");
        closed = true;
        msg.sender.transfer(address(this).balance);
    }
    // 读者赎回资金
    function withdraw() external {
        require(now > endTime, "还未到众筹结束时间");
        require(!closed, "众筹达标, 众筹资金已提取");
        require(Target > address(this).balance, "众筹达标,你没法提取资金");
        msg.sender.transfer(joined[msg.sender]);
    }
}

编译智能合约

truffle compile

部署智能合约

在migrations下创建部署脚本,2_crowfunding.js:

const crowd = artifacts.require("Crowdfunding");

module.exports = function (deployer) {
  deployer.deploy(crowd);
};

在truffle-config.js配置要部署的网络,同时确保Ganache已运行,执行智能合约部署:

truffle migrate

众筹前端实现

Vue创建的脚手架工程,默认会有个HelloWorld.vue组件,我们写一个自己的CrowdFund.vue组件,把App.vue中的HelloWorld.vue替换掉。

App.vue修改为:

<template>
  <div id="app">
    <CrowdFund/>
  </div>
</template>

<script>
import CrowdFund from './components/CrowdFund.vue'

export default {
  name: 'App',
  components: {
    CrowdFund
  }
}
</script>

然后在CrowdFund.vue中完成众筹界面及相应逻辑,界面需要显示以下几个部分:

  1. 当前众筹到的金额;
  2. 众筹的截止时间;
  3. 当前众筹的价格,参与众筹按钮;
  4. 如果是已经参与,显示其参与的价格以及赎回按钮;
  5. 如果是创作者,显示一个提取资金的按钮。

CrowdFund.vue修改如下:

<template>
<div class="content">
  <h3> 新书众筹</h3>
  <span>以最低的价格获取我的新书 </span>

  <!-- 众筹的总体状态  -->
  <div class="status">
    <div v-if="!closed">已众筹资金:<b>{{ total }} ETH </b></div>
    <div v-if="closed"> 众筹已完成 </div>
    <div>众筹截止时间:{{ endDate }}</div>
  </div>

  <!-- 当读者参与过,显示如下div  -->
  <div v-if="joined" class="card-bkg">
    <div class="award-des">
      <span> 参与价格 </span>
      <b> {{ joinPrice }} ETH </b>
    </div>

    <button :disabled="closed" @click="withdraw">赎回</button>
  </div>

  <!--  当读者还未参与时,显示如下div  -->
  <div v-if="!joined" class="card-bkg">
    <div class="award-des">
      <span> 当前众筹价格 </span>
      <b> {{ price }} ETH </b>
    </div>

    <button :disabled="closed" @click="join">参与众筹</button>
  </div>

  <!--  如果是创作者,显示 -->
  <div class="box" v-if="isAuthor">

    <button :disabled="closed" @click="withdrawFund"> 提取资金</button>
  </div>

</div>
</template>

继续编写JavaScript逻辑部分,与合约交互需要用到truffle-contract及web3,先安装:

npm install --save truffle-contract web3

CrowdFund.vue修改如下:

<script>
import Web3 from "web3";
import contract from "truffle-contract";
import crowd from '../../build/contracts/Crowdfunding.json';

export default {
  name: 'CrowdFund',
  data() {
    return {
      price: null,
      total: 0,
      closed: true,
      joinPrice: null,
      joined: false,
      endDate: "null",
      isAuthor: true,
    };
  },

  // 当前Vue组件被创建时回调的hook 函数
  async created() {
    // 初始化web3及账号
    await this.initWeb3Account()
    // 初始化合约实例
    await this.initContract()
    // 获取合约的状态信息
    await this.getCrowdInfo()
  },

  methods: {

    // 初始化 web3及账号
    async initWeb3Account() {
      if (window.ethereum) {
        this.provider = window.ethereum;
        try {
          await window.ethereum.enable();
        } catch (error) {
          //   console.log("User denied account access");
        }
      } else if (window.web3) {
        this.provider = window.web3.currentProvider;
      } else {
        this.provider = new Web3.providers.HttpProvider("http://127.0.0.1:7545");
      }
      this.web3 = new Web3(this.provider);
      this.web3.eth.getAccounts().then(accs  => {
        this.account = accs[0]
      });
    },

    // 初始化合约实例
    async initContract() {
      const crowdContract = contract(crowd);
      crowdContract.setProvider(this.provider);
      this.crowdFund = await crowdContract.deployed();
    },

    // 获取合约的状态信息
    async getCrowdInfo() {
      // 获取合约的余额
      this.web3.eth.getBalance(this.crowdFund.address).then(
        r => {
          this.total = this.web3.utils.fromWei(r)
        }
      );
      // 获取读者的参与金额, joined 在合约中是public 的状态变量,自动生成相应的访问器函数
      this.crowdFund.joined(this.account).then(
        r => {
          if (r > 0) {
            this.joined = true
            this.joinPrice = this.web3.utils.fromWei(r)
          }
        }
      );
     // 获取合约的关闭状态
      this.crowdFund.closed().then(
        r => this.closed = r
      );
      // 获取当前的众筹价格
      this.crowdFund.price().then(
        r => this.price = this.web3.utils.fromWei(r)
      );
      // 获取众筹截止时间
      this.crowdFund.endTime().then(r => {
        var endTime = new Date(r * 1000)
        // 把时间戳转化为本地时间
        this.endDate = endTime.toLocaleDateString().replace(/\//g, "-") + " " + endTime.toTimeString().substr(0, 8);
      });
      // 获取众筹创作者地址
      this.crowdFund.author().then(r => {
        if (this.account == r) {
          this.isAuthor = true
        } else {
          this.isAuthor = false
        }
      });
    },

    // 读者点击参与众筹时调用
    join() {
      this.web3.eth.sendTransaction({
        from: this.account,
        to: this.crowdFund.address,
        value: this.web3.utils.toWei(this.price)
      }).then(() =>
        this.getCrowdInfo()
      );
    },

    // 赎回
    withdraw() {
      this.crowdFund.withdraw(
        this.crowdFund.withdraw({
          from: this.account
        }).then(() => {
          this.getCrowdInfo()
        })
      );
    },

    // 提取资金
    withdrawFund() {
      this.crowdFund.withdrawFund({
        from: this.account
      }).then(() => {
        this.getCrowdInfo()
      })
    },

  }
}
</script>

到之类,众筹案例就全部完成了。

DAPP运行

在项目目录运行应用:

npm run serve

浏览器地址访问http://localhost:8080即可体验该DAPP:

截屏2021-09-15 下午12.58.28.png

注意要确保MetaMask连接的网络和合约部署的网络一致,这样子DAPP才能通过web3获取到合约数据

DAPP发布

npm run build

dist目录下,构建出用户发布的完整前端代码。拷贝到公网服务器对外服务即可。


以太坊实践整理(一)区块链基础知识
以太坊实践整理(二)Geth客户端
以太坊实践整理(三)Remix开发部署智能合约
以太坊实践整理(四)Truffle智能合约开发框架
以太坊实践整理(五)DApp开发全过程记录(上)
以太坊实践整理(五)DApp开发全过程记录(下)
以太坊实践整理(六)文件去中心化存储


zhutianxiang
1.5k 声望327 粉丝