Создайте Ethereum приложение, используя Ethers.js

  • HLCS 

Источник · Перевод автора

Руководство от генерального директора (CEO) Zastrin.com Махеша Мурти (Mahesh Murthy)

Если вы создали приложение в Ethereum, вы, скорее всего, использовали Web3.js для создания своего внешнего интерфейса javascript. Ethers.js — это легковесная библиотека javascript, которую можно использовать как альтернативу Web3.js для создания внешнего интерфейса javascript и взаимодействия с блокчейном Ethereum. Недавно у меня была возможность повозиться с Ethers.js, и она меня впечатлила. В этом посте / руководстве я покажу, как вы можете использовать Ethers.js для создания простого приложения. Я надеюсь, что это поможет вам оценить и начать использовать Ethers.js для вашего следующего проекта.

В этом уроке мы создадим простое приложение для голосования (очень похожий на наш популярный «Привет, мир, голосующее приложение», который многие из вас могут знать!). Контракт Solidity будет таким же, но мы будем использовать Ethers.js вместо Web3.js для внешнего интерфейса. Приложение очень простое, все, что оно делает, это инициализирует набор кандидатов, позволяет любому голосовать за этих кандидатов и отображать общее количество голосов, полученных каждым кандидатом.

Если вы новичок в Ethereum, вот несколько курсов, которые могут помочь вам начать.

Цели этого урока:

1. Настройте среду разработки

2. Изучите процесс написания контракта, его компиляции и развертывания в среде разработки.

3. Взаимодействие с контрактом, используя ethers.js через консоль nodejs.

4. Взаимодействие с контрактом, используя ethers.js на простой веб-странице, чтобы отобразить подсчет голосов и проголосовать за кандидатов на странице.

Если вы уже работали с приложением «Привет, мир, голосование», вы можете перейти к шагу 3.

Вот как я бы визуализировал это приложение, которое мы собираемся построить.

1. Настройка среды разработки

Вместо того чтобы разрабатывать приложение на основе живого блокчейна, мы будем использовать блокчейн в памяти (представьте его как симулятор блокчейна), называемый ganache. Ниже приведены шаги для установки ganache, ethersjs, solc (для компиляции нашего контракта) и запуска тестовой цепочки блоков на макросах. Точно такие же инструкции работают и на Linux.

zastrin@macbook$ brew update
zastrin@macbook$ brew install nodejs
zastrin@macbook$ mkdir -p ethereum_voting_dapp/chapter1-ethersjs
zastrin@macbook$ cd ethereum_voting_dapp/chapter1-ethersjs
zastrin@macbook$ npm install ganache-cli ethers solc@0.5.3
zastrin@macbook$ 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 (фейковыми) эфирами.

2. Простой контракт голосования

Мы собираемся использовать язык программирования solidity, чтобы написать наш контракт. Если вы знакомы с объектно-ориентированным программированием, обучение написанию контрактов на прочность должно быть быстрым. Мы напишем контракт (думайте о контракте как о классе на вашем любимом языке ООП), который называется Voting с конструктором, который инициализирует массив кандидатов. Мы напишем 2 метода: один для подсчета общего количества голосов, полученных кандидатом, а другой — для увеличения количества голосов за кандидата.

Примечание. Конструктор вызывается один раз и только один раз при развертывании контракта в блокчейне. В отличие от веб-мира, где каждое развертывание вашего кода перезаписывает старый код, развернутый код в блокчейне является неизменным. То есть, если вы обновите свой контракт и развернете его снова, старый контракт все еще будет в нетронутой цепочке блоков вместе со всеми данными, хранящимися в нем, новое развертывание создаст новый экземпляр контракта.

Ниже приведен код договора голосования с пояснительным комментарием:

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;
  }
}

Скопируйте приведенный выше код в файл с именем Voting.sol в каталоге ethereum_voting_dapp / chapter1-ethersjs. Теперь давайте скомпилируем код и развернем его в блокчейне ganache. Следуйте приведенным ниже командам, чтобы составить договор.

zastrin@macbook$ node_modules/.bin/solcjs — bin — abi Voting.sol
zastrin@macbook$ 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. Вы можете прочитать более подробную информацию о ABI здесь

Теперь мы будем использовать библиотеку ethersjs для развертывания нашего приложения и взаимодействия с ним.

Сначала запустите команду «node» в вашем терминале, чтобы войти в консоль узла и инициализировать объект ethers. Все приведенные ниже фрагменты кода должны быть набраны в консоли узла. Чтобы скомпилировать контракт, загрузите байт-код и abi из файловой системы в строку, как показано ниже

zastrin@macbook$ 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))

Следующим шагом является инициализация подписавшего, который используется для подписания транзакций. Мы можем выбрать, какую учетную запись мы хотим использовать (у нас в настройке 10 учетных записей ganache), передав индекс getSigner, как показано ниже. Как только кошелек будет инициализирован, создайте фабрику контрактов и разверните контракт, как показано. Функция 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, о котором мы говорили ранее.

3. Взаимодействовие с контрактом в консоли 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()))

Попробуйте перечисленные выше команды в консоли вашего узла, и вы увидите увеличение количества голосов. Каждый раз, когда вы голосуете за кандидата, вы получаете идентификатор транзакции: Пример: ‘0xdedc7ae544c3dde74ab5a0b07422c5a51b5240603d31074f5b75c0ebc786bf53’ выше). Этот идентификатор транзакции является доказательством того, что эта транзакция произошла, и вы можете вернуться к ней в любое время в будущем. Эта транзакция неизменна. Эта неизменность является одним из больших преимуществ блокчейнов, таких как Ethereum.

4. Веб-страница для подключения к блокчейну и голосования

Теперь, когда большая часть работы завершена, все, что нам нужно сделать, — это создать простой html-файл с именами кандидатов и вызвать команды голосования (которые мы уже попробовали и протестировали в консоли nodejs) в файле js. Ниже вы можете найти 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">
 <h1>A Simple Hello World Voting Application</h1>
 <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="https://cdn.ethers.io/scripts/ethers-v4.min.js"
        type="text/javascript">
</script>
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js"></script>
<script src="./index.js"></script>
</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);
  })
 }
});

Теперь вы должны увидеть подсчет голосов, а также сможете голосовать за кандидатов.

Если вы используете кошелек типа Metamask, вам нужно будет использовать провайдера Web3 вместо того JsonRPCProvider, который мы использовали ранее. Для этого просто измените провайдера в вашем index.js на:

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

Если вы хотите взаимодействовать через Metamask, вы не можете просто открыть index.html и больше взаимодействовать в своем браузере. Вы должны сервер файл через сервер. Итак, установите простой веб-сервер и начните как показано ниже

zastrin@macbook$ npm install http-server
zastrin@macbook$ http-server

Теперь вы можете перейти на localhost: 8080 и взаимодействовать с вашим приложением. Ниже приведена краткая демонстрация, включающая загрузку тестовой учетной записи эфира в Metamask.

Я надеюсь, что вы смогли подписаться и заставить приложение работать. Зайдите на Zastrin.com и зарегистрируйтесь, чтобы получить больше уроков и уроков, чтобы вы могли создавать и развертывать реальные рабочие приложения.