Перейти к основному содержанию

Смарт-контракт Hello World для начинающих — полный стек

Solidity
hardhat
alchemy
Умные контракты
развертывание
обозреватель блоков
интерфейс
транзакции
Beginner
nstrike2
25 октября 2021 г.
41 минута прочтения

Это руководство для вас, если вы новичок в разработке блокчейна и не знаете, с чего начать или как развертывать смарт-контракты и взаимодействовать с ними. Мы рассмотрим создание и развертывание простого смарт-контракта в тестовой сети Goerli с использованием MetaMask (opens in a new tab), Solidity (opens in a new tab), Hardhat (opens in a new tab) и Alchemy (opens in a new tab).

Для выполнения этого руководства вам понадобится аккаунт Alchemy. Зарегистрируйте бесплатный аккаунт (opens in a new tab).

Если у вас возникнут вопросы, обращайтесь в Discord Alchemy (opens in a new tab)!

Часть 1. Создание и развертывание вашего смарт-контракта с помощью Hardhat

Подключение к сети Ethereum

Существует много способов отправлять запросы в сеть Ethereum. Для простоты мы будем использовать бесплатный аккаунт на Alchemy, платформе для разработчиков блокчейна и API, которая позволяет нам взаимодействовать с блокчейном Ethereum без необходимости запускать собственный узел. Alchemy также имеет инструменты для разработчиков для мониторинга и аналитики; мы воспользуемся ими в этом руководстве, чтобы понять, что происходит «под капотом» при развертывании нашего смарт-контракта.

Создайте свое приложение и ключ API

После создания аккаунта Alchemy вы можете сгенерировать ключ API, создав приложение. Это позволит вам делать запросы к тестовой сети Goerli. Если вы не знакомы с тестовыми сетями, вы можете прочитать руководство Alchemy по выбору сети (opens in a new tab).

На панели инструментов Alchemy найдите выпадающее меню Приложения на панели навигации и нажмите Создать приложение.

Создание приложения Hello world

Дайте вашему приложению имя «Hello World» и напишите краткое описание. Выберите Staging в качестве среды и Goerli в качестве сети.

Вид создания приложения hello world

Примечание: обязательно выберите Goerli, иначе это руководство не будет работать.

Нажмите Создать приложение. Ваше приложение появится в таблице ниже.

Создайте аккаунт Ethereum

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

Вы можете бесплатно скачать и создать аккаунт MetaMask здесь (opens in a new tab). При создании аккаунта или если у вас уже есть аккаунт, убедитесь, что вы переключились на «тестовую сеть Goerli» в правом верхнем углу (чтобы мы не имели дело с реальными деньгами).

Шаг 4. Добавьте эфир из крана (Faucet)

Чтобы развернуть свой смарт-контракт в тестовой сети, вам понадобится немного тестовых ETH. Чтобы получить ETH в сети Goerli, перейдите в кран Goerli и введите адрес своего аккаунта Goerli. Обратите внимание, что краны Goerli в последнее время могут быть немного ненадежными — смотрите страницу тестовых сетей для списка вариантов, которые можно попробовать:

Примечание: из-за перегрузки сети это может занять некоторое время. ``

Шаг 5: Проверьте свой баланс

Чтобы перепроверить, что ETH находится в вашем кошельке, давайте сделаем запрос eth_getBalance (opens in a new tab) с помощью инструмента-компоновщика Alchemy (opens in a new tab). Результат будет содержать сумму ETH в нашем кошельке. Чтобы узнать больше, посмотрите короткое руководство от Alchemy о том, как использовать инструмент-компоновщик (opens in a new tab).

Введите адрес своего аккаунта MetaMask и нажмите Отправить запрос. Вы увидите ответ, похожий на фрагмент кода ниже.

1{ "jsonrpc": "2.0", "id": 0, "result": "0x2B5E3AF16B1880000" }

Примечание: этот результат указан в wei, а не в ETH. Wei используется как наименьшая единица эфира.

Фух! Наши ненастоящие деньги уже все там.

Шаг 6: Инициализация нашего проекта

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

1mkdir hello-world
2cd hello-world

Теперь, когда мы находимся в папке нашего проекта, мы будем использовать npm init для его инициализации.

Если у вас еще не установлен npm, следуйте этим инструкциям по установке Node.js и npm (opens in a new tab).

Для целей этого руководства не имеет значения, как вы ответите на вопросы инициализации. Вот как мы это сделали для примера:

1имя пакета: (hello-world)
2версия: (1.0.0)
3описание: смарт-контракт hello world
4точка входа: (index.js)
5команда для тестирования:
6репозиторий git:
7ключевые слова:
8автор:
9лицензия: (ISC)
10
11Будет записано в /Users/.../.../.../hello-world/package.json:
12
13{
14 "name": "hello-world",
15 "version": "1.0.0",
16 "description": "смарт-контракт hello world",
17 "main": "index.js",
18 "scripts": {
19 "test": "echo \"Error: no test specified\" && exit 1"
20 },
21 "author": "",
22 "license": "ISC"
23}
Показать все

Подтвердите package.json, и мы готовы!

Шаг 7: Загрузка Hardhat

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

Внутри нашего проекта hello-world запустите:

1npm install --save-dev hardhat

Более подробную информацию об инструкциях по установке (opens in a new tab) можно найти на этой странице.

Шаг 8: Создание проекта Hardhat

В папке нашего проекта hello-world выполните:

1npx hardhat

Вы увидите приветственное сообщение и интерфейс с вариантами того, что делать дальше. Выберите "create an empty hardhat.config.js":

1888 888 888 888 888
2888 888 888 888 888
3888 888 888 888 888
48888888888 8888b. 888d888 .d88888 88888b. 8888b. 888888
5888 888 "88b 888P" d88" 888 888 "88b "88b 888
6888 888 .d888888 888 888 888 888 888 .d888888 888
7888 888 888 888 888 Y88b 888 888 888 888 888 Y88b.
8888 888 "Y888888 888 "Y88888 888 888 "Y888888 "Y888
9
10👷 Добро пожаловать в Hardhat v2.0.11 👷‍
11
12Что вы хотите сделать? …
13Создать пример проекта
14❯ Создать пустой hardhat.config.js
15Выйти
Показать все

Это создаст файл hardhat.config.js в проекте. Мы будем использовать его позже в этом руководстве, чтобы указать настройки для нашего проекта.

Шаг 9: Добавление папок проекта

Чтобы поддерживать порядок в проекте, давайте создадим две новые папки. В командной строке перейдите в корневой каталог вашего проекта hello-world и введите:

1mkdir contracts
2mkdir scripts
  • contracts/ — здесь мы будем хранить файл с кодом нашего смарт-контракта «hello world».
  • scripts/ — здесь мы будем хранить скрипты для развертывания нашего контракта и взаимодействия с ним.

Шаг 10: Написание нашего контракта

Вы можете спросить себя, когда мы собираемся писать код? Время пришло!

Откройте проект hello-world в своем любимом редакторе. Смарт-контракты чаще всего пишутся на Solidity, который мы и будем использовать для написания нашего смарт-контракта.

  1. Перейдите в папку contracts и создайте новый файл с именем HelloWorld.sol
  2. Ниже приведен пример смарт-контракта Hello World, который мы будем использовать в этом руководстве. Скопируйте содержимое ниже в файл HelloWorld.sol.

Примечание: обязательно прочитайте комментарии, чтобы понять, что делает этот контракт.

1// Указывает версию Solidity, используя семантическое версионирование.
2// Узнайте больше: https://solidity.readthedocs.io/en/v0.5.10/layout-of-source-files.html#pragma
3pragma solidity >=0.7.3;
4
5// Определяет контракт с именем `HelloWorld`.
6// Контракт — это набор функций и данных (его состояние). После развертывания контракт находится по определенному адресу в блокчейне Ethereum. Узнайте больше: https://solidity.readthedocs.io/en/v0.5.10/structure-of-a-contract.html
7contract HelloWorld {
8
9 // Генерируется при вызове функции update
10 //События смарт-контракта — это способ сообщить из вашего контракта во фронтенд вашего приложения о том, что что-то произошло в блокчейне. Фронтенд может «прослушивать» определенные события и предпринимать действия, когда они происходят.
11 event UpdatedMessages(string oldStr, string newStr);
12
13 // Объявляет переменную состояния `message` типа `string`.
14 // Переменные состояния — это переменные, значения которых постоянно хранятся в хранилище контракта. Ключевое слово `public` делает переменные доступными извне контракта и создает функцию, которую другие контракты или клиенты могут вызывать для доступа к значению.
15 string public message;
16
17 // Подобно многим объектно-ориентированным языкам на основе классов, конструктор — это специальная функция, которая выполняется только при создании контракта.
18 // Конструкторы используются для инициализации данных контракта. Узнайте больше:https://solidity.readthedocs.io/en/v0.5.10/contracts.html#constructors
19 constructor(string memory initMessage) {
20
21 // Принимает строковый аргумент `initMessage` и устанавливает значение в переменную хранилища контракта `message`).
22 message = initMessage;
23 }
24
25 // Публичная функция, которая принимает строковый аргумент и обновляет переменную хранилища `message`.
26 function update(string memory newMessage) public {
27 string memory oldMsg = message;
28 message = newMessage;
29 emit UpdatedMessages(oldMsg, newMessage);
30 }
31}
Показать все

Это базовый смарт-контракт, который хранит сообщение при создании. Его можно обновить, вызвав функцию update.

Шаг 11. Подключите MetaMask и Alchemy к вашему проекту

Мы создали кошелек MetaMask, учетную запись Alchemy и написали наш смарт-контракт, теперь пришло время их соединить.

Каждая транзакция, отправляемая из вашего кошелька, требует подписи с использованием вашего уникального приватного ключа. Чтобы предоставить нашей программе это разрешение, мы можем безопасно хранить наш приватный ключ в файле среды. Мы также будем хранить здесь ключ API для Alchemy.

Чтобы узнать больше об отправке транзакций, ознакомьтесь с этим руководством (opens in a new tab) по отправке транзакций с помощью web3.

Во-первых, установите dotenv, находясь в директории проекта:

1npm install dotenv --save

Затем создайте файл .env в корневом каталоге проекта. Добавьте в него свой приватный ключ MetaMask и URL-адрес HTTP API Alchemy.

Ваш файл среды должен называться .env, иначе он не будет распознан как файл среды.

Не называйте его process.env, .env-custom или как-либо еще.

Ваш .env должен выглядеть следующим образом:

1API_URL = "https://eth-goerli.alchemyapi.io/v2/ваш-api-ключ"
2PRIVATE_KEY = "ваш-приватный-ключ-metamask"

Чтобы подключить их к нашему коду, мы будем ссылаться на эти переменные в нашем файле hardhat.config.js в шаге 13.

Шаг 12: Установите Ethers.js

Ethers.js — это библиотека, которая упрощает взаимодействие и отправку запросов в Ethereum, оборачивая стандартные методы JSON-RPC (opens in a new tab) в более удобные для пользователя методы.

Hardhat позволяет нам интегрировать плагины (opens in a new tab) для дополнительных инструментов и расширенной функциональности. Мы воспользуемся плагином Ethers (opens in a new tab) для развертывания контракта.

В директории проекта запустите:

npm install --save-dev @nomiclabs/hardhat-ethers "ethers@^5.0.0"

Шаг 13: Обновление hardhat.config.js

Мы добавили несколько зависимостей и плагинов, и теперь нам нужно обновить hardhat.config.js, чтобы наш проект знал обо всех них.

Обновите ваш hardhat.config.js, чтобы он выглядел следующим образом:

1/**
2 * @type import('hardhat/config').HardhatUserConfig
3 */
4
5require("dotenv").config()
6require("@nomiclabs/hardhat-ethers")
7
8const { API_URL, PRIVATE_KEY } = process.env
9
10module.exports = {
11 solidity: "0.7.3",
12 defaultNetwork: "goerli",
13 networks: {
14 hardhat: {},
15 goerli: {
16 url: API_URL,
17 accounts: [`0x${PRIVATE_KEY}`],
18 },
19 },
20}
Показать все

Шаг 14: Компиляция нашего контракта

Пора заставить это работать, давайте скомпилируем наш контракт. Задача compile — одна из встроенных задач hardhat.

Запустите в командной строке:

npx hardhat compile

Вы можете получить предупреждение SPDX license identifier not provided in source file, но не стоит об этом беспокоиться — надеемся, все остальное выглядит хорошо! Если нет, вы всегда можете написать в Discord-канал Alchemy (opens in a new tab).

Шаг 15: Написание нашего скрипта развертывания

Контракт написан, файл конфигурации корректен, пора писать скрипт развертывания.

Перейдите в папку scripts/, создайте новый файл deploy.js и добавьте в него следующее содержимое:

1async function main() {
2 const HelloWorld = await ethers.getContractFactory("HelloWorld")
3
4 // Начинаем развертывание, возвращая promise, который разрешается в объект контракта
5 const hello_world = await HelloWorld.deploy("Hello World!")
6 console.log("Контракт развернут по адресу:", hello_world.address)
7}
8
9main()
10 .then(() => process.exit(0))
11 .catch((error) => {
12 console.error(error)
13 process.exit(1)
14 })
Показать все

Hardhat отлично объясняет, что делает каждая из этих строк кода, в своем руководстве по контрактам (opens in a new tab), мы использовали их объяснения здесь.

1const HelloWorld = await ethers.getContractFactory("HelloWorld")

ContractFactory в ethers.js — это абстракция, используемая для развертывания новых смарт-контрактов, поэтому HelloWorld здесь — это фабрика (opens in a new tab) для экземпляров нашего контракта hello world. При использовании плагина hardhat-ethers экземпляры ContractFactory и Contract по умолчанию подключаются к первому подписанту (владельцу).

1const hello_world = await HelloWorld.deploy()

Вызов deploy() на ContractFactory запустит развертывание и вернет Promise, который разрешается в объект Contract. Это объект, который имеет метод для каждой из функций нашего смарт контракта.

Шаг 16: Разверните наш контракт

Мы наконец-то готовы развернуть наш смарт контракт! Перейдите в командную строку и запустите:

npx hardhat run scripts/deploy.js --network goerli

Вы должны увидеть что-то наподобие:

Контракт развернут по адресу: 0x6cd7d44516a20882cEa2DE9f205bF401c0d23570

Пожалуйста, сохраните этот адрес. Мы будем использовать его позже в этом руководстве.

Если мы перейдем на Etherscan для Goerli (opens in a new tab) и поищем адрес нашего контракта, мы должны увидеть, что он был успешно развернут. Транзакция будет выглядеть примерно так:

Адрес From должен совпадать с адресом вашего аккаунта MetaMask, а в адресе To будет указано Создание контракта. Если мы щелкнем по транзакции, то увидим адрес нашего контракта в поле To.

Поздравляем! Вы только что развернули смарт-контракт в тестовой сети Ethereum.

Чтобы понять, что происходит «под капотом», давайте перейдем на вкладку Explorer на нашей панели инструментов Alchemy (opens in a new tab). Если у вас несколько приложений Alchemy, убедитесь, что вы отфильтровали их по приложению и выбрали Hello World.

Здесь вы увидите несколько методов JSON-RPC, которые Hardhat/Ethers сделали для нас «под капотом», когда мы вызвали функцию .deploy(). Два важных метода здесь — это eth_sendRawTransaction (opens in a new tab), который является запросом на запись нашего контракта в сеть Goerli, и eth_getTransactionByHash (opens in a new tab), который является запросом на чтение информации о нашей транзакции по заданному хэшу. Чтобы узнать больше об отправке транзакций, ознакомьтесь с нашим руководством по отправке транзакций с помощью Web3.

Часть 2. Взаимодействие с вашим смарт-контрактом

Теперь, когда мы успешно развернули смарт-контракт в сети Goerli, давайте научимся с ним взаимодействовать.

Создайте файл interact.js

Это файл, в котором мы напишем наш скрипт взаимодействия. Мы будем использовать библиотеку Ethers.js, которую вы ранее установили в Части 1.

В папке scripts/ создайте новый файл с именем interact.js и добавьте следующий код:

1// interact.js
2
3const API_KEY = process.env.API_KEY
4const PRIVATE_KEY = process.env.PRIVATE_KEY
5const CONTRACT_ADDRESS = process.env.CONTRACT_ADDRESS

Обновите ваш файл .env

Мы будем использовать новые переменные среды, поэтому нам нужно определить их в файле .env, который мы создали ранее.

Нам нужно будет добавить определение для нашего API_KEY Alchemy и CONTRACT_ADDRESS, по которому был развернут ваш смарт-контракт.

Ваш файл .env должен выглядеть примерно так:

# .env
API_URL = "https://eth-goerli.alchemyapi.io/v2/<ваш-api-ключ>"
API_KEY = "<ваш-api-ключ>"
PRIVATE_KEY = "<ваш-приватный-ключ-metamask>"
CONTRACT_ADDRESS = "0x<адрес_вашего_контракта>"

Получите ABI вашего контракта

Наш контракта — это интерфейс для взаимодействия с нашим смарт-контрактом. Hardhat автоматически генерирует ABI и сохраняет его в HelloWorld.json. Чтобы использовать ABI, нам нужно будет разобрать его содержимое, добавив следующие строки кода в наш файл interact.js:

1// interact.js
2const contract = require("../artifacts/contracts/HelloWorld.sol/HelloWorld.json")

Если вы хотите увидеть ABI, вы можете вывести его в консоль:

1console.log(JSON.stringify(contract.abi))

Чтобы увидеть ваш ABI, выведенный в консоль, перейдите в терминал и выполните:

npx hardhat run scripts/interact.js

Создайте экземпляр вашего контракта

Для взаимодействия с нашим контрактом нам нужно создать экземпляр контракта в нашем коде. Чтобы сделать это с помощью Ethers.js, нам нужно будет работать с тремя концепциями:

  1. Provider (провайдер) — поставщик узлов, который дает вам доступ на чтение и запись в блокчейн.
  2. Signer (подписант) — представляет аккаунт Ethereum, который может подписывать транзакции.
  3. Contract (контракт) — объект Ethers.js, представляющий конкретный контракт, развернутый в сети.

Мы будем использовать ABI контракта из предыдущего шага, чтобы создать наш экземпляр контракта:

1// interact.js
2
3// Провайдер
4const alchemyProvider = new ethers.providers.AlchemyProvider(
5 (network = "goerli"),
6 API_KEY
7)
8
9// Подписант
10const signer = new ethers.Wallet(PRIVATE_KEY, alchemyProvider)
11
12// Контракт
13const helloWorldContract = new ethers.Contract(
14 CONTRACT_ADDRESS,
15 contract.abi,
16 signer
17)
Показать все

Узнайте больше о провайдерах, подписантах и контрактах в документации ethers.js (opens in a new tab).

Прочитайте начальное сообщение

Помните, как мы развертывали наш контракт с initMessage = "Hello world!"? Теперь мы собираемся прочитать это сообщение, хранящееся в нашем смарт-контракте, и вывести его в консоль.

В JavaScript асинхронные функции используются при взаимодействии с сетями. Чтобы узнать больше об асинхронных функциях, прочитайте эту статью на Medium (opens in a new tab).

Используйте приведенный ниже код, чтобы вызвать функцию message в нашем смарт-контракте и прочитать начальное сообщение:

1// interact.js
2
3// ...
4
5async function main() {
6 const message = await helloWorldContract.message()
7 console.log("Сообщение: " + message)
8}
9main()
Показать все

После запуска файла с помощью npx hardhat run scripts/interact.js в терминале мы должны увидеть следующий ответ:

1Сообщение: Hello world!

Поздравляем! Вы только что успешно прочитали данные смарт-контракта из блокчейна Ethereum, так держать!

Обновите сообщение

Вместо того, чтобы просто читать сообщение, мы также можем обновить сообщение, сохраненное в нашем смарт-контракте, с помощью функции update! Круто, не так ли?

Чтобы обновить сообщение, мы можем напрямую вызвать функцию update на нашем созданном объекте Contract:

1// interact.js
2
3// ...
4
5async function main() {
6 const message = await helloWorldContract.message()
7 console.log("Сообщение: " + message)
8
9 console.log("Обновление сообщения...")
10 const tx = await helloWorldContract.update("Это новое сообщение.")
11 await tx.wait()
12}
13main()
Показать все

Обратите внимание, что в строке 11 мы вызываем .wait() для возвращенного объекта транзакции. Это гарантирует, что наш скрипт дождется майнинга транзакции в блокчейне перед выходом из функции. Если вызов .wait() не включен, скрипт может не увидеть обновленное значение message в контракте.

Прочитайте новое сообщение

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

Если вам нужна подсказка, вот как должен выглядеть ваш файл interact.js на данном этапе:

1// interact.js
2
3const API_KEY = process.env.API_KEY
4const PRIVATE_KEY = process.env.PRIVATE_KEY
5const CONTRACT_ADDRESS = process.env.CONTRACT_ADDRESS
6
7const contract = require("../artifacts/contracts/HelloWorld.sol/HelloWorld.json")
8
9// провайдер - Alchemy
10const alchemyProvider = new ethers.providers.AlchemyProvider(
11 (network = "goerli"),
12 API_KEY
13)
14
15// подписант - вы
16const signer = new ethers.Wallet(PRIVATE_KEY, alchemyProvider)
17
18// экземпляр контракта
19const helloWorldContract = new ethers.Contract(
20 CONTRACT_ADDRESS,
21 contract.abi,
22 signer
23)
24
25async function main() {
26 const message = await helloWorldContract.message()
27 console.log("Сообщение: " + message)
28
29 console.log("Обновление сообщения...")
30 const tx = await helloWorldContract.update("это новое сообщение")
31 await tx.wait()
32
33 const newMessage = await helloWorldContract.message()
34 console.log("Новое сообщение: " + newMessage)
35}
36
37main()
Показать все

Теперь просто запустите скрипт, и вы сможете увидеть старое сообщение, статус обновления и новое сообщение, выведенные в ваш терминал!

npx hardhat run scripts/interact.js --network goerli

1Сообщение: Hello World!
2Обновление сообщения...
3Новое сообщение: This is the new message.

Во время выполнения этого скрипта вы можете заметить, что шаг Обновление сообщения... занимает некоторое время перед загрузкой нового сообщения. Это связано с процессом майнинга; если вам интересно отслеживать транзакции во время их майнинга, посетите мемпул Alchemy (opens in a new tab), чтобы увидеть статус транзакции. Если транзакция была отброшена, также полезно проверить Etherscan для Goerli (opens in a new tab) и найти хэш вашей транзакции.

Часть 3: Публикация вашего смарт-контракта на Etherscan

Вы проделали всю тяжелую работу по воплощению вашего смарт-контракта в жизнь; теперь пришло время поделиться им со всем миром!

Проверив свой смарт-контракт на Etherscan, любой сможет просмотреть ваш исходный код и взаимодействовать с вашим смарт-контрактом. Давайте начнем!

Шаг 1: Создайте ключ API в своей учетной записи Etherscan

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

Если у вас еще нет аккаунта Etherscan, зарегистрируйте аккаунт (opens in a new tab).

После входа в систему найдите свое имя пользователя на панели навигации, наведите на него курсор и выберите кнопку Мой профиль.

На странице вашего профиля вы должны увидеть боковую панель навигации. На боковой панели навигации выберите API Keys. Затем нажмите кнопку «Add», чтобы создать новый ключ API, назовите свое приложение hello-world и нажмите кнопку Create New API Key.

Ваш новый ключ API должен появиться в таблице ключей API. Скопируйте ключ API в буфер обмена.

Далее нам нужно добавить ключ API Etherscan в наш файл .env.

После его добавления ваш файл .env должен выглядеть так:

1API_URL = "https://eth-goerli.alchemyapi.io/v2/ваш-api-ключ"
2PUBLIC_KEY = "ваш-публичный-адрес-аккаунта"
3PRIVATE_KEY = "ваш-приватный-адрес-аккаунта"
4CONTRACT_ADDRESS = "адрес-вашего-контракта"
5ETHERSCAN_API_KEY = "ваш-ключ-etherscan"

Смарт-контракты, развернутые с помощью Hardhat

Установите hardhat-etherscan

Публикация вашего контракта на Etherscan с помощью Hardhat очень проста. Сначала вам нужно будет установить плагин hardhat-etherscan. hardhat-etherscan автоматически проверит исходный код смарт-контракта и ABI на Etherscan. Чтобы добавить его, в каталоге hello-world выполните:

1npm install --save-dev @nomiclabs/hardhat-etherscan

После установки включите следующее выражение вверху вашего hardhat.config.js и добавьте параметры конфигурации Etherscan:

1// hardhat.config.js
2
3require("dotenv").config()
4require("@nomiclabs/hardhat-ethers")
5require("@nomiclabs/hardhat-etherscan")
6
7const { API_URL, PRIVATE_KEY, ETHERSCAN_API_KEY } = process.env
8
9module.exports = {
10 solidity: "0.7.3",
11 defaultNetwork: "goerli",
12 networks: {
13 hardhat: {},
14 goerli: {
15 url: API_URL,
16 accounts: [`0x${PRIVATE_KEY}`],
17 },
18 },
19 etherscan: {
20 // Ваш ключ API для Etherscan
21 // Получите его на https://etherscan.io/
22 apiKey: ETHERSCAN_API_KEY,
23 },
24}
Показать все

Подтвердите свой смарт-контракт на Etherscan

Убедитесь, что все файлы сохранены и все переменные .env настроены правильно.

Запустите задачу verify, передав адрес контракта и сеть, в которой он развернут:

1npx hardhat verify --network goerli АДРЕС_РАЗВЕРНУТОГО_КОНТРАКТА 'Hello World!'

Убедитесь, что DEPLOYED_CONTRACT_ADDRESS — это адрес вашего развернутого смарт-контракта в тестовой сети Goerli. Кроме того, последний аргумент ('Hello World!') должен быть той же строковой величиной, которая использовалась на этапе развертывания в части 1.

Если все пройдет хорошо, вы увидите следующее сообщение в своем терминале:

1Исходный код для контракта успешно отправлен
2contracts/HelloWorld.sol:HelloWorld по адресу 0xdeployed-contract-address
3для проверки на Etherscan. Ожидание результата проверки...
4
5
6Контракт HelloWorld успешно проверен на Etherscan.
7https://goerli.etherscan.io/address/<адрес-контракта>#contracts

Поздравляем! Код вашего смарт-контракта находится на Etherscan!

Проверьте свой смарт-контракт на Etherscan!

Когда вы перейдете по ссылке, указанной в вашем терминале, вы сможете увидеть код вашего смарт-контракта и ABI, опубликованные на Etherscan!

Урааа — ты сделал это, чемпион! Теперь любой может вызывать или записывать данные в ваш смарт-контракт! Нам не терпится увидеть, что вы создадите дальше!

Часть 4 — Интеграция вашего смарт-контракта с фронтендом

К концу этого руководства вы узнаете, как:

  • Подключить кошелек MetaMask к вашему децентрализованному приложению
  • Считывать данные с вашего смарт-контракта с помощью API Alchemy Web3 (opens in a new tab)
  • Подписывать транзакции Ethereum с помощью MetaMask

Для этого децентрализованного приложения мы будем использовать React (opens in a new tab) в качестве нашего фронтенд-фреймворка; однако важно отметить, что мы не будем тратить много времени на разбор его основ, поскольку в основном мы сосредоточимся на внедрении функциональности Web3 в наш проект.

В качестве предварительного условия вы должны иметь начальный уровень понимания React. В противном случае мы рекомендуем пройти официальное руководство по введению в React (opens in a new tab).

Клонируйте стартовые файлы

Сначала перейдите в репозиторий GitHub hello-world-part-four (opens in a new tab), чтобы получить стартовые файлы для этого проекта, и клонируйте этот репозиторий на свой локальный компьютер.

Откройте клонированный репозиторий локально. Обратите внимание, что он содержит две папки: starter-files и completed.

  • starter-files - мы будем работать в этом каталоге, мы подключим пользовательский интерфейс к вашему кошельку Ethereum и смарт-контракту, который мы опубликовали на Etherscan в Части 3.
  • completed содержит полностью завершенное руководство и должен использоваться только в качестве справочника, если вы застряли.

Далее откройте свою копию starter-files в вашем любимом редакторе кода, а затем перейдите в папку src.

Весь код, который мы напишем, будет находиться в папке src. Мы будем редактировать компонент HelloWorld.js и файлы JavaScript util/interact.js, чтобы придать нашему проекту функциональность Web3.

Ознакомьтесь со стартовыми файлами

Прежде чем мы начнем писать код, давайте рассмотрим, что нам предоставлено в стартовых файлах.

Запустите ваш React-проект

Начнем с запуска проекта React в нашем браузере. Прелесть React в том, что как только наш проект запускается в нашем браузере, любые сохраняемые нами изменения будут обновляться в реальном времени в нашем браузере.

Чтобы запустить проект, перейдите в корневой каталог папки starter-files и запустите npm install в своем терминале, чтобы установить зависимости проекта:

cd starter-files
npm install

После завершения установки выполните npm start в терминале:

npm start

Это должно открыть http://localhost:3000/ (opens in a new tab) в вашем браузере, где вы увидите интерфейс нашего проекта. Он должен состоять из одного поля (места для обновления сообщения, хранящегося в вашем смарт-контракте), кнопки «Подключить кошелек» и кнопки «Обновить».

Если вы попробуете нажать на любую из кнопок, вы заметите, что они не работают — это потому, что нам все еще нужно запрограммировать их функциональность.

Компонент HelloWorld.js

Давайте вернемся в папку src в нашем редакторе и откроем файл HelloWorld.js. Очень важно, чтобы мы понимали все в этом файле, поскольку это основной компонент React, над которым мы будем работать.

В верхней части этого файла вы заметите, что у нас есть несколько операторов импорта, которые необходимы для запуска нашего проекта, включая библиотеку React, хуки useEffect и useState, некоторые элементы из ./util/interact.js (мы опишем их более подробно в ближайшее время!) и логотип Alchemy.

1// HelloWorld.js
2
3import React from "react"
4import { useEffect, useState } from "react"
5import {
6 helloWorldContract,
7 connectWallet,
8 updateMessage,
9 loadCurrentMessage,
10 getCurrentWalletConnected,
11} from "./util/interact.js"
12
13import alchemylogo from "./alchemylogo.svg"
Показать все

Далее у нас есть переменные состояния, которые мы будем обновлять после определенных событий.

1// HelloWorld.js
2
3//Переменные состояния
4const [walletAddress, setWallet] = useState("")
5const [status, setStatus] = useState("")
6const [message, setMessage] = useState("Нет подключения к сети.")
7const [newMessage, setNewMessage] = useState("")

Вот что представляет собой каждая из переменных:

  • walletAddress — строка, в которой хранится адрес кошелька пользователя
  • status — строка, в которой хранится полезное сообщение, которое помогает пользователю взаимодействовать с децентрализованным приложением
  • message — строка, в которой хранится текущее сообщение в смарт-контракте
  • newMessage — строка, в которой хранится новое сообщение, которое будет записано в смарт-контракт

После переменных состояния вы увидите пять нереализованных функций: useEffect, addSmartContractListener, addWalletListener, connectWalletPressed и onUpdatePressed. Ниже мы объясним, что они делают:

1// HelloWorld.js
2
3//вызывается только один раз
4useEffect(async () => {
5 //TODO: реализовать
6}, [])
7
8function addSmartContractListener() {
9 //TODO: реализовать
10}
11
12function addWalletListener() {
13 //TODO: реализовать
14}
15
16const connectWalletPressed = async () => {
17 //TODO: реализовать
18}
19
20const onUpdatePressed = async () => {
21 //TODO: реализовать
22}
Показать все
  • useEffect (opens in a new tab) — это хук React, который вызывается после рендеринга вашего компонента. Поскольку в него передается пустой массив [] в качестве свойства (см. строку 4), он будет вызываться только при первом рендеринге компонента. Здесь мы загрузим текущее сообщение, хранящееся в нашем смарт-контракте, вызовем наши прослушиватели смарт-контракта и кошелька и обновим наш пользовательский интерфейс, чтобы отразить, подключен ли уже кошелек.
  • addSmartContractListener — эта функция настраивает прослушиватель, который будет отслеживать событие UpdatedMessages нашего контракта HelloWorld и обновлять наш пользовательский интерфейс при изменении сообщения в нашем смарт-контракте.
  • addWalletListener — эта функция настраивает прослушиватель, который обнаруживает изменения в состоянии кошелька MetaMask пользователя, например, когда пользователь отключает свой кошелек или переключает адреса.
  • connectWalletPressed — эта функция будет вызываться для подключения кошелька MetaMask пользователя к нашему децентрализованному приложению.
  • onUpdatePressed — эта функция будет вызываться, когда пользователь захочет обновить сообщение, хранящееся в смарт-контракте.

Ближе к концу этого файла находится пользовательский интерфейс нашего компонента.

1// HelloWorld.js
2
3//UI нашего компонента
4return (
5 <div id="container">
6 <img id="logo" src={alchemylogo}></img>
7 <button id="walletButton" onClick={connectWalletPressed}>
8 {walletAddress.length > 0 ? (
9 "Подключено: " +
10 String(walletAddress).substring(0, 6) +
11 "..." +
12 String(walletAddress).substring(38)
13 ) : (
14 <span>Подключить кошелек</span>
15 )}
16 </button>
17
18 <h2 style={{ paddingTop: "50px" }}>Текущее сообщение:</h2>
19 <p>{message}</p>
20
21 <h2 style={{ paddingTop: "18px" }}>Новое сообщение:</h2>
22
23 <div>
24 <input
25 type="text"
26 placeholder="Обновите сообщение в вашем смарт-контракте."
27 onChange={(e) => setNewMessage(e.target.value)}
28 value={newMessage}
29 />
30 <p id="status">{status}</p>
31
32 <button id="publishButton" onClick={onUpdatePressed}>
33 Обновить
34 </button>
35</div>
36
37</div>
38)
Показать все

Если вы внимательно изучите этот код, вы заметите, где мы используем наши различные переменные состояния в нашем пользовательском интерфейсе:

  • В строках 6–12, если кошелек пользователя подключен (т. е. walletAddress.length > 0), мы отображаем усеченную версию walletAddress пользователя на кнопке с идентификатором «walletButton»; в противном случае она просто гласит «Подключить кошелек».
  • В строке 17 мы отображаем текущее сообщение, хранящееся в смарт-контракте, которое зафиксировано в строке message.
  • В строках 23–26 мы используем контролируемый компонент (opens in a new tab) для обновления нашей переменной состояния newMessage при изменении ввода в текстовом поле.

В дополнение к нашим переменным состояния вы также увидите, что функции connectWalletPressed и onUpdatePressed вызываются при нажатии кнопок с идентификаторами publishButton и walletButton соответственно.

Наконец, давайте рассмотрим, куда добавляется этот компонент HelloWorld.js.

Если вы перейдете к файлу App.js, который является основным компонентом в React, выступающим в качестве контейнера для всех других компонентов, вы увидите, что наш компонент HelloWorld.js внедряется в строке 7.

И последнее, но не менее важное: давайте рассмотрим еще один предоставленный вам файл — interact.js.

Файл interact.js

Поскольку мы хотим следовать парадигме M-V-C (opens in a new tab), нам понадобится отдельный файл, который содержит все наши функции для управления логикой, данными и правилами нашего децентрализованного приложения, а затем мы сможем экспортировать эти функции в наш интерфейс (наш компонент HelloWorld.js).

👆🏽Именно в этом и заключается цель нашего файла interact.js!

Перейдите в папку util в вашем каталоге src, и вы заметите, что мы включили файл с именем interact.js, который будет содержать все наши функции и переменные для взаимодействия со смарт-контрактами и кошельками.

1// interact.js
2
3//export const helloWorldContract;
4
5export const loadCurrentMessage = async () => {}
6
7export const connectWallet = async () => {}
8
9const getCurrentWalletConnected = async () => {}
10
11export const updateMessage = async (message) => {}
Показать все

Вы заметите, что вверху файла мы закомментировали объект helloWorldContract. Позже в этом руководстве мы раскомментируем этот объект и создадим экземпляр нашего смарт-контракта в этой переменной, который затем экспортируем в наш компонент HelloWorld.js.

Четыре нереализованные функции после нашего объекта helloWorldContract делают следующее:

  • loadCurrentMessage — эта функция обрабатывает логику загрузки текущего сообщения, хранящегося в смарт-контракте. Она сделает вызов для чтения в смарт-контракт Hello World с помощью API Alchemy Web3 (opens in a new tab).
  • connectWallet — эта функция подключит MetaMask пользователя к нашему децентрализованному приложению.
  • getCurrentWalletConnected — эта функция проверит, подключен ли уже аккаунт Ethereum к нашему децентрализованному приложению при загрузке страницы, и соответствующим образом обновит наш пользовательский интерфейс.
  • updateMessage — эта функция обновит сообщение, хранящееся в смарт-контракте. Она сделает вызов для записи в смарт-контракт Hello World, поэтому кошелек MetaMask пользователя должен будет подписать транзакцию Ethereum для обновления сообщения.

Теперь, когда мы понимаем, с чем работаем, давайте разберемся, как читать из нашего смарт-контракта!

Шаг 3: Чтение из вашего смарт-контракта

Чтобы читать из вашего смарт-контракта, вам нужно будет успешно настроить:

  • API-соединение с блокчейном Ethereum
  • Загруженный экземпляр вашего смарт-контракта
  • Функция для вызова функции вашего смарт-контракта
  • Прослушиватель для отслеживания обновлений, когда данные, которые вы читаете из смарт-контракта, изменяются

Это может показаться большим количеством шагов, но не волнуйтесь! Мы проведем вас через каждый из них шаг за шагом! :)

Установите API-соединение с блокчейном Ethereum

Итак, помните, как во второй части этого руководства мы использовали наш ключ Alchemy Web3 для чтения из нашего смарт-контракта (opens in a new tab)? Вам также понадобится ключ Alchemy Web3 в вашем децентрализованном приложении для чтения из блокчейна.

Если у вас его еще нет, сначала установите Alchemy Web3 (opens in a new tab), перейдя в корневой каталог ваших starter-files и выполнив в терминале следующую команду:

1npm install @alch/alchemy-web3

Alchemy Web3 (opens in a new tab) — это оболочка для Web3.js (opens in a new tab), предоставляющая расширенные методы API и другие важные преимущества, облегчающие жизнь веб-разработчика. Он разработан с требованием минимальной настройки, поэтому вы можете сразу начать использовать его в своем приложении!

Затем установите пакет dotenv (opens in a new tab) в каталог вашего проекта, чтобы у нас было безопасное место для хранения нашего ключа API после того, как мы его получим.

1npm install dotenv --save

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

Как только у вас будет ключ API, создайте файл .env в вашем корневом каталоге и добавьте в него свой URL-адрес для Websockets Alchemy. После этого ваш файл .env должен выглядеть следующим образом:

1REACT_APP_ALCHEMY_KEY = wss://eth-goerli.ws.alchemyapi.io/v2/<ключ>

Теперь мы готовы настроить нашу конечную точку Alchemy Web3 в нашем децентрализованном приложении! Давайте вернемся к нашему interact.js, который находится в нашей папке util, и добавим следующий код в начало файла:

1// interact.js
2
3require("dotenv").config()
4const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY
5const { createAlchemyWeb3 } = require("@alch/alchemy-web3")
6const web3 = createAlchemyWeb3(alchemyKey)
7
8//export const helloWorldContract;

Выше мы сначала импортировали ключ Alchemy из нашего файла .env, а затем передали наш alchemyKey в createAlchemyWeb3, чтобы установить нашу конечную точку Alchemy Web3.

С этой готовой конечной точкой пришло время загрузить наш смарт-контракт!

Загрузка вашего смарт-контракта Hello World

Чтобы загрузить ваш смарт-контракт Hello World, вам понадобится его адрес контракта и ABI, оба из которых можно найти на Etherscan, если вы завершили Часть 3 этого руководства.

Как получить ABI вашего контракта с Etherscan

Если вы пропустили часть 3 этого руководства, вы можете использовать контракт HelloWorld с адресом 0x6f3f635A9762B47954229Ea479b4541eAF402A6A (opens in a new tab). Его ABI можно найти здесь (opens in a new tab).

ABI контракта необходим для указания, какую функцию будет вызывать контракт, а также для обеспечения того, чтобы функция возвращала данные в ожидаемом формате. После того, как мы скопировали ABI нашего контракта, давайте сохраним его в виде файла JSON с именем contract-abi.json в вашем каталоге src.

Ваш файл contract-abi.json должен храниться в вашей папке src.

Вооружившись адресом нашего контракта, ABI и конечной точкой Alchemy Web3, мы можем использовать метод contract (opens in a new tab), чтобы загрузить экземпляр нашего смарт-контракта. Импортируйте ABI вашего контракта в файл interact.js и добавьте адрес вашего контракта.

1// interact.js
2
3const contractABI = require("../contract-abi.json")
4const contractAddress = "0x6f3f635A9762B47954229Ea479b4541eAF402A6A"

Теперь мы наконец-то можем раскомментировать нашу переменную helloWorldContract и загрузить смарт-контракт с помощью нашей конечной точки AlchemyWeb3:

1// interact.js
2export const helloWorldContract = new web3.eth.Contract(
3 contractABI,
4 contractAddress
5)

Подводя итог, первые 12 строк вашего interact.js теперь должны выглядеть так:

1// interact.js
2
3require("dotenv").config()
4const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY
5const { createAlchemyWeb3 } = require("@alch/alchemy-web3")
6const web3 = createAlchemyWeb3(alchemyKey)
7
8const contractABI = require("../contract-abi.json")
9const contractAddress = "0x6f3f635A9762B47954229Ea479b4541eAF402A6A"
10
11export const helloWorldContract = new web3.eth.Contract(
12 contractABI,
13 contractAddress
14)
Показать все

Теперь, когда мы загрузили наш контракт, мы можем реализовать нашу функцию loadCurrentMessage!

Реализация loadCurrentMessage в вашем файле interact.js

Эта функция очень проста. Мы сделаем простой асинхронный вызов web3 для чтения из нашего контракта. Наша функция вернет сообщение, хранящееся в смарт-контракте:

Обновите loadCurrentMessage в вашем файле interact.js до следующего:

1// interact.js
2
3export const loadCurrentMessage = async () => {
4 const message = await helloWorldContract.methods.message().call()
5 return message
6}

Поскольку мы хотим отображать этот смарт-контракт в нашем пользовательском интерфейсе, давайте обновим функцию useEffect в нашем компоненте HelloWorld.js до следующего:

1// HelloWorld.js
2
3//вызывается только один раз
4useEffect(async () => {
5 const message = await loadCurrentMessage()
6 setMessage(message)
7}, [])

Обратите внимание, мы хотим, чтобы наша loadCurrentMessage вызывалась только один раз во время первого рендеринга компонента. Вскоре мы реализуем addSmartContractListener для автоматического обновления пользовательского интерфейса после изменения сообщения в смарт-контракте.

Прежде чем мы углубимся в наш прослушиватель, давайте посмотрим, что у нас есть на данный момент! Сохраните ваши файлы HelloWorld.js и interact.js, а затем перейдите по адресу http://localhost:3000/ (opens in a new tab)

Вы заметите, что текущее сообщение больше не гласит «Нет подключения к сети». Вместо этого оно отражает сообщение, хранящееся в смарт-контракте. Отлично!

Ваш пользовательский интерфейс теперь должен отражать сообщение, хранящееся в смарт-контракте

Теперь о прослушивателе...

Реализуйте addSmartContractListener

Если вы вернетесь к файлу HelloWorld.sol, который мы написали в Части 1 этой серии руководств (opens in a new tab), вы вспомните, что есть событие смарт-контракта под названием UpdatedMessages, которое генерируется после вызова функции update нашего смарт-контракта (см. строки 9 и 27):

1// HelloWorld.sol
2
3// Указывает версию Solidity, используя семантическое версионирование.
4// Узнайте больше: https://solidity.readthedocs.io/en/v0.5.10/layout-of-source-files.html#pragma
5pragma solidity ^0.7.3;
6
7// Определяет контракт с именем `HelloWorld`.
8// Контракт — это набор функций и данных (его состояние). После развертывания контракт находится по определенному адресу в блокчейне Ethereum. Узнайте больше: https://solidity.readthedocs.io/en/v0.5.10/structure-of-a-contract.html
9contract HelloWorld {
10
11 // Генерируется при вызове функции update
12 //События смарт-контракта — это способ сообщить из вашего контракта во фронтенд вашего приложения о том, что что-то произошло в блокчейне. Фронтенд может «прослушивать» определенные события и предпринимать действия, когда они происходят.
13 event UpdatedMessages(string oldStr, string newStr);
14
15 // Объявляет переменную состояния `message` типа `string`.
16 // Переменные состояния — это переменные, значения которых постоянно хранятся в хранилище контракта. Ключевое слово `public` делает переменные доступными извне контракта и создает функцию, которую другие контракты или клиенты могут вызывать для доступа к значению.
17 string public message;
18
19 // Подобно многим объектно-ориентированным языкам на основе классов, конструктор — это специальная функция, которая выполняется только при создании контракта.
20 // Конструкторы используются для инициализации данных контракта. Узнайте больше:https://solidity.readthedocs.io/en/v0.5.10/contracts.html#constructors
21 constructor(string memory initMessage) {
22
23 // Принимает строковый аргумент `initMessage` и устанавливает значение в переменную хранилища контракта `message`).
24 message = initMessage;
25 }
26
27 // Публичная функция, которая принимает строковый аргумент и обновляет переменную хранилища `message`.
28 function update(string memory newMessage) public {
29 string memory oldMsg = message;
30 message = newMessage;
31 emit UpdatedMessages(oldMsg, newMessage);
32 }
33}
Показать все

События смарт-контракта — это способ, которым ваш контракт сообщает вашему фронтенд-приложению о том, что что-то произошло (т. е. произошло событие) в блокчейне, которое может «прослушивать» определенные события и предпринимать действия, когда они происходят.

Функция addSmartContractListener будет специально прослушивать событие UpdatedMessages нашего смарт-контракта Hello World и обновлять наш пользовательский интерфейс для отображения нового сообщения.

Измените addSmartContractListener на следующее:

1// HelloWorld.js
2
3function addSmartContractListener() {
4 helloWorldContract.events.UpdatedMessages({}, (error, data) => {
5 if (error) {
6 setStatus("😥 " + error.message)
7 } else {
8 setMessage(data.returnValues[1])
9 setNewMessage("")
10 setStatus("🎉 Ваше сообщение было обновлено!")
11 }
12 })
13}
Показать все

Давайте разберем, что происходит, когда прослушиватель обнаруживает событие:

  • Если при генерации события произойдет ошибка, она будет отражена в пользовательском интерфейсе через нашу переменную состояния status.
  • В противном случае мы будем использовать возвращенный объект data. data.returnValues — это массив, индексированный с нуля, где первый элемент массива хранит предыдущее сообщение, а второй — обновленное. В целом, при успешном событии мы установим нашу строку message на обновленное сообщение, очистим строку newMessage и обновим нашу переменную состояния status, чтобы отразить, что новое сообщение было опубликовано в нашем смарт-контракте.

Наконец, давайте вызовем наш прослушиватель в нашей функции useEffect, чтобы он был инициализирован при первом рендеринге компонента HelloWorld.js. В целом, ваша функция useEffect должна выглядеть так:

1// HelloWorld.js
2
3useEffect(async () => {
4 const message = await loadCurrentMessage()
5 setMessage(message)
6 addSmartContractListener()
7}, [])

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

Итак, далее мы займемся настройкой нашего кошелька Ethereum (MetaMask), а затем подключим его к нашему децентрализованному приложению!

Шаг 4: Настройте свой кошелек Ethereum

Чтобы что-либо записать в блокчейн Ethereum, пользователи должны подписывать транзакции с помощью приватных ключей своего виртуального кошелька. В этом руководстве мы будем использовать MetaMask (opens in a new tab), виртуальный кошелек в браузере, используемый для управления адресом вашего аккаунта Ethereum, так как он делает подписание транзакций очень простым для конечного пользователя.

Если вы хотите больше узнать о том, как работают транзакции в Ethereum, ознакомьтесь с этой страницей от Ethereum Foundation.

Загрузите MetaMask

Вы можете бесплатно скачать и создать аккаунт MetaMask здесь (opens in a new tab). При создании аккаунта или если у вас уже есть аккаунт, убедитесь, что вы переключились на «тестовую сеть Goerli» в правом верхнем углу (чтобы мы не имели дело с реальными деньгами).

Добавьте эфир из крана

Чтобы подписать транзакцию в блокчейне Ethereum, нам понадобится немного тестового Eth. Чтобы получить Eth, вы можете перейти на FaucETH (opens in a new tab) и ввести адрес своего аккаунта Goerli, нажать «Запросить средства», затем выбрать «Ethereum Testnet Goerli» в выпадающем списке и, наконец, снова нажать кнопку «Запросить средства». Вскоре после этого вы должны увидеть Eth в своей учетной записи MetaMask!

Проверьте свой баланс

Чтобы дважды проверить наш баланс, давайте сделаем запрос eth_getBalance (opens in a new tab), используя инструмент для составления запросов от Alchemy (opens in a new tab). Так сумма Eth вернется в наш кошелек. После ввода адреса вашего аккаунта MetaMask и нажатия «Send Request» вы должны увидеть примерно такой ответ:

1{"jsonrpc": "2.0", "id": 0, "result": "0xde0b6b3a7640000"}

ПРИМЕЧАНИЕ: этот результат указан в wei, а не в ETH. Wei это наименьшая единица измерения эфира. Преобразование wei в eth: 1 eth = 10¹⁸ wei. Итак, если мы преобразуем 0xde0b6b3a7640000 в десятичное число, мы получим 1*10¹⁸, что равно 1 eth.

Фух! Наши ненастоящие деньги уже все там! 🤑

Шаг 5: Подключите MetaMask к вашему пользовательскому интерфейсу

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

Функция connectWallet

В нашем файле interact.js мы реализуем функцию connectWallet, которую затем сможем вызвать в нашем компоненте HelloWorld.js.

Давайте изменим connectWallet на следующее:

1// interact.js
2
3export const connectWallet = async () => {
4 if (window.ethereum) {
5 try {
6 const addressArray = await window.ethereum.request({
7 method: "eth_requestAccounts",
8 })
9 const obj = {
10 status: "👆🏽 Напишите сообщение в текстовом поле выше.",
11 address: addressArray[0],
12 }
13 return obj
14 } catch (err) {
15 return {
16 address: "",
17 status: "😥 " + err.message,
18 }
19 }
20 } else {
21 return {
22 address: "",
23 status: (
24 <span>
25 <p>
26 {" "}
27 🦊 <a target="_blank" href={`https://metamask.io/download`}>
28 Вы должны установить MetaMask, виртуальный кошелек Ethereum, в свой
29 браузер.
30 </a>
31 </p>
32 </span>
33 ),
34 }
35 }
36}
Показать все

Так что же именно делает этот гигантский блок кода?

Ну, во-первых, он проверяет, включен ли window.ethereum в вашем браузере.

window.ethereum — это глобальный API, внедряемый MetaMask и другими поставщиками кошельков, который позволяет веб-сайтам запрашивать аккаунты пользователей Ethereum. В случае одобрения он может считывать данные из блокчейнов, к которым подключен пользователь, и предлагать пользователю подписывать сообщения и транзакции. Для получения дополнительной информации см. документацию MetaMask (opens in a new tab)!

Если window.ethereum отсутствует, это означает, что MetaMask не установлен. В результате возвращается объект JSON, где возвращаемый address представляет собой пустую строку, а объект status JSX сообщает, что пользователь должен установить MetaMask.

Если же window.ethereum присутствует, то здесь начинается самое интересное.

Используя цикл try/catch, мы попытаемся подключиться к MetaMask, вызвав window.ethereum.request({ method: "eth_requestAccounts" }); (opens in a new tab). Вызов этой функции откроет MetaMask в браузере, и пользователю будет предложено подключить свой кошелек к вашему децентрализованному приложению.

  • Если пользователь решит подключиться, method: "eth_requestAccounts" вернет массив, содержащий все адреса аккаунтов пользователя, подключенных к децентрализованному приложению. В целом, наша функция connectWallet вернет объект JSON, который содержит первый address в этом массиве (см. строку 9) и сообщение status, предлагающее пользователю написать сообщение в смарт-контракт.
  • Если пользователь отклоняет подключение, то объект JSON будет содержать пустую строку для возвращаемого address и сообщение status, которое отражает, что пользователь отклонил подключение.

Теперь, когда мы написали эту функцию connectWallet, следующим шагом является ее вызов в нашем компоненте HelloWorld.js.

Добавьте функцию connectWallet в ваш компонент пользовательского интерфейса HelloWorld.js

Перейдите к функции connectWalletPressed в HelloWorld.js и обновите ее до следующего:

1// HelloWorld.js
2
3const connectWalletPressed = async () => {
4 const walletResponse = await connectWallet()
5 setStatus(walletResponse.status)
6 setWallet(walletResponse.address)
7}

Обратите внимание, как большая часть нашей функциональности абстрагирована от нашего компонента HelloWorld.js из файла interact.js? Это значит, что мы соответствуем парадигме M-V-С!

В connectWalletPressed мы просто делаем вызов await к нашей импортированной функции connectWallet и, используя ее ответ, обновляем наши переменные status и walletAddress через их хуки состояния.

Теперь давайте сохраним оба файла (HelloWorld.js и interact.js) и протестируем наш пользовательский интерфейс.

Откройте браузер на странице http://localhost:3000/ (opens in a new tab) и нажмите кнопку «Подключить кошелек» в правом верхнем углу страницы.

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

Вы должны увидеть, что кнопка кошелька теперь показывает, что ваш адрес подключен! Даааааа 🔥

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

Однако, не бойтесь! Мы легко можем решить эту проблему (поняли каламбур с адресом?). реализовав getCurrentWalletConnected, которая проверит, подключен ли уже адрес к нашему децентрализованному приложению, и соответствующим образом обновит наш пользовательский интерфейс!

Функция getCurrentWalletConnected

Обновите вашу функцию getCurrentWalletConnected в файле interact.js до следующего:

1// interact.js
2
3export const getCurrentWalletConnected = async () => {
4 if (window.ethereum) {
5 try {
6 const addressArray = await window.ethereum.request({
7 method: "eth_accounts",
8 })
9 if (addressArray.length > 0) {
10 return {
11 address: addressArray[0],
12 status: "👆🏽 Напишите сообщение в текстовом поле выше.",
13 }
14 } else {
15 return {
16 address: "",
17 status: "🦊 Подключитесь к MetaMask, используя кнопку в правом верхнем углу.",
18 }
19 }
20 } catch (err) {
21 return {
22 address: "",
23 status: "😥 " + err.message,
24 }
25 }
26 } else {
27 return {
28 address: "",
29 status: (
30 <span>
31 <p>
32 {" "}
33 🦊 <a target="_blank" href={`https://metamask.io/download`}>
34 Вы должны установить MetaMask, виртуальный кошелек Ethereum, в свой
35 браузер.
36 </a>
37 </p>
38 </span>
39 ),
40 }
41 }
42}
Показать все

Этот код очень похож на функцию connectWallet, которую мы только что написали в предыдущем шаге.

Основное отличие состоит в том, что вместо вызова метода eth_requestAccounts, который открывает MetaMask для подключения пользователя к кошельку, мы вызываем метод eth_accounts, который просто возвращает массив с адресами MetaMask, которые в данный момент подключены к нашему децентрализованному приложению.

Чтобы увидеть эту функцию в действии, давайте вызовем ее в нашей функции useEffect нашего компонента HelloWorld.js:

1// HelloWorld.js
2
3useEffect(async () => {
4 const message = await loadCurrentMessage()
5 setMessage(message)
6 addSmartContractListener()
7
8 const { address, status } = await getCurrentWalletConnected()
9 setWallet(address)
10 setStatus(status)
11}, [])
Показать все

Обратите внимание: мы используем ответ на наш вызов getCurrentWalletConnected для обновления наших переменных состояния walletAddress и status.

Теперь, когда вы добавили этот код, давайте попробуем обновить окно нашего браузера.

Отличнооооо! На кнопке должно быть указано, что вы подключены, и показан предварительный просмотр адреса вашего подключенного кошелька — даже после обновления!

Реализуйте addWalletListener

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

В вашем файле HelloWorld.js измените вашу функцию addWalletListener следующим образом:

1// HelloWorld.js
2
3function addWalletListener() {
4 if (window.ethereum) {
5 window.ethereum.on("accountsChanged", (accounts) => {
6 if (accounts.length > 0) {
7 setWallet(accounts[0])
8 setStatus("👆🏽 Напишите сообщение в текстовом поле выше.")
9 } else {
10 setWallet("")
11 setStatus("🦊 Подключитесь к MetaMask, используя кнопку в правом верхнем углу.")
12 }
13 })
14 } else {
15 setStatus(
16 <p>
17 {" "}
18 🦊 <a target="_blank" href={`https://metamask.io/download`}>
19 Вы должны установить MetaMask, виртуальный кошелек Ethereum, в свой браузер.
20 </a>
21 </p>
22 )
23 }
24}
Показать все

Спорим, вам даже не нужна наша помощь, чтобы понять, что здесь происходит, но для полноты картины давайте быстро разберем это:

  • Сначала наша функция проверяет, включен ли window.ethereum (т. е. установлен ли MetaMask).
    • Если нет, мы просто устанавливаем для нашей переменной состояния status строку JSX, которая предлагает пользователю установить MetaMask.
    • Если он включен, мы устанавливаем прослушиватель window.ethereum.on("accountsChanged") в строке 3, который прослушивает изменения состояния в кошельке MetaMask, включая подключение пользователем дополнительного аккаунта к децентрализованному приложению, переключение аккаунтов или отключение аккаунта. Если подключен хотя бы один аккаунт, переменная состояния walletAddress обновляется как первый аккаунт в массиве accounts, возвращаемом прослушивателем. В противном случае walletAddress устанавливается как пустая строка.

И последнее, но не менее важное: мы должны вызвать ее в нашей функции useEffect:

1// HelloWorld.js
2
3useEffect(async () => {
4 const message = await loadCurrentMessage()
5 setMessage(message)
6 addSmartContractListener()
7
8 const { address, status } = await getCurrentWalletConnected()
9 setWallet(address)
10 setStatus(status)
11
12 addWalletListener()
13}, [])
Показать все

Вот и все! Мы успешно завершили программирование всей функциональности нашего кошелька! Теперь перейдем к нашей последней задаче: обновлению сообщения, хранящегося в нашем смарт-контракте!

Шаг 6: Реализуйте функцию updateMessage

Итак, друзья, мы на финишной прямой! В updateMessage вашего файла interact.js мы сделаем следующее:

  1. Убедитесь, что сообщение, которое мы хотим опубликовать в нашем смарт-контакте, является действительным
  2. Подписать нашу транзакцию с помощью MetaMask
  3. Вызвать эту функцию из нашего фронтенд-компонента HelloWorld.js

Это не займет много времени; давайте завершим это децентрализованное приложение!

Обработка ошибок ввода

Естественно, имеет смысл иметь некоторую обработку ошибок ввода в начале функции.

Мы хотим, чтобы наша функция завершалась раньше, если не установлено расширение MetaMask, не подключен кошелек (т. е. переданный address является пустой строкой) или message является пустой строкой. Давайте добавим следующую обработку ошибок в updateMessage:

1// interact.js
2
3export const updateMessage = async (address, message) => {
4 if (!window.ethereum || address === null) {
5 return {
6 status:
7 "💡 Подключите свой кошелек MetaMask, чтобы обновить сообщение в блокчейне.",
8 }
9 }
10
11 if (message.trim() === "") {
12 return {
13 status: "❌ Ваше сообщение не может быть пустой строкой.",
14 }
15 }
16}
Показать все

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

Подписание нашей транзакции

Если вы уже знакомы с традиционными транзакциями Ethereum в web3, код, который мы напишем дальше, будет вам очень знаком. Ниже вашего кода обработки ошибок ввода добавьте следующее в updateMessage:

1// interact.js
2
3//настройка параметров транзакции
4const transactionParameters = {
5 to: contractAddress, // Обязательно, кроме случаев публикации контракта.
6 from: address, // должен совпадать с активным адресом пользователя.
7 data: helloWorldContract.methods.update(message).encodeABI(),
8}
9
10//подписание транзакции
11try {
12 const txHash = await window.ethereum.request({
13 method: "eth_sendTransaction",
14 params: [transactionParameters],
15 })
16 return {
17 status: (
18 <span>
19{" "}
20 <a target="_blank" href={`https://goerli.etherscan.io/tx/${txHash}`}>
21 Посмотрите статус вашей транзакции на Etherscan!
22 </a>
23 <br />
24 ℹ️ Как только транзакция будет проверена сетью, сообщение будет
25 обновлено автоматически.
26 </span>
27 ),
28 }
29} catch (error) {
30 return {
31 status: "😥 " + error.message,
32 }
33}
Показать все

Давайте разберемся, что происходит. Сначала мы настраиваем параметры наших транзакций, где:

  • to указывает адрес получателя (наш смарт-контракт)
  • from указывает подписанта транзакции, переменную address, которую мы передали в нашу функцию
  • data содержит вызов метода update нашего смарт-контракта Hello World, получая нашу строковую переменную message в качестве входных данных

Затем мы делаем вызов await, window.ethereum.request, где мы просим MetaMask подписать транзакцию. Обратите внимание, в строках 11 и 12 мы указываем наш метод eth, eth_sendTransaction, и передаем наши transactionParameters.

На этом этапе MetaMask откроется в браузере и предложит пользователю подписать или отклонить транзакцию.

  • Если транзакция будет успешной, функция вернет объект JSON, где строковая JSX status предлагает пользователю проверить Etherscan для получения дополнительной информации о своей транзакции.
  • Если транзакция не удастся, функция вернет объект JSON, где строковая status передает сообщение об ошибке.

В целом, наша функция updateMessage должна выглядеть так:

1// interact.js
2
3export const updateMessage = async (address, message) => {
4 //обработка ошибок ввода
5 if (!window.ethereum || address === null) {
6 return {
7 status:
8 "💡 Подключите свой кошелек MetaMask, чтобы обновить сообщение в блокчейне.",
9 }
10 }
11
12 if (message.trim() === "") {
13 return {
14 status: "❌ Ваше сообщение не может быть пустой строкой.",
15 }
16 }
17
18 //настройка параметров транзакции
19 const transactionParameters = {
20 to: contractAddress, // Обязательно, кроме случаев публикации контракта.
21 from: address, // должен совпадать с активным адресом пользователя.
22 data: helloWorldContract.methods.update(message).encodeABI(),
23 }
24
25 //подписание транзакции
26 try {
27 const txHash = await window.ethereum.request({
28 method: "eth_sendTransaction",
29 params: [transactionParameters],
30 })
31 return {
32 status: (
33 <span>
34{" "}
35 <a target="_blank" href={`https://goerli.etherscan.io/tx/${txHash}`}>
36 Посмотрите статус вашей транзакции на Etherscan!
37 </a>
38 <br />
39 ℹ️ Как только транзакция будет проверена сетью, сообщение будет
40 обновлено автоматически.
41 </span>
42 ),
43 }
44 } catch (error) {
45 return {
46 status: "😥 " + error.message,
47 }
48 }
49}
Показать все

И последнее, но не менее важное: нам нужно подключить нашу функцию updateMessage к нашему компоненту HelloWorld.js.

Подключите updateMessage к фронтенду HelloWorld.js

Наша функция onUpdatePressed должна сделать вызов await к импортированной функции updateMessage и изменить переменную состояния status, чтобы отразить, удалась ли наша транзакция или нет:

1// HelloWorld.js
2
3const onUpdatePressed = async () => {
4 const { status } = await updateMessage(walletAddress, newMessage)
5 setStatus(status)
6}

Это очень чисто и просто. И угадайте что... ВАШЕ ДЕЦЕНТРАЛИЗОВАННОЕ ПРИЛОЖЕНИЕ ГОТОВО!!!

Вперед, протестируйте кнопку Обновить!

Создайте свое собственное децентрализованное приложение

Ура, вы дошли до конца руководства! Подведем итоги, вы научились:

  • Подключить кошелек MetaMask к вашему проекту децентрализованного приложения
  • Считывать данные с вашего смарт-контракта с помощью API Alchemy Web3 (opens in a new tab)
  • Подписывать транзакции Ethereum с помощью MetaMask

Теперь вы полностью готовы применить навыки из этого руководства для создания своего собственного проекта децентрализованного приложения! Как всегда, если у вас есть какие-либо вопросы, не стесняйтесь обращаться к нам за помощью в Discord Alchemy (opens in a new tab). 🧙‍♂️

Как только вы завершите это руководство, дайте нам знать, как прошел ваш опыт или если у вас есть какие-либо отзывы, отметив нас в Twitter @alchemyplatform (opens in a new tab)!

Последнее обновление страницы: 26 февраля 2026 г.

Было ли это руководство полезным?