使用Ethers.js構建以太坊DApp

買賣虛擬貨幣
你在以太坊上構建DApps時,你很可能最常使用Web3.js來構建javascript前端。Ethers.js是一個輕量級的JavaScript庫,可以用來替代Web3.js來構建javascript前端並與以太坊區塊鏈進行互動。這篇文章/教程中,我將展示如何使用Ethers.js構建一個簡單的DApp。我希望這有助於您評估並開始使用Ethers.js進行下一個專案。在本教程中,我們將建立一個簡單的Voting dapp。Solidity合同將是相同的,但我們將使用Ethers.js而不是Web3.js作為前端。申請非常簡單,只需初始化一組候選人,讓任何人投票給那些候選人並顯示每位候選人收到的總票數。本教程的目標是:1.設定開發環境。2.學習編寫合同、編譯合同和在開發環境中部署合同的過程。3.透過nodejs控制檯使用ethers.js與合同互動。
4.在簡單的網頁中使用ethers.js與合同互動,以顯示投票計數並透過頁面對候選人進行投票。

這就是我們將要構建的應用程式的流程圖。

搭建開發環境

我們將使用名為ganache的記憶體區塊鏈(將其視為區塊鏈模擬器),而不是針對實時區塊鏈開發應用程式。 下面是在MacOS上安裝Ganache、EthersJS、Solc(編譯我們的合同)和啟動測試區塊鏈的步驟。同樣的指令也適用於Linux。

admin@chaindaily$ brew update
admin@chaindaily$ brew install nodejs
admin@chaindaily$ mkdir -p ethereum_voting_dapp/chapter1-ethersjs
admin@chaindaily$ cd ethereum_voting_dapp/chapter1-ethersjs
admin@chaindaily$ npm install ganache-cli ethers admin@chaindaily
admin@chaindaily$ node_modules/.bin/ganache-cli
Ganache CLI v6.0.3 (ganache-core: 2.0.2)
Available Accounts
==================
(0) 0x5c252a0c0475f9711b56ab160a1999729eccce97
(1) 0x353d310bed379b2d1df3b727645e200997016ba3
(2) 0xa3ddc09b5e49d654a43e161cae3f865261cabd23
(3) 0xa8a188c6d97ec8cf905cc1dd1cd318e887249ec5
(4) 0xc0aa5f8b79db71335dacc7cd116f357d7ecd2798
(5) 0xda695959ff85f0581ca924e549567390a0034058
(6) 0xd4ee63452555a87048dcfe2a039208d113323790
(7) 0xc60c8a7b752d38e35e0359e25a2e0f6692b10d14
(8) 0xba7ec95286334e8634e89760fab8d2ec1226bf42
(9) 0x208e02303fe29be3698732e92ca32b88d80a2d36
Private Keys
==================
(0) a6de9563d3db157ed9926a993559dc177be74a23fd88ff5776ff0505d21fed2b
(1) 17f71d31360fbafbc90cad906723430e9694daed3c24e1e9e186b4e3ccf4d603
(2) ad2b90ce116945c11eaf081f60976d5d1d52f721e659887fcebce5c81ee6ce99
(3) 68e2288df55cbc3a13a2953508c8e0457e1e71cd8ae62f0c78c3a5c929f35430
(4) 9753b05bd606e2ffc65a190420524f2efc8b16edb8489e734a607f589f0b67a8
(5) 6e8e8c468cf75fd4de0406a1a32819036b9fa64163e8be5bb6f7914ac71251cc
(6) c287c82e2040d271b9a4e071190715d40c0b861eb248d5a671874f3ca6d978a9
(7) cec41ef9ccf6cb3007c759bf3fce8ca485239af1092065aa52b703fd04803c9d
(8) c890580206f0bbea67542246d09ab4bef7eeaa22c3448dcb7253ac2414a5362a
(9) eb8841a5ae34ff3f4248586e73fcb274a7f5dd2dc07b352d2c4b71132b3c73f
HD Wallet
==================
Mnemonic: cancel better shock lady capable main crunch alcohol derive alarm duck umbrella
Base HD Path: m/44'/60'/0'/0/{account_index}
Listening on localhost:8545

請注意,GANACHE CLI建立了10個要自動使用的測試帳戶。這些帳戶預裝了100(測試)以太。

簡單投票合同

我們將使用可靠性程式語言來編寫我們的合同。如果您熟悉物件導向程式設計,那麼學習編寫可靠性合同應該是輕而易舉的。我們將使用一個建構函式編寫一個智慧合約(將合同視為您最喜歡的OOP語言中的類),該建構函式初始化一組候選項。我們將編寫2個方法,一個用於返回候選人收到的總票數,另一個用於增加候選人的投票數。

注意:在將合同部署到區塊鏈時,只呼叫一次建構函式。與Web程式碼中每次部署程式碼都覆蓋舊程式碼的情況不同,區塊鏈中部署的程式碼是不可變的。即如果您更新合同並再次部署,舊合同仍將保留區塊鏈以及儲存在其中的所有資料,新部署將建立合同的新例項。

下面是帶內聯註釋解釋的投票合同程式碼:

pragma solidity >=0.4.0 <0.6.0;
// We have to specify what version of compiler this code will compile with

contract Voting {
  /* mapping field below is equivalent to an associative array or hash.
  The key of the mapping is candidate name stored as type bytes32 and value is
  an unsigned integer to store the vote count
  */

  mapping (bytes32 => uint256) public votesReceived;

  /* Solidity doesn't let you pass in an array of strings in the constructor (yet).
  We will use an array of bytes32 instead to store the list of candidates
  */

  bytes32[] public candidateList;

  /* This is the constructor which will be called once when you
  deploy the contract to the blockchain. When we deploy the contract,
  we will pass an array of candidates who will be contesting in the election
  */
  constructor(bytes32[] memory candidateNames) public {
    candidateList = candidateNames;
  }

  // This function returns the total votes a candidate has received so far
  function totalVotesFor(bytes32 candidate) view public returns (uint256) {
    require(validCandidate(candidate));
    return votesReceived[candidate];
  }

  // This function increments the vote count for the specified candidate. This
  // is equivalent to casting a vote
  function voteForCandidate(bytes32 candidate) public {
    require(validCandidate(candidate));
    votesReceived[candidate] += 1;
  }

  function validCandidate(bytes32 candidate) view public returns (bool) {
    for(uint i = 0; i < candidateList.length; i++) {
      if (candidateList[i] == candidate) {
        return true;
      }
    }
    return false;
  }
}

將以上程式碼複製到ethereum_voting_dapp / chapter1-ethersjs目錄中名為Voting.sol的檔案中。 現在讓我們編譯程式碼並將其部署到ganache區塊鏈。 按照以下命令編譯合同。

admin@chaindaily$ node_modules/.bin/solcjs — bin — abi Voting.sol
admin@chaindaily$ ls
Voting.sol Voting_sol_Voting.abi Voting_sol_Voting.bin

當您使用上述命令成功編譯程式碼時,編譯器輸出2個檔案,這些檔案對於理解:

1. Voting_sol_Voting.bin:這是編譯Voting.sol中的原始碼時得到的位元組碼。 這是將部署到區塊鏈的程式碼。

2. Voting_sol_Voting.abi:這是合同的介面或模板(稱為abi),它告訴合同使用者合同中有哪些方法可用。 每當您將來必須與合同進行互動時,您將需要此abi定義。

我們現在將使用ethersjs庫來部署我們的應用程式並與之互動。

首先,在終端中執行'node'命令以進入節點控制檯並初始化ethers物件。 下面的所有程式碼片段都需要在節點控制檯中輸入。 要編譯合同,請將位元組碼和abi從檔案系統載入到如下所示的字串中

admin@chaindaily$ node
> ethers = require(‘ethers’)
> bytecode = fs.readFileSync(‘Voting_sol_Voting.bin’).toString()
> abi = JSON.parse(fs.readFileSync(‘Voting_sol_Voting.abi’).toString())

接下來是初始化程式,它是連線到區塊鏈的通用介面。 由於我們在本地執行區塊鏈,我們將使用JsonRPCProvider連線到它。 如果您想連線到生產的區塊鏈上,您還有許多其他提供選項。 連線後,您可以透過查詢ganache並列出所有帳戶來測試連線。 在nodejs控制檯中執行以下命令應該列出10個帳戶。

> provider = new ethers.providers.JsonRpcProvider()
> provider.listAccounts().then(result => console.log(result))

下一步是初始化用於簽署事務的簽名者。 我們可以透過將索引傳遞給getSigner來選擇我們想要使用的帳戶(我們在設定中有10個帳戶ganache),如下所示。 錢包初始化後,建立合同庫並按所示部署合同。 deploy函式接受一組傳遞給合同建構函式的引數。 在我們的案例中,我們傳遞候選人的姓名。 我們必須顯式地將字串轉換為bytes32,因為我們的智慧合約將bytes32作為引數。

> signer = provider.getSigner(0)
> factory = new ethers.ContractFactory(abi, bytecode, signer)
> contract = null
> factory.deploy([ethers.utils.formatBytes32String(‘Rama’), ethers.utils.formatBytes32String(‘Nick’), ethers.utils.formatBytes32String(‘Jose’)]).then(© => { contract = c})

如果您成功部署了合同,則您的合同物件應具有已部署合同的所有詳細資訊。 區塊鏈上部署了數十萬份合同。 那麼,你如何識別區塊鏈中的合同? 答案:contract.address。 當您必須與合同進行互動時,您需要我們之前談到的這個部署地址和abi定義。

在nodejs控制檯中與合同互動

> contract.totalVotesFor(ethers.utils.formatBytes32String(‘Rama’)).
then((f) => console.log(f.toNumber()))
> contract.voteForCandidate(ethers.utils.formatBytes32String(‘Rama’)).
then((f) => console.log(f))
> contract.totalVotesFor(ethers.utils.formatBytes32String(‘Rama’)).
then((f) => console.log(f.toNumber()))

在節點控制檯中嘗試上述命令,您應該看到投票計數增量。 每次投票給候選人時,都會得到一個交易ID:例如:
'0xdedc7ae544c3dde74ab5a0b07422c5a51b5240603d31074f5b75c0ebc786bf53'。 此事務ID是此事務發生的證據,您可以在將來的任何時間再參考此事務。 此事務是不可變的。 

連線到區塊鏈的投票網頁


現在大部分工作已經完成,我們現在要做的就是建立一個帶有候選名稱的簡單html檔案,並在js檔案中呼叫投票命令(我們已在nodejs控制檯中嘗試和測試過)。 您可以在下面找到html程式碼和js檔案。 將它們都放在ethereum_voting_dapp / chapter1-ethersjs目錄中,然後在瀏覽器中開啟index.html。

<!DOCTYPE html>
<html>
<head>
 <title>Hello World DApp</title>
 <link href='https://fonts.googleapis.com/css?family=Open Sans:400,700' rel='stylesheet' type='text/css'>
 <link href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' rel='stylesheet' type='text/css'>
</head>
<body class="container">
 <h2>A Simple Hello World Voting Application</h2>
 <div class="table-responsive">
  <table class="table table-bordered">
   <thead>
    <tr>
     <th>Candidate</th>
     <th>Votes</th>
    </tr>
   </thead>
   <tbody>
    <tr>
     <td>Rama</td>
     <td id="candidate-1"></td>
    </tr>
    <tr>
     <td>Nick</td>
     <td id="candidate-2"></td>
    </tr>
    <tr>
     <td>Jose</td>
     <td id="candidate-3"></td>
    </tr>
   </tbody>
  </table>
 </div>
 <input type="text" id="candidate" />
 <a href="#" onclick="voteForCandidate()" class="btn btn-primary">Vote</a>
</body>
<script charset="utf-8"
        src="//cdn.ethers.io/scripts/ethers-v4.min.js"
        type="text/javascript"></script>
<script src="//code.jquery.com/jquery-3.1.1.slim.min.js"></script>
<script src="./index.js"></script>
</html>
index.html 

abi = JSON.parse('[{"constant":true,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"totalVotesFor","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"validCandidate","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"votesReceived","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"candidateList","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"voteForCandidate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"candidateNames","type":"bytes32[]"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]')

provider = new ethers.providers.JsonRpcProvider()
signer = provider.getSigner(0);
contract = new ethers.Contract('0x5735731eEbDA5BE1eEe9f0b119B9374a63b0f507', abi, signer)

candidates = {"Rama": "candidate-1", "Nick": "candidate-2", "Jose": "candidate-3"}

function voteForCandidate(candidate) {
 candidateName = $("#candidate").val();
  console.log(candidateName);

  contract.voteForCandidate(ethers.utils.formatBytes32String(candidateName)).then((f) => {
    let div_id = candidates[candidateName];
    contract.totalVotesFor(ethers.utils.formatBytes32String(candidateName)).then((f) => {
      $("#" + div_id).html(f);
    })
  });
}

$(document).ready(function() {

 candidateNames = Object.keys(candidates);

 for(var i=0; i<candidateNames.length; i++) {
  let name = candidateNames[i];
  contract.totalVotesFor(ethers.utils.formatBytes32String(name)).then((f) => {
    $("#" + candidates[name]).html(f);
  })
 }
});
index.js

您現在應該看到投票計數,您還應該能夠投票給候選人。

如果您使用Metamask之類的錢包,則需要使用Web3提供程式而不是之前使用的JsonRPCProvider。 要做到這一點,只需將index.js中的提供程式更改為:

provider = new ethers.providers.Web3Provider(web3.currentProvider);

如果您想透過Metamask進行互動,則不能再開啟index.html並在瀏覽器中進行互動。 您必須透過伺服器來服務該檔案。 所以安裝一個簡單的網路伺服器,然後如下所示

admin@chaindaily$ npm install http-server
admin@chaindaily$ http-server

您現在可以轉到localhost:8080並與您的應用程式進行互動。 下面是一個快速演示,其中包括將測試乙太網帳戶載入到元掩碼中。

我希望你能夠順利搭建應用程式並正常執行,好,教程就到此結束。

免責聲明:

  1. 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
  2. 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
  3. 鏈報僅提供相關項目信息,不構成任何投資建議

推荐阅读

;