本講將經過一些簡單的例子從近處看以太坊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,加入技術佈道羣
線上課程推薦