開發(fā)一個去中心化應(yīng)用Dapp-寵物領(lǐng)養(yǎng)

1. 參考資料

http://truffleframework.com/tutorials/pet-shop
這是一個使用以太坊開發(fā)的完整的去中心化應(yīng)用(Dapp)替废,出自tuffle suite箍铭。
Pete有一個寵物店,養(yǎng)了16只寵物椎镣,他想開發(fā)一個去中心化應(yīng)用诈火,讓大家來領(lǐng)養(yǎng)寵物。
應(yīng)用效果如下:

屏幕快照 2018-02-09 上午11.00.25 (2).png

2. 環(huán)境搭建

  • 安裝nodejs
  • 安裝truffle
  • 安裝ganache-cli
  • 安裝MetaMask

3. 創(chuàng)建項目

感謝truffle unbox衣陶,你不必從頭開始創(chuàng)建項目柄瑰。

truffle unbox pet-shop

這一步需要稍等一會闸氮,truffle會自動下載pet-shop項目,項目目錄如下:


屏幕快照 2018-02-09 上午11.05.31.png
  • contracts 存放智能合約教沾;
  • migrations 存放部署腳本蒲跨;
  • test 存放測試用例;
  • truffle.js 配置文件授翻,例如區(qū)塊鏈地址和端口;

4. 編寫智能合約

在contracts目錄下或悲,添加合約文件Adoption.sol

pragma solidity ^0.4.17;

contract Adoption {

  address[16] public adopters;  // 保存領(lǐng)養(yǎng)者的地址

    // 領(lǐng)養(yǎng)寵物
  function adopt(uint petId) public returns (uint) {
    require(petId >= 0 && petId <= 15);  // 確保id在數(shù)組長度內(nèi)

    adopters[petId] = msg.sender;        // 保存調(diào)用這地址
    return petId;
  }

  // 返回領(lǐng)養(yǎng)者
  function getAdopters() public view returns (address[16]) {
    return adopters;
  }

}

5. 編譯智能合約

truffle compile

6. 啟動以太坊客戶端

yangang:~ yangang$ ganache-cli
Ganache CLI v6.0.3 (ganache-core: 2.0.2)

Available Accounts
==================
(0) 0xebbc700bb15e3aa969f9d8ece9090aeb262cbf6e
(1) 0x940ead554300f8b0a40530f6f77b138d24b9359b
(2) 0xf47f7cd72e1377a6220bf512f5690d1027313dee
(3) 0x1c22e4df38807b47f26ad1f25503c23947e9e7e1
(4) 0xb8cf54c3ebdf06ae887222c23930bad155453891
(5) 0xc8232b9bc251ab9c393071747616e0a831b143ec
(6) 0x01c05db44cfbb7bf7b5671cc0e197e72607f0166
(7) 0xa08e0b2855556167afd44c63c6429c8372a6cf01
(8) 0x18ca58212d87cca4842471475ada5d78183d7fdb
(9) 0xcb465d0d7a7b067c7b0c8272078710bc4937d134

Private Keys
==================
(0) 0eb629b963b89ef28272b68437cf65f52a92a963188fa41ed86578158388f0f5
(1) a9c3f1332bd3d7b42b45c6aec59f2d966478c831ca4e71e0ebd251d248cfecf4
(2) b50d1f99eca8a322c2968265a48017be275fcf1f8e9f6ff382b9d1d4c4495443
(3) 6a14cbcb204156880cf663557cf0f919aa297ddf9bed83afc7c4bfe2a1aa5aef
(4) af1c52412f92380d7536324106875e0f30106fa9e50da3e0336eef17779da3ca
(5) 13d45f5707aab605bc42c411aa455c77b7346371609b3606bf001a6359a3a37c
(6) 3763c2a208e5c690a187e8228344a7ff6f25494ed5af907f0c14660074f9484d
(7) 230a4a6beb106e20f5204b14a3ce293e240ffcdf58c8c349ea297af446724c43
(8) 80410ba728a87b877921a8614cd802e5f06c638bf38d3df2b774bc2ecb969ea8
(9) 0f0b78f8b517f065e8a57d4ddcfbc6b3dcfa480f70a51bdd260d7cf530a543be

HD Wallet
==================
Mnemonic:      powder omit enforce twelve student rigid large caution eternal sibling pepper human
Base HD Path:  m/44'/60'/0'/0/{account_index}

Listening on localhost:8545

注意這里的MnemonicAccounts,后面MetaMask需要用到堪唐。

7. 部署合約(migrate)

在migrations目錄下巡语,創(chuàng)建一個部署腳本2_deploy_contracts.js:

var Adoption = artifacts.require("Adoption");

module.exports = function(deployer) {
  deployer.deploy(Adoption);
};

注意這里的寫法,artifacts.require("Adoption")的參數(shù)為合約名稱淮菠。
此外男公,為什么部署腳本要以數(shù)字開頭呢?這是truffle部署時用的ID合陵。
接下來執(zhí)行部署命令:

yangang:migrations yangang$ truffle migrate --reset
Using network 'development'.

Running migration: 1_initial_migration.js
  Replacing Migrations...
  ... 0xb61538c615d6268399b938d9d143ae6b941f5042047a352f282000e28d477968
  Migrations: 0x359208970726992a5c8fc91fd73f4bd5e85ec797
Saving successful migration to network...
  ... 0x2d647da26ab9cec9023f9f6cf472a88d4b6c80589c5411dcd0625008f628c46c
Saving artifacts...
Running migration: 2_deploy_contracts.js
  Replacing Adoption...
  ... 0x8794814a4af225df5034f55ac34b9ec93fb803286b5b12c152b151881b9d389b
  Adoption: 0x3dd3623e515a405921f0479205cfa41596efe755
Saving successful migration to network...
  ... 0x58fac81009ebbe60969e4f13ff69bc63db5649abd1161e36a7a15613969442b4
Saving artifacts..

使用--reset來強(qiáng)制重編譯并部署所有合約枢赔。

8. 測試合約

在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());

// Testing the 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.");
}

// Testing retrieval of a single pet's 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.");
}

// Testing retrieval of all pet owners
function testGetAdopterAddressByPetIdInArray() public {
  // Expected owner is this contract
  address expected = this;

  // Store adopters in memory rather than contract's storage
  address[16] memory adopters = adoption.getAdopters();

  Assert.equal(adopters[8], expected, "Owner of pet ID 8 should be recorded.");
}

}

接下來拥知,運行測試案例:

yangang:test yangang$ truffle test
Using network 'development'.

Compiling ./contracts/Adoption.sol...
Compiling ./test/TestAdoption.sol...
Compiling truffle/Assert.sol...
Compiling truffle/DeployedAddresses.sol...


  TestAdoption
    ? testUserCanAdoptPet (98ms)
    ? testGetAdopterAddressByPetId (62ms)
    ? testGetAdopterAddressByPetIdInArray (90ms)


  3 passing (781ms)

9. 創(chuàng)建UI和智能合約交互

在pet-shop項目中踏拜,已經(jīng)包含了前端代碼,在src目錄下低剔。
我們主要是修改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() {
    /*
     * Replace me...
     */
       // 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:8545');
  }
  web3 = new Web3(App.web3Provider);

    return App.initContract();
  },

  initContract: function() {
    /*
     * Replace me...
     */
       // 加載Adoption.json速梗,保存了Adoption的ABI(接口說明)信息及部署后的網(wǎng)絡(luò)(地址)信息,它在編譯合約的時候生成ABI襟齿,在部署的時候追加網(wǎng)絡(luò)信息
  $.getJSON('Adoption.json', function(data) {
    // 用Adoption.json數(shù)據(jù)創(chuàng)建一個可交互的TruffleContract合約實例姻锁。
    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) {
    /*
     * Replace me...
     */
       var adoptionInstance;

  App.contracts.Adoption.deployed().then(function(instance) {
    adoptionInstance = instance;

    // 調(diào)用合約的getAdopters(), 用call讀取信息不用消耗gas
    return adoptionInstance.getAdopters.call();
  }).then(function(adopters) {
    for (i = 0; i < adopters.length; i++) {
      if (adopters[i] !== '0x0000000000000000000000000000000000000000') {
        console.log('hahahah');
        console.log(adopters[i]);
        $('.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'));

    /*
     * Replace me...
     */
       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;

      // 發(fā)送交易領(lǐng)養(yǎng)寵物
      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();
  });
});

9.1 實例化合約

Adoption部署成功后,在build/contracts目錄下會生成Adoption.json文件蕊唐,記錄合約部署信息屋摔;
initContract函數(shù)讀取Adoption.json文件舔涎,創(chuàng)建一個合約實例contracts.Adoption.
markAdopted函數(shù)使用合約實例揩环,調(diào)用getAdopters()方法萎攒,獲取所有領(lǐng)養(yǎng)者列表顾孽,回到頁面上托享,把對應(yīng)寵物的領(lǐng)養(yǎng)按鈕置灰晰搀,改名為Success呀舔。


屏幕快照 2018-02-09 上午11.41.58.png

9.2 處理領(lǐng)養(yǎng)

handleAdopt函數(shù)處理領(lǐng)養(yǎng)按鈕场斑,首先獲取區(qū)塊鏈用戶賬號糠睡,然后調(diào)用合約實例的adopt(petId)方法挽鞠,領(lǐng)養(yǎng)寵物。

10. 在瀏覽器中訪問區(qū)塊鏈

MetaMask是一款插件形式的以太坊客戶端,我使用的是firefox瀏覽器信认。
MetaMask登錄界面:


屏幕快照 2018-02-09 下午1.33.30.png

選擇可供連接的網(wǎng)絡(luò):


屏幕快照 2018-02-09 下午1.35.47.png

因為ganache-cli的監(jiān)聽端口為8545材义,所以我們選擇“Localhost 8545”:
點擊“Restore from seed phrase”,
屏幕快照 2018-02-09 下午1.40.04.png

注意嫁赏,"Wallet Seed"框中填入ganache-cli的Mnemonic:

HD Wallet
==================
Mnemonic:      powder omit enforce twelve student rigid large caution eternal sibling pepper human

設(shè)置密碼其掂,確認(rèn)即可,下次登錄直接使用該密碼潦蝇。


屏幕快照 2018-02-09 下午1.44.04.png

登錄后款熬,能看到用戶及相關(guān)交易信息,默認(rèn)用戶名為Account 1攘乒,查看該用戶地址:

0xebBc700bb15e3aA969f9D8ECe9090aEb262CBf6e

與ganache-cli的用戶列表相比:


Available Accounts
==================
(0) 0xebbc700bb15e3aa969f9d8ece9090aeb262cbf6e
(1) 0x940ead554300f8b0a40530f6f77b138d24b9359b
(2) 0xf47f7cd72e1377a6220bf512f5690d1027313dee
(3) 0x1c22e4df38807b47f26ad1f25503c23947e9e7e1
(4) 0xb8cf54c3ebdf06ae887222c23930bad155453891
(5) 0xc8232b9bc251ab9c393071747616e0a831b143ec
(6) 0x01c05db44cfbb7bf7b5671cc0e197e72607f0166
(7) 0xa08e0b2855556167afd44c63c6429c8372a6cf01
(8) 0x18ca58212d87cca4842471475ada5d78183d7fdb
(9) 0xcb465d0d7a7b067c7b0c8272078710bc4937d134

發(fā)現(xiàn)什么了嗎贤牛?其實就是第一個用戶。

11. 安裝和配置lite-server

接下來需要本地的web服務(wù)器提供服務(wù)的訪問则酝,Truffle Box pet-shop里面殉簸,提供了一個lite-server可以直接使用,我們看看它是如何工作的堤魁。
bs-config.json指示了lite-server的工作目錄:

{
  "server": {
    "baseDir": ["./src", "./build/contracts"]
  }
}

與此同時喂链,在pckage.json的scriptes中添加了dev命令:

"scripts": {
  "dev": "lite-server",
  "test": "echo \"Error: no test specified\" && exit 1"
},

這樣,當(dāng)運行npm run dev的時候妥泉,就會啟動lite-server.

12. 啟動服務(wù)

npm run dev

會自動打開瀏覽器,顯示我們的dapp:


屏幕快照 2018-02-09 下午1.54.01 (2).png

現(xiàn)在領(lǐng)養(yǎng)第四只寵物Melissa看看洞坑,當(dāng)我們點擊Adopt時盲链,MetaMask會提示我們交易的確認(rèn):


屏幕快照 2018-02-09 下午1.55.21.png

注意:MetaMask和dapp必須在同一個瀏覽器中哦,否則不會提示的迟杂。
點擊submit確認(rèn)后刽沾,如圖所示,交易29執(zhí)行成功了排拷。
屏幕快照 2018-02-09 下午1.57.33.png

刷新頁面侧漓,我們可以發(fā)現(xiàn),Melissa已經(jīng)被領(lǐng)養(yǎng)了监氢。


屏幕快照 2018-02-09 下午1.58.57 (2).png

13. 總結(jié)

到此為止布蔗,我們已經(jīng)完成了一個簡單的dapp,恭喜自己吧浪腐。
后續(xù)還有其他問題纵揍,例如如何控制用戶權(quán)限,如何處理并發(fā)領(lǐng)養(yǎng)等等议街。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末泽谨,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌吧雹,老刑警劉巖骨杂,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異雄卷,居然都是意外死亡搓蚪,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進(jìn)店門龙亲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來陕凹,“玉大人,你說我怎么就攤上這事鳄炉《虐遥” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵拂盯,是天一觀的道長佑女。 經(jīng)常有香客問我,道長谈竿,這世上最難降的妖魔是什么团驱? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮空凸,結(jié)果婚禮上嚎花,老公的妹妹穿的比我還像新娘。我一直安慰自己呀洲,他們只是感情好紊选,可當(dāng)我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著道逗,像睡著了一般兵罢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上滓窍,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天卖词,我揣著相機(jī)與錄音,去河邊找鬼吏夯。 笑死此蜈,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的锦亦。 我是一名探鬼主播舶替,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼杠园!你這毒婦竟也來了顾瞪?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎陈醒,沒想到半個月后惕橙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡钉跷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年弥鹦,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片爷辙。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡彬坏,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出膝晾,到底是詐尸還是另有隱情栓始,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布血当,位于F島的核電站幻赚,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏臊旭。R本人自食惡果不足惜落恼,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望离熏。 院中可真熱鬧佳谦,春花似錦、人聲如沸滋戳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽胧瓜。三九已至,卻和暖如春郑什,著一層夾襖步出監(jiān)牢的瞬間府喳,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工蘑拯, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留钝满,地道東北人。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓申窘,卻偏偏與公主長得像弯蚜,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子剃法,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內(nèi)容