Децентрализованные приложения или dApps, - это приложения, которые не полагаются на централизованный сервер или бэкэнд, а используют технологии Web3, такие как блокчейн и оракулы для хранения своей логики и функций бэкэнда, что делает их защищенными от взлома и безопасными.
В этом техническом руководстве вы узнаете, как создать простой сквозной dApp, который позволяет пользователю получать и хранить текущую цену Ethereum в смарт-контракте. Готовую демонстрационную версию можно найти на GitHub.
Требования
Пожалуйста, убедитесь, что у вас установлено следующее:
Что такое децентрализованное приложение?
В dApp код бэкенда работает на блокчейне, в отличие от традиционного приложения, где код бэкенда работает на централизованных серверах. dApp может иметь внешний код и пользовательские интерфейсы, написанные на любом языке и развернутые на любом сервере или серверах для взаимодействия с внутренней логикой.
Благодаря тому, что логика бэкенда размещается в высокозащищенных, защищенных от взлома смарт-контрактах, dApp имеют множество преимуществ, недоступных для традиционных систем Web2:
нулевое время простоя
повышенная конфиденциальность
устойчивость к цензуре
выполнение логики с минимальным уровнем доверия
Однако эти преимущества имеют и некоторые недостатки. Обслуживание dApps требует больших усилий, поскольку код, развернутый на блокчейне, по умолчанию не подлежит изменению. Кроме того, из-за того, что логика выполняется в распределенной сети, а не на централизованном сервере, повышаются и эксплуатационные расходы. В дополнение к этому, пользовательский опыт может пострадать из-за того, что пользователю dApp необходимо пройти через сложности, связанные с созданием кошелька Web3 и пополнением его достаточным количеством криптовалюты для оплаты комиссий за транзакции.
Компоненты dApp
Компоненты dApp можно разделить на три различные категории: смарт-контракты, фронтенд-логика и пользовательский интерфейс, а также хранилище данных.
Смарт-контракты Умные контракты хранят бизнес-логику dApp, а также состояние приложения. Это самое большое отличие dApp от традиционного веб-приложения, и именно это дает dApp все преимущества, упомянутые выше.
Фронтенд/интерфейс пользователя В то время как логика бэкенда dApp требует от разработчика написания кода смарт-контракта для развертывания на блокчейне, фронтенд или клиентская часть dApp может использовать стандартные веб-технологии, такие как HTML и JavaScript. Это позволяет разработчикам использовать знакомые инструменты, библиотеки и фреймворки. Пользовательский интерфейс на стороне клиента обычно связан со смарт-контрактами через клиентские библиотеки, такие как Web3.js или Ethers.js, которые поставляются в комплекте с ресурсами фронтенда и отправляются в браузер вместе с пользовательским интерфейсом. Взаимодействие со смарт-контрактами, такое как подписание сообщений и отправка транзакций смарт-контрактам, обычно осуществляется через браузерный Web3-кошелек, например MetaMask.
Хранение данных Большинству приложений необходимо хранить данные, но из-за распределенной природы блокчейн хранение больших объемов данных на цепочке нецелесообразно и может оказаться очень дорогим. Поэтому многие dApp, которым необходимо хранить данные, используют офф-чейн сервисы хранения данных, такие как IPFS или Filecoin, оставляя блокчейн только для хранения важной бизнес-логики и состояния.
Можно также использовать традиционные облачные сервисы хранения данных. Однако многие разработчики выбирают децентрализованные варианты, чтобы сохранить и расширить свойства минимизации доверия, которые обеспечивает dApp на базе блокчейна.

Теперь, когда мы знаем компоненты dApp, давайте рассмотрим пример создания простого сквозного контракта.
Шаг первый: создание смарт-контракта
Смарт-контракт в нашем dApp будет простым примером, используемым для поиска данных и отражения изменений состояния на блокчейне. В данном случае мы будем искать стоимость ETH/USD, используя ETH/USD Data Feed, а затем постоянно сохранять результат в смарт-контракте.
Первым делом откройте документацию и перейдите на страницу Using Data Feeds. Оттуда вы можете скопировать исходный код примера и вставить его в новый файл в выбранной вами IDE (например, Visual Code), либо нажать кнопку "Open In Remix" и работать из веб-версии Remix.
В этом примере мы будем работать с Visual Studio Code и Hardhat, фреймворком для разработки виртуальной машины Ethereum.
Сначала мы создадим новую структуру каталогов для нашего dApp с папкой backend для кода смарт-контракта:
mkdir chainlink-dapp-example cd chainlink-dapp-example mkdir backend cd backend
Далее мы откроем созданную директорию для нашего dApp в редакторе VS Code, а затем установим Hardhat:
npm init -y npm install --save-dev hardhat npx hardhat (choose create javascript project, choose default parameters)
После этого удалите файл Touch.sol в папке "contracts", создайте новый файл в этой папке под названием PriceConsumerV3.sol и сохраните. Здесь мы создадим наш смарт-контракт, поэтому скопируйте код из примера в документации Chainlink в этот файл и сохраните его.
В коде примера вы увидите, что в демо-контракте уже есть функция getLatestPrice для поиска текущей цены Ethereum на Rinkeby ETH/USD Data Feed.
function getLatestPrice() public view returns (int) { ( /*uint80 roundID*/, int price, /*uint startedAt*/, /*uint timeStamp*/, /*uint80 answeredInRound*/ ) = priceFeed.latestRoundData(); return price;
Нам нужно создать новую переменную и новую функцию для хранения этого значения в смарт-контракте. Первым шагом будет создание новой переменной под существующей priceFeedone для хранения цены Ethereum:
int public storedPrice;
Далее нам нужно создать новую функцию, которая будет вызываться фронтендом dApp. Эта функция должна искать последнюю цену Ethereum, вызывая существующую функцию getLatestPrice. Затем она должна сохранить это значение в новом параметре storedPrice:
function storeLatestPrice() external { storedPrice = getLatestPrice(); }
Ваш новый контракт должен выглядеть следующим образом:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.7; import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; contract PriceConsumerV3 { AggregatorV3Interface internal priceFeed; int public storedPrice; /** * Network: Rinkeby * Aggregator: ETH/USD * Address: 0x8A753747A1Fa494EC906cE90E9f37563A8AF630e */ constructor() { priceFeed = AggregatorV3Interface(0x8A753747A1Fa494EC906cE90E9f37563A8AF630e); } /** * Returns the latest price */ function getLatestPrice() public view returns (int) { ( /*uint80 roundID*/, int price, /*uint startedAt*/, /*uint timeStamp*/, /*uint80 answeredInRound*/ ) = priceFeed.latestRoundData(); return price; } function storeLatestPrice() external { storedPrice = getLatestPrice(); } }
Шаг второй: развертывание смарт-контракта
Теперь вы готовы скомпилировать и развернуть свой контракт в тестовой сети Rinkeby. Не забудьте сначала пополнить свой кошелек MetaMask некоторым количеством Rinkeby ETH.
Если вы используете Remix, вы можете скомпилировать и развернуть свой контракт, используя стандартный процесс Remix. Если вы используете IDE, например Visual Studio Code, мы рекомендуем использовать Hardhat для управления контрактами.
Первым шагом к компиляции и развертыванию вашего контракта является установка библиотеки инструментов Hardhat, библиотеки контрактов Chainlink и библиотеки dotenv для хранения паролей и секретных ключей в отдельном файле .env:
npm install --save-dev @nomicfoundation/hardhat-toolbox npm install @chainlink/contracts --save npm install dotenv
Затем замените содержимое файла hardhat-config.js на следующее:
require("@nomicfoundation/hardhat-toolbox"); //require("@nomiclabs/hardhat-ethers") require('dotenv').config() const RINKEBY_RPC_URL = process.env.RINKEBY_RPC_URL || "https://eth-rinkeby.alchemyapi.io/v2/your-api-key" const PRIVATE_KEY = process.env.PRIVATE_KEY || "abcdef" module.exports = { defaultNetwork: "rinkeby", networks: { hardhat: { // // If you want to do some forking, uncomment this // forking: { // url: MAINNET_RPC_URL // } }, localhost: { }, rinkeby: { url: RINKEBY_RPC_URL, accounts: [PRIVATE_KEY], saveDeployments: true, }, }, solidity: "0.8.9", };
Следующим шагом будет создание файла .env в папке бэкенда. Затем вам нужно извлечь ваш приватный ключ из кошелька Web3 и вставить его в секцию значения поля PRIVATE_KEY в файле .env. Пожалуйста, убедитесь, что вы используете новый кошелек Web3, на котором нет средств в mainnet.
После этого вам необходимо получить конечную точку RPC для доступа к сети Rinkeby. Это можно сделать, вставив URL RPC в поле RINKEBY_RPC_URL в файле .env. Мы рекомендуем подписаться на бесплатную учетную запись Infura или Alchemy, чтобы получить URL RPC.

Следующим шагом будет изменение содержимого файла deploy.js в папке 'scripts', чтобы убедиться, что он развернет ваш новый контракт. Откройте файл и убедитесь, что следующий код заменяет уже имеющийся. Это просто возьмет ваш скомпилированный контракт PriceConsumerV3 и попытается развернуть его. Не забудьте сохранить изменения.
// We require the Hardhat Runtime Environment explicitly here. This is optional // but useful for running the script in a standalone fashion through `node <script>`. // // You can also run a script with `npx hardhat run <script>`. If you do that, Hardhat // will compile your contracts, add the Hardhat Runtime Environment's members to the // global scope, and execute the script. const hre = require("hardhat"); async function main() { const PriceConsumer = await hre.ethers.getContractFactory("PriceConsumerV3"); const priceConsumer = await PriceConsumer.deploy(); await priceConsumer.deployed(); console.log("Contract deployed to:", priceConsumer.address); } // We recommend this pattern to be able to use async/await everywhere // and properly handle errors. main().catch((error) => { console.error(error); process.exitCode = 1; });
Теперь вы готовы составить и развернуть свой смарт-контракт в сети Rinkeby с помощью Hardhat:
npx hardhat compile npx hardhat run --network rinkeby scripts/deploy.js
Вы должны увидеть сообщение, подобное приведенному ниже, в котором будет указан адрес на Rinkeby, на который был развернут ваш смарт-контракт. Запишите этот адрес, он понадобится нам для следующего шага.

Поздравляем, теперь вы готовы перейти к фронтенд-части вашего dApp!
Шаг 3: Создание фронтенд-приложения
Логика фронтенда и пользовательский интерфейс вашего dApp могут быть построены с использованием широкого спектра различных фреймворков.
React - одна из самых популярных библиотек JavaScript для создания многофункциональных пользовательских веб-интерфейсов, поэтому она используется во многих Web3 dApp. Кроме того, Ethers.js - это библиотека JavaScript для подключения и взаимодействия с блокчейнами и смарт-контрактами на базе EVM. Если объединить эти два компонента, вы получите разумную отправную точку для создания фронтенда вашего dApp.
В этом разделе мы создадим новое приложение React с помощью генератора шаблонов create-react-app. Затем мы внедрим офф-чейн логику, использующую Ethers.js для связи пользовательского интерфейса с развернутым смарт-контрактом, что позволит нам создать полноценный сквозной dApp.
Создание React-приложения Первым шагом для создания фронтенда является установка и реализация проекта create-react-app boilerplate, а затем его модификация для нашего dApp. Первым шагом будет установка библиотеки в новую папку "frontend":
cd .. npx create-react-app frontend
После этого в вашем проекте должна появиться новая папка "frontend" со всем связанным с ней кодом React. Раскройте папку "frontend" и выполните следующие действия:
Удалить /src/setupTests.js
Удалить /src/ReportWebVitals.js
Удалить /src/logo.svg
Удалить /src/App.test.js
Удалить /src/App.css
Структура папок должна выглядеть следующим образом:

Теперь мы почти готовы приступить к изменению кода приложения React. Но сначала не забудьте установить библиотеки для Bootstrap и Ethers.js. Bootstrap - это популярный CSS-фреймворк для фронтенда, который поставляется с удобными для React виджетами пользовательского интерфейса с наложенной стилизацией CSS, а Ethers.js позволяет нам подключить наш фронтенд к развернутым смарт-контрактам на блокчейне. Убедитесь, что эти команды выполняются из папки "frontend".
cd frontend npm install bootstrap npm install ethers
Теперь мы готовы к изменению кода приложения React. Откройте файл App.js в папке /src/ и удалите его содержимое. Мы начнем создавать его с нуля.
Первый шаг - сообщить приложению, что мы хотим использовать React (включая библиотеки useEffect и useState) и Ethers.js:
import React, { useEffect, useState } from 'react'; import { ethers } from "ethers";
Затем создайте функцию под названием "App" и экспортируйте ее:
Теперь мы начнем заполнять содержимое функции "App". Добавьте в нее следующий код. Этот код делает следующее:
Устанавливает реактивные хуки storedPrice и setStoresPrice. Создает соединение с вашим кошельком MetaMask Web3. Устанавливает адрес развернутого смарт-контракта и ABI. Оба эти параметра необходимы Ethers.js для взаимодействия с развернутым смарт-контрактом. Адрес смарт-контракта можно получить из шага развертывания, описанного ранее в этом руководстве. Вставьте это значение вместо строки REPLACE_WITH_DEPLOYED_CONTRACT_ADDRESS. ABI смарт-контракта можно получить из файла /backend/artifacts/contracts/PriceConsumerV3.json, в элементе abi. Вы можете использовать минификатор кода, чтобы отформатировать его в лучшем виде для хранения в вашем приложении.
const [storedPrice, setStoredPrice] = useState(''); const provider = new ethers.providers.Web3Provider(window.ethereum) const signer = provider.getSigner() const contractAddress = <REPLACE_WITH_DEPLOYED_CONTRACT_ADDRESS>’'; const ABI = '[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"getLatestPrice","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"storeLatestPrice","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"storedPrice","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"}]' const contract = new ethers.Contract(contractAddress, ABI, signer);
Теперь мы создадим две функции, которые будут использоваться в нашем приложении:
getStoredPrice подключится к развернутому смарт-контракту и получит текущее значение геттер-функции storedPrice(). Функция setNewPrice будет вызывать функцию storeLatestPrice развернутого смарт-контракта, ждать завершения транзакции, а затем вызывать функцию getStoredPrice для получения сохраненной в смарт-контракте цены. Мы также добавим вызов getStoredPrice в нашу функцию App, чтобы она первоначально вызывала функцию getter при загрузке страницы:
const getStoredPrice = async () => { try { const contractPrice = await contract.storedPrice(); setStoredPrice(parseInt(contractPrice) / 100000000); } catch (error) { console.log("getStoredPrice Error: ", error); } } async function updateNewPrice() { try { const transaction = await contract.storeLatestPrice(); await transaction.wait(); await getStoredPrice(); } catch (error) { console.log("updateNewPrice Error: ", error); } } getStoredPrice() .catch(console.error)
Последним шагом в создании фронтенда является возврат JSX-кода для рендеринга браузером. Вставьте следующий код в нижней части функции App, под вызовом getStorePrice(). Этот код делает следующее:
Возвращает простой двухколоночный макет сетки. Первый столбец содержит текущую хранимую цену ETH/USD в смарт-контракте. Второй столбец содержит кнопку, которую пользователь может использовать для взаимодействия с умным контрактом и обновления сохраненной цены. Нажатие кнопки вызывает функцию setNewPrice, описанную выше.
return ( <div className="container"> <div className="row mt-5"> <div className="col"> <h3>Stored Price</h3> <p>Stored ETH/USD Price: {storedPrice}</p> </div> <div className="col"> <h3>Update Price</h3> <button type="submit" className="btn btn-dark" onClick={updateNewPrice}>Update</button> </div> </div> </div> );
Теперь ваше приложение готово. При необходимости вы можете сравнить свой код с готовым примером, чтобы убедиться, что вы все сделали правильно. Теперь вы готовы к запуску вашего dApp.
Запуск приложения dApp Убедившись, что все файлы сохранены, запустите свой dApp локально, выполнив следующую команду из папки frontend:
npm run start
После загрузки приложения в вашем браузере должно появиться новое окно, отображающее пользовательский интерфейс dApp. Вы также должны получить всплывающее уведомление от MetaMask с просьбой подключить ваш кошелек к приложению.

После того как вы убедитесь, что пополнили свой счет в MetaMask некоторым количеством Rinkeby ETH, нажмите кнопку "Обновить" в пользовательском интерфейсе dApp, чтобы взаимодействовать с развернутым смарт-контрактом в сети Rinkeby. Вы должны получить уведомление от MetaMask с просьбой подтвердить транзакцию. После этого в течение нескольких секунд ваш dApp должен автоматически обновиться, а в разделе "Сохраненная цена" появится текущая цена Ethereum:

Поздравляем, теперь вы успешно создали, развернули и взаимодействовали с простым dApp! В этом руководстве вы просто запустите фронтенд локально на своем компьютере, но при желании вы можете развернуть его на облачном сервере или даже децентрализовать фронтенд и развернуть его на IPFS! Вы также можете поработать с CSS приложения, чтобы изменить внешний вид пользовательского интерфейса.
Резюме
Децентрализованные приложения - это приложения, которые заменяют традиционную обработку данных на внутреннем сервере технологиями Web3, такими как блокчейн и смарт-контракты, что дает им уникальные гарантии безопасности и устойчивости к цензуре, которые невозможны для традиционных веб-приложений.
В этой технической демонстрации мы создали простой dApp, содержащий смарт-контракт, который получает последнюю цену от ETH/USD Data Feed и сохраняет результат в смарт-контракте. Затем мы создали простой пользовательский интерфейс, использующий React и Ethers.js для подключения к развернутому смарт-контракту и взаимодействия с ним.
Телеграм канал про web3 разработку, смарт-контракты и оракулы.
