В этом руководстве пошагово рассмотрим, как создать 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 в браузере и попросит авторизоваться через него.

Если вы создаете приложение через 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
- Напишем простой смарт-контракт на 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++; } } - Используя Remix откомпилируем его и задеплоим в наш локальный блокчейн Ganache:

- После успешного деплоя контракта, в логе Ganache находим его адрес:

- Теперь нужно вытащить интерфейс ABI нашего контракта, — это можно сделать через Remix.
В вашем Workspace после компиляции смарт-контракта в директории /contracts/artifacts/ дожен появиться файл CatFactory_metadata.json, открыв его найдите элемент «abi«.
На следующем шаге вам понадобится весь этот массив (вместе с квадратными скобками).

- Теперь перейдем к 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" } ] - Теперь расширим наш компонент <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; - В итоге на странице вы должны увидеть примерно следующее:

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