基于React+truffle的完整智能合约构建

2018-02-22 11:17:42

内容:使用solidity的truffle框架开发智能合约,前端使用react框架,最终完成智能合约从前端到后端,从开发到部署的完整流程。

1、truffle安装

在nodejs安装完成的环境下,全局安装truffle:cnpm i -g truffle

Truffle Boxes是truffle框架集成的脚手架工具,可以使用这个脚手架快捷的生成完备的DAPP的项目结构,其中集成了前端视图、编译压缩工具等。可以在http://truffleframework.com/boxes/中查看并选择合适的模板来进行项目初始化。

2、项目初始化

这里我在http://truffleframework.com/boxes/上选择react模板来开发DAPP,直接在终端执行truffle unbox React即可完成项目的初始化,期间需要安装nodejs依赖,耐心等待即可(都没个安装进度提示,差评!)。

ludis@MacBook -> ~/Desktop/golang -> mkdir truffle && cd truffle
ludis@MacBook -> ~/Desktop/golang/truffle -> truffle unbox React
Downloading...
Unpacking...
Setting up...

项目初始化完成之后,目录结构如下:

.
├── box-img-lg.png
├── box-img-sm.png
├── config
│   ├── env.js
│   ├── jest
│   ├── paths.js
│   ├── polyfills.js
│   ├── webpack.config.dev.js
│   └── webpack.config.prod.js
├── contracts
│   ├── Migrations.sol
│   └── SimpleStorage.sol
├── migrations
│   ├── 1_initial_migration.js
│   └── 2_deploy_contracts.js
├── package.json
├── public
│   ├── favicon.ico
│   └── index.html
├── scripts
│   ├── build.js
│   ├── start.js
│   └── test.js
├── src
│   ├── App.css
│   ├── App.js
│   ├── App.test.js
│   ├── css
│   ├── fonts
│   ├── index.css
│   ├── index.js
│   └── utils
├── test
│   ├── TestSimpleStorage.sol
│   └── simplestorage.js
├── tmp-8766pK0xGOxZkgsX
│   └── box
├── truffle-config.js
└── truffle.js
  • contractsmigrations文件夹上篇已经介绍过,分别为solidity合约文件及相应配置文件。
  • src目录下是react前端代码。

3、合约编写、编译和部署

在项目初始化完成后,有一个默认的合约示例SimpleStorage.sol,我们直接用这个示例做测试:

pragma solidity ^0.4.18;

contract SimpleStorage {
  uint storedData;

  function set(uint x) public {
    storedData = x;
  }

  function get() public view returns (uint) {
    return storedData;
  }
}

这个合约代码是最简单的读和写两个操作,也是最基础最常用的操作。

编译过程非常简单,在项目根目录下执行truffle compile即可,会在根目录生成build编译目录。这里的编译与部署合约无关,只是为了后续前端代码的调用。

接着将合约部署到以太坊网络上,同理,可以选择部署到正式网络/测试网络/本地,这里我们选择测试网络,部署过程详见区块链学习 - solidity合约部署,小狐狸选择测试网络,将代码粘贴至左侧编辑器,右侧选择对应选项后即可部署成功,部署成功后我们便能得到部署的十六位的合约地址,以我部署的地址0x2dc69582315624fba54ae69655abea27fd48468e为例。

合约部署

4、前端与合约交互

上步部署完合约后,需要将合约地址记下,后续对合约的所有操作都需要通过合约地址进行。

src目录下便是react的前端代码,index.js作为入口文件:

import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

可以看到index.js中将App.js作为主组件引入,打开App.js,头部引用为:

import React, { Component } from 'react'
import SimpleStorageContract from '../build/contracts/SimpleStorage.json'
import getWeb3 from './utils/getWeb3'

通过头部引用可以看出前端与合约交互的流程:

  1. 首先前端使用了react框架;
  2. 第二句import SimpleStorageContract from '../build/contracts/SimpleStorage.json'导入的实际上是上步合约代码编译后产生的json文件,该文件中包含两个重要信息:一个是abi字段,通俗讲就是合约代码编译后产生的二进制接口,其中会声明合约代码暴露的可供调用的接口;另一个是bytecode字段,为合约代码的十六进制码。通过这两个重要信息就可以对一个合约进行相应操作。
  3. 导入了web3.jsweb3.js是以太坊提供的一个JavaScript库,他封装了以太坊的JSON RPC API,提供了一系列与区块链交互的JavaScript对象和函数,包括查看网络状态,查看本地账户、查看交易和区块、发送交易、编译/部署智能合约、调用智能合约等,其中最重要的就是与智能合约交互的API。

明白了前端与合约交互的原理,剩下来的就是使用react语法结合web3.js提供的接口对合约操作即可。

App.js文件进行一些修改,实现:在网页输入框中输入值,点击确定后可以修改合约中的值(写操作),写入成功后读取合约中的新值,并自动显示到网页(读操作),也就是通过前端网页对上述合约中定义的的set()get()两个方法的调用。

最终App.js如下,主要修改分以下几点:

  • 将部署完成的合约地址作为变量contractAddress保存,以供后续调用合约使用。
  • 声明simpleStorageInstance变量,作用是通过合约地址得到合约实例后将合约实例保存起来,方便后续调用。
  • 添加输入框及按钮,点击按钮时读取输入框的值,通过合约实例及web3提供的方法修改合约中的storedData值。
  • 使用Promise语法,当写操作成功后,执行读操作,并在读取成功后,修改react的组件状态state中的变量值,使前端显示自动更新
import React, { Component } from 'react'
import SimpleStorageContract from '../build/contracts/SimpleStorage.json'
import getWeb3 from './utils/getWeb3'

import './css/oswald.css'
import './css/open-sans.css'
import './css/pure-min.css'
import './App.css'

const contractAddress = "0x2dc69582315624fba54ae69655abea27fd48468e"; // 合约地址
var simpleStorageInstance; // 合约实例

class App extends Component {
	constructor(props) {
		super(props)

		this.state = {
			storageValue: 0,
			web3: null
		}
	}

	componentWillMount() {
		// Get network provider and web3 instance.
		// See utils/getWeb3 for more info.

		getWeb3
		.then(results => {
			this.setState({
				web3: results.web3
			})

			// Instantiate contract once web3 provided.
			this.instantiateContract()
		})
		.catch(() => {
			console.log('Error finding web3.')
		})
	}

	instantiateContract() {
		/*
		 * SMART CONTRACT EXAMPLE
		 *
		 * Normally these functions would be called in the context of a
		 * state management library, but for convenience I've placed them here.
		 */

		const contract = require('truffle-contract')
		const simpleStorage = contract(SimpleStorageContract)
		simpleStorage.setProvider(this.state.web3.currentProvider)

		// Declaring this for later so we can chain functions on SimpleStorage.


		// Get accounts.
		this.state.web3.eth.getAccounts((error, accounts) => {      
		
		// 获取合约地址获取合约实例并保存
		simpleStorage.at(contractAddress).then((instance) => {
				simpleStorageInstance = instance

				// Stores a given value, 5 by default.
				// return simpleStorageInstance.set(5, {from: accounts[0]})
				return;
			}).then((result) => {
				// Get the value from the contract to prove it worked.
				return simpleStorageInstance.get.call(accounts[0])
			}).then((result) => {
				// Update state with the result.
				console.log(result);
				return this.setState({ storageValue: result.c[0] })
			})
		})
	}

	render() {
		return (
			<div className="App">
				<nav className="navbar pure-menu pure-menu-horizontal">
						<a href="#" className="pure-menu-heading pure-menu-link">Truffle Box</a>
				</nav>

				<main className="container">
					<div className="pure-g">
						<div className="pure-u-1-1">
							<h1>Good to Go!</h1>
							<p>Your Truffle Box is installed and ready.</p>
							<h2>Smart Contract Example</h2>
							<p>If your contracts compiled and migrated successfully, below will show a stored value of 5 (by default).</p>
							<p>Try changing the value stored on <strong>line 59</strong> of App.js.</p>
							<p>The stored value is: {this.state.storageValue}</p>
						</div>
					</div>
				</main>

				<input ref="myvalue" className="myinput"/>
				<button
					className="mybutton"
					onClick={() => {
						var num = Number(this.refs.myvalue.value);
						console.log("点击了button");
						console.log(num);
						simpleStorageInstance.set(num, {from: this.state.web3.eth.accounts[0]}).then(() => {
							console.log("数据修改成功");
							simpleStorageInstance.get.call(this.state.web3.eth.accounts[0]).then((result) => {
								console.log("数据读取成功");
								console.log(result);
								// 修改状态变量的值,在react中,一旦状态变量的值发生变化,就会调用render函数重新渲染UI
								this.setState({ storageValue: result.c[0] })
							});

						})

					}}
					style={{height: 40,marginLeft: 50}}>修改合约数据</button>

			</div>
		);
	}
}

export default App

5、启动项目,查看效果

启动本地的react项目,直接终端执行npm start即可。会用node在本地启一个3000端口的服务,浏览器会自动跳转到http://localhost:3000,就会显示react编写的前端页面,在输入框修改相应数值,点击确定,然后小狐狸会弹出支付提示(记得小狐狸切换到测试网络)(所有的写操作均需要支付手续费),确认支付后耐心等待被打包成功,就能看到网页上数值的变化了。

本地测试

6、总结

一个完整的覆盖前后端的DAPP实际上就两点,跟传统互联网项目的前后端类似:

  1. 合约编写、部署
  2. 前端调用

基于以太坊开发DAPP实际上比较简单,重点是在合约的逻辑性、安全性上。从这也可以看出来以太坊生态的强大和完整,便捷完备的开发语言、工具,确实是目前继大饼之后最牛的项目。

solidity函数类型及truffle使用

1、solidity类的多继承、重写 solidity类具有多继承的特性: pragma solidity ^0.4.19 contract Animal1 { uint age; } contract Animal2 { string weight; } contract Dog is Animal1, Animal2 { /** Dog 会继承 Animal1 及 Animal2 两个类 */ } 重写与其他语言相通,即子类的同名函数会覆盖从父类继承的方法: pragma solidity ^ 0.4 .19; contract Animal { function testFunc() public pure returns(string) { return "Animal testFunc"; } } /** 子类重写了从父类继承过来的方法,会以子类的方法为基准

一个完整的智能合约—区块链上永存的留言

目标: 基于以太坊开发一个完整的DAPP应用,实现留言及随机展示留言功能。 技术栈:使用solidity语言的truffle框架,项目模板使用react,前端使用ES6语法。 项目线上地址: http://words.ldsun.com/ 项目代码:https://github.com/flute/blockChainWords 总结: 本次从零开发发哦部署上线约耗时六小时,前期在solidity合约开发上费时较多,主要是对其语言特性不了解,后续在react的动画上有些费时,源于对动画的生疏及类库的选择。 整个的开发流程比较清晰: 编写、调试合约 编写react前端页面、与合约交互的逻辑、显示逻辑、动画等 部署合约,与前端联调 编译项目,上传代码,配置nginx解析。 补充: 整个项目的的实现比较简单,这里不做具体分析,可以git查看,同时参考前几篇文章即可。这里贴一下solidity合约开发中开始的一些“美(错)好(误)想(代)法(码)