Децентрализованные приложения или 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 на базе блокчейна.

Архитектура Ethereum dApp
Архитектура Ethereum 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.

Создание файла .env
Создание файла .env

Следующим шагом будет изменение содержимого файла 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 front-end
Структура папок React front-end

Теперь мы почти готовы приступить к изменению кода приложения 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 с просьбой подключить ваш кошелек к приложению.

Фронтенд React
Фронтенд React

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

Фронтенд React, показывающий результат работы Data Feed
Фронтенд React, показывающий результат работы Data Feed

Поздравляем, теперь вы успешно создали, развернули и взаимодействовали с простым dApp! В этом руководстве вы просто запустите фронтенд локально на своем компьютере, но при желании вы можете развернуть его на облачном сервере или даже децентрализовать фронтенд и развернуть его на IPFS! Вы также можете поработать с CSS приложения, чтобы изменить внешний вид пользовательского интерфейса.

Резюме

Децентрализованные приложения - это приложения, которые заменяют традиционную обработку данных на внутреннем сервере технологиями Web3, такими как блокчейн и смарт-контракты, что дает им уникальные гарантии безопасности и устойчивости к цензуре, которые невозможны для традиционных веб-приложений.

В этой технической демонстрации мы создали простой dApp, содержащий смарт-контракт, который получает последнюю цену от ETH/USD Data Feed и сохраняет результат в смарт-контракте. Затем мы создали простой пользовательский интерфейс, использующий React и Ethers.js для подключения к развернутому смарт-контракту и взаимодействия с ним.


Телеграм канал про web3 разработку, смарт-контракты и оракулы.

Комментарии (0)