最近要找個H5的前端寫個簡單的DApp,聊過幾個H5的工程師,都被跟以太坊交互的部分嚇住了。雖然網上有N多的教程,可是對於H5工程師來講,仍是有些困難。分析其緣由,在於不瞭解ganache-cli(原來叫testrpc)/web3/以太坊節點/metamask之間的架構關係。html
梳理一下架構關係:前端
web3.js與以太坊通訊是經過rpc的方式實現的。node
以太坊節點原本提供了rpc的訪問方式,可是由於以太坊節點的地址不肯定,而且DApp須要訪問錢包,因此用web3.js直接訪問以太坊節點的rpc服務是不現實的。jquery
ganache-cli模擬了一個以太坊的測試節點並提供對外的rpc訪問方式(就是例子裏常常說的http://localhost:7545或者http://localhost:8545)。同時在其中內置了M個以太坊賬號,用於測試。web
MetaMask是一個以太坊的網絡錢包插件,它也提供了web3的訪問方式。並且能夠經過這個插件指定後面的以太坊節點是什麼。由於MetaMask是個錢包插件,因此解決了DApp中的支付問題。因此如今的DApp都依賴它。npm
有一個以太坊教程,是在線學習的,你們能夠去看看,若是本身本機上搞,開發DApp的基本過程都是同樣的以下:json
一、安裝NodeJSbootstrap
二、安裝truffle:一個開發DApp的開發框架數組
nmp install -g truffle
三、安裝Ganache(原來用testrpc):在內存中模擬以太坊運行並對外提供rpc服務。瀏覽器
npm install -g ganache-cli
四、運行ganache-cli
ganache-cli
五、生成一個DApp的項目
mkdir project1 truffle init
若是想用truffle中的某個例子,能夠用
truffle unbox pet-shop
「pet-shop」是例子名稱
六、編寫智能合約
具體如何用solidity編寫智能合約可參考各類文章,這裏再也不重複。
編寫好的智能合約的Project1.sol文件放到contracts目錄下
七、編譯和部署智能合約
在migrations目錄下建立文件2_deploy_contracts.js:
var Project1 = artifacts.require("Project1"); module.exports = function(deployer) { deployer.deploy(Project1); };
以後執行:
truffle compile truffle migrate
若是你的智能合約沒有問題的話,如今你的以太坊智能合約應該已經部署到你用來測試的ganache中去了。
這裏可能遇到的問題是:默認的truffle生成的項目,測試用的ganache的地址和端口會被設置成http://localhost:7545,而實際上執行ganache-cli以後的服務端口是http://localhost:8545,須要在truffle.js中修改一下:
module.exports = { // See <http://truffleframework.com/docs/advanced/configuration> // for more about customizing your Truffle configuration! networks: { development: { host: 「127.0.0.1」, port: 7545, //改爲8545 network_id: 「*」 // Match any network id } } };
八、編寫前端的js代碼跟以太坊交互
一般須要以下的輔助js庫:
<!– jQuery (necessary for Bootstrap’s JavaScript plugins) –> <script src=」js/jquery.min.js」></script> <!– Include all compiled plugins (below), or include individual files as needed –> <script src=」js/bootstrap.min.js」></script> <script src=」js/web3.min.js」></script> <script src=」js/truffle-contract.js」></script>
在此基礎上,編輯你本身業務邏輯的js,一般命名爲app.js,app.js的框架以下:
App = { web3Provider: null, contracts: {}, init: function() { //初始化你本身的頁面、變量等 return App.initWeb3(); }, initWeb3: function() { /* * 初始化web3: */ if (typeof web3 !== ‘undefined’){ //若是你的瀏覽器安裝了MetaMask的錢包插件,那麼插件會賦值web3.currentProvider App.web3Provider = web3.currentProvider; } else { //若是沒裝插件,那麼建立一個基於Http的provider,這裏用到的就是用ganache-cli啓動所提供的rpc服務,由於ganache-cli啓動的時候綁定的是localhost,因此測試所使用的瀏覽器也要在本機。(如何讓ganache-cli綁定其餘地址我還沒找到) App.web3Provider = new Web3.providers.HttpProvider(‘http://localhost:8545’); } web3 = new Web3(App.web3Provider); return App.initContract(); }, initContract: function() { /* * 初始化智能合約,實際上就是爲你的智能合約建立一個對應的js對象,方便後續調用 */ //一般的作法是使用你的智能合約編譯以後生成的abi的json文件,該文件在用truffle compile以後,生成在build/contracts/目錄下,由於我用了一個Division.sol,因此用Division.json,你能夠根據你的實際狀況來寫。 $.getJSON(‘Division.json‘, function(data) { var DivisionArtifact = data; App.contracts.Division = TruffleContract(DivisionArtifact); App.contracts.Division.setProvider(App.web3Provider); //用智能合約中的信息來更新你的web應用,App.refreshPlots()是我例子中獲取智能合約中信息並更新UI的函數 return App.refreshPlots(); }); return App.bindEvents(); }, bindEvents: function() { /* * 事件綁定,這個能夠根據你的UI來設置,例子中就是綁定一個button的點擊操做 */ $(document).on(‘click’, ‘.btn-adopt’, App.handlePlot); }, refreshPlots: function(plots, account) { /* * 這個函數就是上面initContract中調用的用智能合約更新頁面 */ //繼續使用division這個智能合約作例子 var divisionInstance; App.contracts.Division.deployed().then(function(instance){ divisionInstance = instance; //getGenPlots是Division的這個智能合約的一個查詢函數(不須要gas),須要3個參數 return divisionInstance.getGenPlots(0,0,2); }).then(function(results){ //注意:這個地方有點意思,我原先理解也有問題,後來打印輸出才搞明白 //智能合約返回的多個結果變量在這裏就是一個results數組 //數組的每一個成員就是智能合約返回的每一個結果變量 //以getGenPlots爲例,Division.json中定義以下: /*"name": "getGenPlots", "outputs": [ { "name": "", "type": "uint64[]" }, { "name": "", "type": "address[]" }, { "name": "", "type": "uint256[]" }, { "name": "", "type": "uint8[]" } ], "payable": false, "stateMutability": "view", "type": "function" */ //那麼:results[0]是uint64[] // results[1]是address[]... console.log(results[0].length); }).catch(function(err){ console.log(err.message); }); }, handlePlot: function(event) { /* * 這個函數就是上面bindEvents中調用的響應函數,演示要花eth的函數調用 */ event.preventDefault(); //從event中獲取參數,這是jquery的東西,跟web3無關 var plotId = parseInt($(event.target).data('id')); var divisionInstance; //獲取以太坊賬號信息 web3.eth.getAccounts(function(error,accounts){ if(error) { console.log(error); } //我隨便取賬號列表裏的第3個賬號。 //由於咱們連的是ganache-cli的rpc模擬服務, //其中給咱們預製了幾個有eth的賬號 //若是安裝了MetaMask插件,應該得到的就是MetaMask裏的賬號 var account = accounts[2]; App.contracts.Division.deployed().then(function(instance){ divisionInstance = instance; //調用智能合約的buyPlot函數,該函數須要2個參數, //後面的{}中的內容跟發起以太坊交易的時候所帶的默認值。 //from: 使用哪一個以太坊賬號 //value: 要使用的eth數量,以wei爲單位(1eth=10^18wei) //gas: 礦工費,以wei爲單位 return divisionInstance.buyPlot(plotId, 3, {from: account, value: 100000000000000000, gas:6000000}); }).then(function(result){ //返回結果後從新更新UI return App.refreshPlots(); }).catch(function(error){ console.log(error.message); }); }); } };
測試你的基於Web的DApp是否正常,可使用nodejs裏面提供的lite-server模塊來當簡單的webserver來用。
安裝lite-server,在你的truffle項目目錄下,執行:
npm install lite-server
安裝完以後會在項目目錄下聲稱node_modules目錄,lite-server以及依賴的模塊都在該目錄下了。
要運行lite-server,還須要編寫項目目錄下的package.json文件:
{ "name": "項目名稱", "version": "1.0.0", "description": "", "main": "truffle.js", "directories": { "test": "test" }, "scripts": { "dev": "lite-server", "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", "devDependencies": { "lite-server": "^2.3.0" }, "dependencies": { "liteserver": "^0.3.0" } }
還須要編寫bs-config.json來配置一下lite-server
{ "server": { "baseDir": ["./src", "./build/contracts"] } }
baseDir是用來設置lite-server所提供的web服務的文件路徑的。這個設置代表你能夠把你上面寫的app.js,依賴的各類js放到./src目錄下,而後寫index.html,把app.js等集成進去,就大功告成了。
啓動lite-server,執行:
npm run dev
不只啓動了lite-server,並且會啓動一個瀏覽器去打開頁面。
本文的目的是爲了澄清一下寫DApp的各項工具之間的架構關係,幫助技術人員更快的理解和實現本身的項目。
具體的例子網上多如牛毛,就不去寫業務的具體代碼了。
想先搞明白了,再本身玩的仍是推薦一下這個以太坊教程