本讲将经过一些简单的例子从近处看以太坊DApp的开发细节。偷偷告诉你,本文会涉及到以太坊中的一个热门场景:“发币”,知足一下各位苦逼的开发当一回大佬的愿望 ;)前端
本文用到的开发工具:node
Nodegit
Trufflegithub
相关的包:web
yargs,cli库npm
web3,json-rpc抽象json
truffle-contract,合约抽象安全
openzeppelin-solidity,安全合约库微信
文章中建立的项目为一个“node + truffle”工程,对外提供cli。这个cli暴露了两条命令:app
$./app.js help app.js [命令]
命令:
app.js simple-data <action> [from] access simple-data contract from an [value] external address. app.js myico <command> [purchaser] commands about my ico. [value]
选项:
--version 显示版本号 [布尔] --help 显示帮助信息 [布尔]
选择以cli而非gui的方式做为dapp的前端主要的理由:
当前的重点是迅速掌握以太坊dapp开发的套路。
cli相比起gui来说,省去了不少麻烦事。并且,我对于gui的开发,实在兴趣不大。
那么,让咱们先来准备工程的架子:
mkdir 目录 && cd 目录
npm init
truffle init
npm install yargs --save
执行完成后,cli工程须要基本环境就都具有了。以后,在项目的工程根下建立app.js,它将做为整个工程的入口。而且工程采用Command Module(https://github.com/yargs/yargs/blob/master/docs/advanced.md#providing-a-command-module)的方式组织。
app.js的内容以下:
#!/usr/bin/env node require('yargs') .command(require('./simple-data.js')) .command(require('./myico.js')) .help() .argv
其中的两条命令以module方式组织,分别对应:
simple-data,简单合约交互
my i-c-o,i-c-o合约交互
simple-data命令展现了一个简单的前端和合约交互的例子,为编写复杂交互提供了参考。它的整个过程以下:
(1)npm install web3 --save
(2)npm install truffle-contract --save
(3)编写合约
pragma solidity ^0.4.23; contract SimpleData { address public owner; uint data; constructor() public { owner = msg.sender; } function set(uint x) public{ data = x; } function get() view public returns (uint) { return data; } }
(4)编写Migration文件
const SimpleData = artifacts.require("./SimpleData.sol"); module.exports = function(deployer) { deployer.deploy(SimpleData); };
(5)编写Command
const Web3 = require('web3'); const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:9545")); const contract = require('truffle-contract'); const SimpleDataContract = require('./build/contracts/SimpleData.json'); const simpleData = contract(SimpleDataContract); simpleData.setProvider(web3.currentProvider); if (typeof simpleData.currentProvider.sendAsync !== "function") { simpleData.currentProvider.sendAsync = function() { return simpleData.currentProvider.send.apply( simpleData.currentProvider, arguments ); }; } exports.command = 'simple-data <action> [from] [value]'; exports.describe = 'access simple-data contract from an external address.';exports.handler = function(argv) { if(argv.action == 'get') { simpleData.deployed().then(function(instance){ instance.get().then(function(result){ console.log(+result); }) }); } else if(argv.action == 'set') { if(!argv.value) { console.log('No value provided!'); return; } simpleData.deployed().then(function(instance){ instance.set(argv.value, {from: argv.from}).then(function(result){ console.log(result); }) }); } else { console.log('Unknown action!'); } }
说明:
“http://localhost:9545”对应“truffle develop”环境中的端口。
“./build/contracts/SimpleData.json”由“truffle compile”产生,这个json文件将和truffle-contract一块儿配合产生针对于合约的抽象。
“if(typeof ... !== "function") { ... }”这个if block是对于truffle-contract这个issue的walkaround。
随后的exports则是yargs command module的接口要求。对于命令:“simple-data <action> [from] [value]”,其格式由yargs解析:<...>,必填;[...],选填
编译部署以后,就能够简单的试用了(先给app.js可执行权限):
app.js simple-data get
app.js simple-data set 地址 值
接下来,就到了最让人期待的时刻:发币,更准确的说是基于ERC 20的代币发放。由于以太坊中各个币种有不一样的含义和用途,好比另外一个常见的币种:ERC 721,它发行的每一个token都是独一无二不可互换的,好比以太猫。
关于发币,究其本质就是实现特定的合约接口。从开发的投入产出比来说,我建议采用OpenZeppelin:
跟钱打交道的事情须要慎重,由不安全合约致使的问题家常便饭。
本身编写安全合约并不简单。
OpenZeppelin包含了当前安全合约的最简实践,其背后的公司自己就提供安全审计服务。
可复用合约代码加快了合约的开发。
如下是使用OpenZeppelin开发i-c-o的过程:
(1)npm install -E openzeppelin-solidity
(2)i-c-o的过程由两个合约组成,它们都将直接基于OpenZeppelin的合约完成。
coin,代币
crowdsale,众筹
(3)代币合约
pragma solidity ^0.4.23; import "../node_modules/openzeppelin-solidity/contracts/token/ERC20/MintableToken.sol"; contract MyCoin is MintableToken { string public name = "MY COIN"; // 代币名称 string public symbol = "MYC"; // 代币代码 uint8 public decimal = 18; // 位数 }
(4)众筹合约
pragma solidity ^0.4.23; import "../node_modules/openzeppelin-solidity/contracts/crowdsale/emission/MintedCrowdsale.sol"; import "../node_modules/openzeppelin-solidity/contracts/crowdsale/validation/TimedCrowdsale.sol"; contract MyCrowdsale is TimedCrowdsale, MintedCrowdsale { constructor ( uint256 _openingTime, uint256 _closingTime, uint256 _rate, address _wallet, MintableToken _token ) public Crowdsale(_rate, _wallet, _token) TimedCrowdsale(_openingTime, _closingTime) { } } 几行代码就完成了核心合约的开发,这要归功于我们选的框架,;)
(5)Migration脚本
const MyCoin = artifacts.require("./MyCoin.sol"); const MyCrowdsale = artifacts.require("./MyCrowdsale.sol"); module.exports = function(deployer, network, accounts) { const openingTime = web3.eth.getBlock('latest').timestamp + 2; const closingTime = openingTime + 3600; const rate = new web3.BigNumber(1000); const wallet = accounts[1]; deployer.deploy(MyCoin).then(function() { return deployer.deploy(MyCrowdsale, openingTime, closingTime, rate, wallet, MyCoin.address); }).then(function() { return MyCoin.deployed(); }).then(function(instance) { var coin = instance; coin.transferOwnership(MyCrowdsale.address); }); };
说明:
上面的合约定义很清楚地代表,众筹须要有Token的地址,所以众筹合约须要在Token部署成功以后进行。
众筹合约须要获得Token的全部权才能进行发行。一开始,Token的owner是部署者(这里是account[0]),所以在众筹合约部署完成以后须要完成Token全部权的移交。
最后一步很是关键,不然会出现相似下面的错误:
Error: VM Exception while processing transaction: revert at Object.InvalidResponse ... ...
(6)最后,就是my i-c-o的命令编写了。
const Web3 = require('web3'); const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:9545")); const contract = require('truffle-contract'); const MyCoin = require('./build/contracts/MyCoin.json'); const myCoin = contract(MyCoin); myCoin.setProvider(web3.currentProvider); if (typeof myCoin.currentProvider.sendAsync !== "function") { myCoin.currentProvider.sendAsync = function() { return myCoin.currentProvider.send.apply( myCoin.currentProvider, arguments ); }; } const MyCrowdsale = require('./build/contracts/MyCrowdsale.json'); const myCrowdsale = contract(MyCrowdsale); myCrowdsale.setProvider(web3.currentProvider); if (typeof myCrowdsale.currentProvider.sendAsync !== "function") { myCrowdsale.currentProvider.sendAsync = function() { return myCrowdsale.currentProvider.send.apply( myCrowdsale.currentProvider, arguments ); }; } exports.command = 'myico <command> [purchaser] [value]'; exports.describe = 'commands about my ico.'; exports.handler = function(argv) { if(argv.command == 'hasClosed') { myCrowdsale.deployed().then(function(instance){ instance.hasClosed().then(function(result){ console.log(result); }); }); } else if(argv.command == 'deliver') { myCrowdsale.deployed().then(function(instance){ instance.token().then(function(address){ instance.sendTransaction({from: argv.purchaser, value: web3.utils.toWei(argv.value.toString(), "ether")}) .then(function(result){ console.log('done!'); }).catch(function(error){ console.error(error); }); }); }); } else if(argv.command == 'totalSupply') { myCrowdsale.deployed().then(function(instance){ instance.token().then(function(address){ let coinInstance = myCoin.at(address); coinInstance.totalSupply().then(function(result){ console.log(+result); }); }); }); } else if(argv.command == 'balance') { myCrowdsale.deployed().then(function(instance){ instance.token().then(function(address){ let coinInstance = myCoin.at(address); coinInstance.balanceOf(argv.purchaser).then(function(balance){ console.log(+balance); }); }); }); } else { console.log('Unknown command!'); } }
有了前面simple-data命令代码的说明,这里的代码应该不会有任何难点了。其中的子命令:
hasClosed,众筹是否结束
totalSupply,众筹中产生的token总量
balance,某个帐户的代币数
deliver,给帐户发行代币
到这里,相信你们应该不会再以为“发币”有什么神秘的了,接下来如何将其应用到业务中,做为业务的催化剂(而不是割韭菜利器)就靠各位的想象力了。(注:本文只分享技术,不作任何项目及投资建议)
本文做者:HiBlock区块链技术布道群-胡键
原文发布于简书
原文连接:
https://www.jianshu.com/p/d78353772029
加微信baobaotalk_com,加入技术布道群
线上课程推荐