Ethereum Dapp на ReactJs через MetaMask: Пошаговое руководство

В этом руководстве пошагово рассмотрим, как создать reactjs проект и настроить его взаимодействие с блокчейном Ethereum.

Подготовка к работе с ReactJS

Предполагается, что node, npm и npx у вас уже установлены (если это не так, установите их).

Следующие 3 команды должны вам показать их версию:

$ node -v
$ npm -v
$ npx -v

Создавать проект reactjs мы будем через npx (вместо npm), – это тоже CLI, который позволяет упростить установку и управление зависимостями, размещенными в реестре npm.

Создание нового проекта ReactJs и установка необходимых пакетов

При помощи npx создадим новый проект reactjs с названием ‘frontend’ и запустим его. Обычно я создаю его внутри truffle проекта, чтобы фронтэнд лежал рядышком для удобства.

# создаем react-проект с названием 'frontend'
$ npx create-react-app frontend

# переходим в директорию проекта
$ cd frontend

Для того, чтобы проверить, что проект корректно создался и запускается, можно при помощи команды: npm start.

В итоге должен запуститься проект по локальному адресу: http://localhost:3000/

Теперь установим пакеты ‘bootstrap’ и ‘web3’ для нашего проекта:

  • bootstrap‘ нужен, чтобы не заморачиваться на дизайне и верстке, а использовать для этого готовый css framework.
  • web3‘ библиотека, которая будет обеспечивать взаимодействие с ethereum блокчейном.
# установка 'bootstrap'
$ npm install react-bootstrap bootstrap

# установка 'web3' и необходимых для ее работы доп.пакетов
$ npm install web3, react-app-rewired, url, assert, buffer, crypto-browserify, stream-http, https-browserify, stream-browserify, os-browserify

Откроем директорию frontend/src и удалим оттуда файл index.css, так как все глобальные изменения со стилями мы будем делать в App.css.

Отредактируем файл frontend/src/index.js и внесем туда след изменения:

  • импортируем ‘bootstrap’ добавив строчку import 'bootstrap/dist/css/bootstrap.min.css';
  • удалим строку import './index.css'; чтобы исключить импорт удаленного ранее файла.

Отредактируем файл frontend/src/App.js и добавим туда импорт библиотеки Web3:

import Web3 from 'web3'

Чтобы Web3 увидел все свои зависимости, создадим в директории frontend файл config-overrides.js и скопируем туда следующую конфигурацию:

const webpack = require('webpack');
 module.exports = function override(config, env) {
     //do stuff with the webpack config...

    config.resolve.fallback = {
        url: require.resolve('url'),
        assert: require.resolve('assert'),
        crypto: require.resolve('crypto-browserify'),
        http: require.resolve('stream-http'),
        https: require.resolve('https-browserify'),
        os: require.resolve('os-browserify/browser'),
        buffer: require.resolve('buffer'),
        stream: require.resolve('stream-browserify'),
    };
    config.plugins.push(
        new webpack.ProvidePlugin({
            process: 'process/browser',
            Buffer: ['buffer', 'Buffer'],
        }),
    );

    return config;
}

После этого, откроем файл package.json и в секцию scripts внесем следующие изменения:

"scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test",
    "eject": "react-app-rewired eject"
},

При помощи команды: npm start проверьте, что после всех наших манимуляций проект успешно запустился по локальному адресу: http://localhost:3000/.

Если все работает, то это значит, что наш reactjs-проект готов к взаимодействию с блокчейном Ethereum.

Прежде чем продолжить далее, рекомендуется выполнить действия, описанные в статье “Настройка взаимодействия MetaMask и Remix с Ganache“.

Этап 1. Авторизация на web-странице через Ethereum

Покажем простую авторизацию в Ethereum через MetaMask на нашей веб-странице и выведем на нее адрес нашего авторизованного кошелька.

Для этого отредактируем файл App.js следующим образом:

import React, { useEffect, useState } from 'react';
import {Container, Row, Col, Alert} from 'react-bootstrap';
import Web3 from 'web3';

import './App.css';


const App = () => {
  const [account, setAccount] = useState();

  useEffect(() => {

    async function load() {
      const web3 = new Web3(Web3.givenProvider);
      console.log('Web3Version: ' + web3.version);
  
      // запрос авторизованного аккаунта (если их несколько, то выбирается первый)
      const accounts = await web3.eth.requestAccounts();
      setAccount(accounts[0]);
      console.log('Account: ' + accounts[0]);
    }
  
    load();
  
   }, []);

  return (
    <Container className="px-4 py-5">
      <Row>
        <Col>
          <Alert variant='info'>
            <p>Ваш аккаунт: <b>{account}</b></p>
          </Alert>
        </Col>
      </Row>
    </Container>
  );
};

export default App;

В отличие от дефолтной версии App.js, здесь мы используем библиотеку react-bootstrap для красивого оформления страницы и библиотеку Web3 для подключения к Ethereum блокчейну через MetaMask аккаунт.

При запуске приложения и переходе по адресу http://localhost:3000/ сработает хук useEffect, который вызовет функцию load(), а она в свою очередь вызовет функцию new Web3(Web3.givenProvider), которая автоматически попросит откроет окно плагина MetaMask в браузере и попросит авторизоваться через него.

MetaMask авторизация через ReactJS

Если вы создаете приложение через create-react-app, то можете столкнуться с ситуацией (и заметить это через консоль разработчика), когда код в хуке useEffect при загрузке страницы вызывается несколько раз, не смотря на установленный пустой массив зависимостей []. Это происходит потому, что в index.js у вас вызов компонента <App /> обернут в <React.StrictMode>, который в development режиме (но не в production) выполняет рендеринг компонентов дважды. По мнению разработчиков React – это позволяет обнаружить некоторые проблемы в коде и предупредить вас об этом.

Чтобы избежать этого, отредактируйте index.js и уберите обертку StrictMode:

// замените эти строки:
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

// на эту:
root.render(<App />);

Этап 2. Взаимодействие со смарт-контрактом Ethereum через ReactJs

  1. Напишем простой смарт-контракт на Solidity под названием CatFactory (Фабрика котиков).
    Принцип его работы простой, – каждый может вызвать функцию createCat и создать себе котика. А при деплое смарт-контракта, владельцу автоматически создаются 3 котика.
    // SPDX-License-Identifier: GPL-3.0
    
    pragma solidity >=0.7.0 <0.9.0;
    
    /**
     * @title CatFactory
     * @dev Фабрика котиков :)
     */
    contract CatFactory {
    
        uint public count = 0; // количество котят
    
        /**
         * @dev Структура для хранения характеристик котиков
         */
        struct Cat {
            string name;    // имя котика
            address owner;  // адрес владельца котика
        }
    
        // @dev Каждый владелец может иметь только 1 котика (дефицит)
        mapping(uint => Cat) public cats;
    
        /**
         * @dev Конструктор - при деплое генерируем несколько котиков
         */
        constructor() {
            createCat("Adam");
            createCat("Bob");
            createCat("Elena");
        }
    
        /**
         * @dev Создание нового котика
         * @param _name имя котика
         */
        function createCat(string memory _name) public {
            cats[count] = Cat({
                name: _name,
                owner: msg.sender
            });
            count++;
        }
    }
  2. Используя Remix откомпилируем его и задеплоим в наш локальный блокчейн Ganache:
    деплой смарт контракта из Remix в Ganache
  3. После успешного деплоя контракта, в логе Ganache находим его адрес:
    Ganache адрес смарт-контракта в Logs
  4. Теперь нужно вытащить интерфейс ABI нашего контракта, – это можно сделать через Remix.
    В вашем Workspace после компиляции смарт-контракта в директории /contracts/artifacts/ дожен появиться файл CatFactory_metadata.json, открыв его найдите элемент “abi“.
    На следующем шаге вам понадобится весь этот массив (вместе с квадратными скобками).
    Где взять ABI смарт-контракта в Remix
  5. Теперь перейдем к reactjs. В директории /frontend/src/ нужно создать файл config.js в котором указать две внешних переменные:
    • CONTACT_ADDRESS – адрес вашего смарт-контракта полученный на шаге 3.
    • CONTACT_ABI – интерфейс ABI смарт-контракта, полученный на шаге 4.

    Вот полное содержимое файла config.js, которое должно получиться:

    export const CONTACT_ADDRESS = '0xda595ea8bd21bf6e91978d67f1c1a7f23c8cd3c4';
    
    export const CONTACT_ABI = [
        {
            "inputs": [],
            "stateMutability": "nonpayable",
            "type": "constructor"
        },
        {
            "inputs": [
                {
                    "internalType": "uint256",
                    "name": "",
                    "type": "uint256"
                }
            ],
            "name": "cats",
            "outputs": [
                {
                    "internalType": "string",
                    "name": "name",
                    "type": "string"
                },
                {
                    "internalType": "address",
                    "name": "owner",
                    "type": "address"
                }
            ],
            "stateMutability": "view",
            "type": "function"
        },
        {
            "inputs": [],
            "name": "count",
            "outputs": [
                {
                    "internalType": "uint256",
                    "name": "",
                    "type": "uint256"
                }
            ],
            "stateMutability": "view",
            "type": "function"
        },
        {
            "inputs": [
                {
                    "internalType": "string",
                    "name": "_name",
                    "type": "string"
                }
            ],
            "name": "createCat",
            "outputs": [],
            "stateMutability": "nonpayable",
            "type": "function"
        }
    ]
  6. Теперь расширим наш компонент <App />, добавив в него еще несколько переменных состояний для хранения переменной контракта и коллекции котиков, полученных из блокчейна. А в хуке useEffect мы теперь обращаемся к смарт-контракту и запрашиваем массив котиков, который сохраняем в переменной состояния cats. Далее, при рендере компонента, мы выводим этот массив при помощи цикла.
    import React, { useEffect, useState } from 'react';
    import {Container, Row, Col, Alert} from 'react-bootstrap';
    import Web3 from 'web3';
    import { CONTACT_ABI, CONTACT_ADDRESS } from './config';
    
    import './App.css';
    
    
    const App = () => {
      const [account, setAccount] = useState();
      const [contract, setContract] = useState();
      const [cats, setCats] = useState([]);
    
      useEffect(() => {
    
        async function load() {
          const web3 = new Web3(Web3.givenProvider);
          console.log('Web3Version: ' + web3.version);
      
          // запрос авторизованного аккаунта (если их несколько, то выбирается первый)
          const accounts = await web3.eth.requestAccounts();
          setAccount(accounts[0]);
          console.log('Account: ' + accounts[0]);
      
          // создадим экземпляр смарт-контракта используя его ABI и адрес, а затем установим его в переменную состояния
          const contract = new web3.eth.Contract(CONTACT_ABI, CONTACT_ADDRESS);
          setContract(contract);
      
          // получим количество котят в хранилище
          const counter = await contract.methods.count().call();
          console.log('Count cats: ' + counter);
      
          // прочитаем их всех и запишем в переменную состояния
          for (var i = 0; i <= counter-1; i++) {
            // вызов мапы cats из смарт-контракта, которое содержит список котиков
            const cat = await contract.methods.cats(i).call();
      
            // добавление полученного котика в переменную состояния
            setCats((cats) => [...cats, cat]);
          }
        }
      
        load();
      
       }, []);
    
      return (
        <Container className="px-4 py-5">
          <Row>
            <Col>
              <Alert variant='info'>
                <p>Ваш аккаунт: <b>{account}</b></p>
                <p>Коллекция котиков:</p>
                <div>
                {
                  Object.keys(cats).map((cat, index) => (
                    <div key={`${cats[index].name}-${index}`}>Cat {index}: {cats[index].name}</div>
                  ))
                }
                </div>
              </Alert>
            </Col>
          </Row>
        </Container>
      );
    };
    
    export default App;
    
  7. В итоге на странице вы должны увидеть примерно следующее:
    вывод информации из смарт-контракта ethereum
    Поздравляю!
    Теперь вы научились делать для вашего приложения простую авторизацию с блокчейном через MetaMask, а также взаимодействовать со смарт-контрактом внутри блокчейна.
Рейтинг
( 1 оценка, среднее 5 из 5 )
Понравилась статья? Поделиться с друзьями:
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: