DApp是區塊鏈技術落地應用的主要體現方式,經過將友好的交互設計與其背後的智能合約相結合,就能產生一個個區塊鏈應用。javascript
開發一個DApp須要哪些技能?須要幾我的配合?其實,1我的分3步就能夠完成一個DApp!css
首先準備並部署開發環境html
其次編寫並部署智能合約前端
最後測試合約並建立交互java
HiBlock區塊鏈社區Bob在本身的摸索與實踐中開發了一款基於以太坊的寵物店,開發成功後,總結本身的經驗與心得,經過一次線上分享的活動,與區塊鏈愛好者交流探討。node
如下是Bob根據開發過程整理的流程文檔。ios
本地環境Macgit
這個教程會用到的環境:github
開發環境:node.js, npm (本文用到的node version, v9.11.1 npm version v5.6.0)web
編譯部署環境:truffle (version 4.1.5, solidity 0.4.21)
以太坊私鏈:ganache (version 1.1.0)
問題:
這裏碰到第一個坑,之前安裝過老版本node.js,提示必須升級新版本,但總提示:brew link error 沒法找到:/usr/local/Cellar/
解決方案:
uninstall, install, link 最終解決
安裝開發環境
BobJianglocal:pet-shop-tutorial bobjiang$ brew uninstall node --force
BobJianglocal:pet-shop-tutorial bobjiang$ brew uninstall npm --force BobJianglocal:pet-shop-tutorial bobjiang$ brew link node BobJianglocal:pet-shop-tutorial bobjiang$ brew install node BobJianglocal:pet-shop-tutorial bobjiang$
安裝truffle
BobJianglocal:pet-shop-tutorial bobjiang$ npm install -g truffle
安裝ganache
下載ganache(地址:http://truffleframework.com/ganache/)
BobJianglocal:pet-shop-tutorial bobjiang$ mkdir pet-shop-tutorial BobJianglocal:pet-shop-tutorial bobjiang$ cd pet-shop-tutorial/ BobJianglocal:pet-shop-tutorial bobjiang$ truffle unbox pet-shop
truffle框架目錄介紹:
contracts/ : 智能合約文件存在這裏,後綴.sol (solidity)
migrations/ : 部署腳本
test/ : 測試腳本
truffle.js :truffle的配置文件
在 contracts/ 目錄下建立 Adoption.sol 文件,內容以下:
pragma solidity ^0.4.17;
contract Adoption { address[16] public adopters;
//adopting a pet function adopt(uint petId) public returns (uint) { require(petId >= 0 && petId <= 15); adopters[petId] = msg.sender; return petId; }
//retrieve the adopters function getAdopters() public view returns (address[16]) { return adopters; } }
編譯合約
BobJianglocal:pet-shop-tutorial bobjiang$ truffle compile Compiling ./contracts/Adoption.sol... Compiling ./contracts/Migrations.sol...
Compilation warnings encountered:
/Users/bobjiang/Documents/hiblock.net/truffle/pet-shop-tutorial/contracts/Migrations.sol:11:3: Warning: No visibility specified. Defaulting to "public". function Migrations() { ^ (Relevant source part starts here and spans across multiple lines). ,/Users/bobjiang/Documents/hiblock.net/truffle/pet-shop-tutorial/contracts/Migrations.sol:15:3: Warning: No visibility specified. Defaulting to "public". function setCompleted(uint completed) restricted { ^ (Relevant source part starts here and spans across multiple lines). ,/Users/bobjiang/Documents/hiblock.net/truffle/pet-shop-tutorial/contracts/Migrations.sol:19:3: Warning: No visibility specified. Defaulting to "public". function upgrade(address new_address) restricted { ^ (Relevant source part starts here and spans across multiple lines).
Writing artifacts to ./build/contracts
上面有警告的地方,能夠忽略。(原來給的文件裏面沒有指定函數的可見性)
部署合約
一、在 migratios/ 目錄內建立新文件 2_deploy_contracts.js 內容以下:
var Adoption = artifacts.require("Adoption");
module.exports = function(deployer) { deployer.deploy(Adoption); };
二、確保安裝好了 ganache
安裝好後第一次啓動的界面以下(借用的源文檔圖片)
三、智能合約部署到以太坊(私鏈)上
BobJianglocal:pet-shop-tutorial bobjiang$ truffle migrate Using network 'development'.
Running migration: 1_initial_migration.js Deploying Migrations... ... 0x579459f9d2e89ed07356c7565056e082b540c5f441ffcdc1e4676f42536451d5 Migrations: 0x651ee6754b509e0f3413fcb6c88c6c20dc8c9d28 Saving successful migration to network... ... 0xfafeb069ba502196abeabef2c097bdd9e4db9ab02c98a9b98d8db47f7d205a9b Saving artifacts... Running migration: 2_deploy_contracts.js Deploying Adoption... ... 0xaee412f76fe2ed3853f8e138f009cd8fca23835547a39e23188affef55665460 Adoption: 0x104ba492f5d8f4e0df6971ae09ca0c9b496ff15b Saving successful migration to network... ... 0x9219eeba1a1eb945d4fe1fb1bf6cdb2b70218c22b264134cfd97e2f4dfe026ef Saving artifacts...
部署完成後,能夠看到有四筆交易(四個區塊):
智能合約能夠也能夠用測試類來進行斷言(assert)驗證。例如在 test/ 目錄內建立新文件 TestAdoption.sol 內容以下:
pragma solidity ^0.4.17;
import "truffle/Assert.sol"; import "truffle/DeployedAddresses.sol"; import "../contracts/Adoption.sol";
contract TestAdoption { Adoption adoption = Adoption(DeployedAddresses.Adoption());
//test adopt() function function testUserCanAdoptPet() public { uint returnedId = adoption.adopt(8);
uint expected = 8;
Assert.equal(returnedId, expected, "Adoption of Pet ID 8 should be recorded."); }
//test retrieve of a single pet owner function testGetAdopterAddressByPetId() public { //expected owner is this contract address expected = this; address adopter = adoption.adopters(8); Assert.equal(adopter, expected, "owner of pet id 8 should be recorded."); }
//test retrive of all pet owners function testGetAdopterAddressByPetIdInArray() public { //expected owner is this contract address expected = this; address[16] memory adopters = adoption.getAdopters();
Assert.equal(adopters[8], expected, "owner of pet id 8 should be recorded."); } }
運行測試,結果以下:
BobJianglocal:pet-shop-tutorial bobjiang$ pwd /hiblock.net/truffle/pet-shop-tutorial BobJianglocal:pet-shop-tutorial bobjiang$ truffle test Using network 'development'.
Compiling ./contracts/Adoption.sol... Compiling ./test/TestAdoption.sol... Compiling truffle/Assert.sol... Compiling truffle/DeployedAddresses.sol...
Compilation warnings encountered:
truffle/Assert.sol:1563:9: Warning: Use of the "var" keyword is deprecated. var nstr = _itoa(value, 10); ^------^ ,truffle/Assert.sol:1580:9: Warning: Use of the "var" keyword is deprecated. var nstr = _utoa(value, 10); ^------^ ,truffle/Assert.sol:1597:9: Warning: Use of the "var" keyword is deprecated. var nstr = _ltoa(value); ^------^ ,truffle/Assert.sol:1347:13: Warning: Invoking events without "emit" prefix is deprecated. TestEvent(true, ""); ^-----------------^ ,truffle/Assert.sol:1349:13: Warning: Invoking events without "emit" prefix is deprecated. TestEvent(false, message); ^-----------------------^
TestAdoption ✓ testUserCanAdoptPet (113ms) ✓ testGetAdopterAddressByPetId (103ms) ✓ testGetAdopterAddressByPetIdInArray (132ms)
3 passing (1s)
上面Assert.sol裏面有警告,var關鍵詞已經不建議使用。忽略掉。
用戶界面(UI)是前端工做,這裏用的javascript。主要文件是app.js,存在目錄 /src/js/app.js 中。文件內容以下:
App = { web3Provider: null, contracts: {},
init: function() { // Load pets. $.getJSON('../pets.json', function(data) { var petsRow = $('#petsRow'); var petTemplate = $('#petTemplate');
for (i = 0; i < data.length; i ++) { petTemplate.find('.panel-title').text(data[i].name); petTemplate.find('img').attr('src', data[i].picture); petTemplate.find('.pet-breed').text(data[i].breed); petTemplate.find('.pet-age').text(data[i].age); petTemplate.find('.pet-location').text(data[i].location); petTemplate.find('.btn-adopt').attr('data-id', data[i].id);
petsRow.append(petTemplate.html()); } });
return App.initWeb3(); },
initWeb3: function() { // Is there an injected web3 instance? if (typeof web3 !== 'undefined') { App.web3Provider = web3.currentProvider; } else { // If no injected web3 instance is detected, fall back to Ganache App.web3Provider = new Web3.providers.HttpProvider('http://localhost:7545'); } web3 = new Web3(App.web3Provider);
return App.initContract(); },
initContract: function() { $.getJSON('Adoption.json', function(data) { // Get the necessary contract artifact file and instantiate it with truffle-contract var AdoptionArtifact = data; App.contracts.Adoption = TruffleContract(AdoptionArtifact);
// Set the provider for our contract App.contracts.Adoption.setProvider(App.web3Provider);
// Use our contract to retrieve and mark the adopted pets return App.markAdopted(); });
return App.bindEvents(); },
bindEvents: function() { $(document).on('click', '.btn-adopt', App.handleAdopt); },
markAdopted: function(adopters, account) { var adoptionInstance;
App.contracts.Adoption.deployed().then(function(instance) { adoptionInstance = instance;
return adoptionInstance.getAdopters.call(); }).then(function(adopters) { for (i = 0; i < adopters.length; i++) { if (adopters[i] !== '0x0000000000000000000000000000000000000000') { $('.panel-pet').eq(i).find('button').text('Success').attr('disabled', true); } } }).catch(function(err) { console.log(err.message); }); },
handleAdopt: function(event) { event.preventDefault();
var petId = parseInt($(event.target).data('id'));
var adoptionInstance;
web3.eth.getAccounts(function(error, accounts) { if (error) { console.log(error); }
var account = accounts[0];
App.contracts.Adoption.deployed().then(function(instance) { adoptionInstance = instance;
// Execute adopt as a transaction by sending account return adoptionInstance.adopt(petId, {from: account}); }).then(function(result) { return App.markAdopted(); }).catch(function(err) { console.log(err.message); }); }); }
};
$(function() { $(window).load(function() { App.init(); }); });
安裝配置MetaMask
與Dapp互動的最容易的方式是經過 MetaMask
一、在瀏覽器內安裝MetaMask
二、裝好後,以Chrome瀏覽器插件形式存在
三、贊成,接受條款
四、以下圖,點擊"Import Existing DEN"
六、複製助記詞,粘貼到 MetaMask 界面中的 Wallet Seed 文本框中
七、設置一個密碼,點擊 OK
八、點擊 MetaMask 左上角的 Main Network ,點擊 Custom RPC
九、在 New RPC URL 中輸入 「http://127.0.0.1:7545」
十、返回 MetaMask 主界面,能夠看到帳戶信息
上面的第六步碰到第二個坑,這裏的助記詞必定用你的 ganache 私鏈上的,不然 MetaMask 錢包沒法連接到本地的私鏈,所以沒法顯示餘額
安裝配置 lite-server
在解開的目錄內,有 bs-config.jscon 和 package.json 兩個配置文件,沒必要要修改。(這裏定義了lite server,一下子瀏覽器訪問dapp用)
命令行啓動lite-server
BobJianglocal:pet-shop-tutorial bobjiang$ npm run dev
pet-shop@1.0.0 dev /Users/bobjiang/Documents/hiblock.net/truffle/pet-shop-tutorial lite-server
[Browsersync] Serving files from: ./src [Browsersync] Serving files from: ./build/contracts [Browsersync] Watching files... 18.04.08 15:20:02 200 GET /index.html
打開寵物店: http://localhost:3000
選擇一個你喜歡的狗狗,點擊 Adopt(領養)
MetaMask會自動提示,有一筆交易須要你贊成
點擊 Submit 返回寵物店
可能須要刷新頁面 http://localhost:3000,就能夠看到剛纔你領養的那隻狗,已經領養成功了。(其餘人沒法領養)
返回 MetaMask 錢包能夠看到剛纔的領養的交易
返回 ganache 也能夠看到剛纔的這筆交易被打包了
恭喜你,第一個 dapp 搞定啦!
truffle pet shop
地址:http://truffleframework.com/tutorials/pet-shop
點擊「閱讀原文」便可查看GitHub源文檔,Bob老師將於4月11日(週三)20:00-21:30進行線上操做演練分享,有興趣的小夥伴可識別下圖二維碼報名哦