Смарт-контракт Hello World для начинающих — полный стек
Это руководство для вас, если вы новичок в разработке блокчейна и не знаете, с чего начать или как развертывать смарт-контракты и взаимодействовать с ними. Мы рассмотрим создание и развертывание простого смарт-контракта в тестовой сети 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» и напишите краткое описание. Выберите Staging в качестве среды и Goerli в качестве сети.
Примечание: обязательно выберите 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-world2cd hello-worldТеперь, когда мы находимся в папке нашего проекта, мы будем использовать npm init для его инициализации.
Если у вас еще не установлен npm, следуйте этим инструкциям по установке Node.js и npm (opens in a new tab).
Для целей этого руководства не имеет значения, как вы ответите на вопросы инициализации. Вот как мы это сделали для примера:
1имя пакета: (hello-world)2версия: (1.0.0)3описание: смарт-контракт hello world4точка входа: (index.js)5команда для тестирования:6репозиторий git:7ключевые слова:8автор:9лицензия: (ISC)1011Будет записано в /Users/.../.../.../hello-world/package.json:1213{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 8882888 888 888 888 8883888 888 888 888 88848888888888 8888b. 888d888 .d88888 88888b. 8888b. 8888885888 888 "88b 888P" d88" 888 888 "88b "88b 8886888 888 .d888888 888 888 888 888 888 .d888888 8887888 888 888 888 888 Y88b 888 888 888 888 888 Y88b.8888 888 "Y888888 888 "Y88888 888 888 "Y888888 "Y888910👷 Добро пожаловать в Hardhat v2.0.11 👷1112Что вы хотите сделать? …13Создать пример проекта14❯ Создать пустой hardhat.config.js15ВыйтиПоказать всеЭто создаст файл hardhat.config.js в проекте. Мы будем использовать его позже в этом руководстве, чтобы указать настройки для нашего проекта.
Шаг 9: Добавление папок проекта
Чтобы поддерживать порядок в проекте, давайте создадим две новые папки. В командной строке перейдите в корневой каталог вашего проекта hello-world и введите:
1mkdir contracts2mkdir scriptscontracts/— здесь мы будем хранить файл с кодом нашего смарт-контракта «hello world».scripts/— здесь мы будем хранить скрипты для развертывания нашего контракта и взаимодействия с ним.
Шаг 10: Написание нашего контракта
Вы можете спросить себя, когда мы собираемся писать код? Время пришло!
Откройте проект hello-world в своем любимом редакторе. Смарт-контракты чаще всего пишутся на Solidity, который мы и будем использовать для написания нашего смарт-контракта.
- Перейдите в папку
contractsи создайте новый файл с именемHelloWorld.sol - Ниже приведен пример смарт-контракта Hello World, который мы будем использовать в этом руководстве. Скопируйте содержимое ниже в файл
HelloWorld.sol.
Примечание: обязательно прочитайте комментарии, чтобы понять, что делает этот контракт.
1// Указывает версию Solidity, используя семантическое версионирование.2// Узнайте больше: https://solidity.readthedocs.io/en/v0.5.10/layout-of-source-files.html#pragma3pragma solidity >=0.7.3;45// Определяет контракт с именем `HelloWorld`.6// Контракт — это набор функций и данных (его состояние). После развертывания контракт находится по определенному адресу в блокчейне Ethereum. Узнайте больше: https://solidity.readthedocs.io/en/v0.5.10/structure-of-a-contract.html7contract HelloWorld {89 // Генерируется при вызове функции update10 //События смарт-контракта — это способ сообщить из вашего контракта во фронтенд вашего приложения о том, что что-то произошло в блокчейне. Фронтенд может «прослушивать» определенные события и предпринимать действия, когда они происходят.11 event UpdatedMessages(string oldStr, string newStr);1213 // Объявляет переменную состояния `message` типа `string`.14 // Переменные состояния — это переменные, значения которых постоянно хранятся в хранилище контракта. Ключевое слово `public` делает переменные доступными извне контракта и создает функцию, которую другие контракты или клиенты могут вызывать для доступа к значению.15 string public message;1617 // Подобно многим объектно-ориентированным языкам на основе классов, конструктор — это специальная функция, которая выполняется только при создании контракта.18 // Конструкторы используются для инициализации данных контракта. Узнайте больше:https://solidity.readthedocs.io/en/v0.5.10/contracts.html#constructors19 constructor(string memory initMessage) {2021 // Принимает строковый аргумент `initMessage` и устанавливает значение в переменную хранилища контракта `message`).22 message = initMessage;23 }2425 // Публичная функция, которая принимает строковый аргумент и обновляет переменную хранилища `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 или как-либо еще.
- Следуйте этим инструкциям (opens in a new tab), чтобы экспортировать свой приватный ключ.
- Ниже показано, как получить URL-адрес HTTP API Alchemy
Ваш .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').HardhatUserConfig3 */45require("dotenv").config()6require("@nomiclabs/hardhat-ethers")78const { API_URL, PRIVATE_KEY } = process.env910module.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")34 // Начинаем развертывание, возвращая promise, который разрешается в объект контракта5 const hello_world = await HelloWorld.deploy("Hello World!")6 console.log("Контракт развернут по адресу:", hello_world.address)7}89main()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.js23const API_KEY = process.env.API_KEY4const PRIVATE_KEY = process.env.PRIVATE_KEY5const CONTRACT_ADDRESS = process.env.CONTRACT_ADDRESSОбновите ваш файл .env
Мы будем использовать новые переменные среды, поэтому нам нужно определить их в файле .env, который мы создали ранее.
Нам нужно будет добавить определение для нашего API_KEY Alchemy и CONTRACT_ADDRESS, по которому был развернут ваш смарт-контракт.
Ваш файл .env должен выглядеть примерно так:
# .envAPI_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.js2const contract = require("../artifacts/contracts/HelloWorld.sol/HelloWorld.json")Если вы хотите увидеть ABI, вы можете вывести его в консоль:
1console.log(JSON.stringify(contract.abi))Чтобы увидеть ваш ABI, выведенный в консоль, перейдите в терминал и выполните:
npx hardhat run scripts/interact.jsСоздайте экземпляр вашего контракта
Для взаимодействия с нашим контрактом нам нужно создать экземпляр контракта в нашем коде. Чтобы сделать это с помощью Ethers.js, нам нужно будет работать с тремя концепциями:
- Provider (провайдер) — поставщик узлов, который дает вам доступ на чтение и запись в блокчейн.
- Signer (подписант) — представляет аккаунт Ethereum, который может подписывать транзакции.
- Contract (контракт) — объект Ethers.js, представляющий конкретный контракт, развернутый в сети.
Мы будем использовать ABI контракта из предыдущего шага, чтобы создать наш экземпляр контракта:
1// interact.js23// Провайдер4const alchemyProvider = new ethers.providers.AlchemyProvider(5 (network = "goerli"),6 API_KEY7)89// Подписант10const signer = new ethers.Wallet(PRIVATE_KEY, alchemyProvider)1112// Контракт13const helloWorldContract = new ethers.Contract(14 CONTRACT_ADDRESS,15 contract.abi,16 signer17)Показать всеУзнайте больше о провайдерах, подписантах и контрактах в документации ethers.js (opens in a new tab).
Прочитайте начальное сообщение
Помните, как мы развертывали наш контракт с initMessage = "Hello world!"? Теперь мы собираемся прочитать это сообщение, хранящееся в нашем смарт-контракте, и вывести его в консоль.
В JavaScript асинхронные функции используются при взаимодействии с сетями. Чтобы узнать больше об асинхронных функциях, прочитайте эту статью на Medium (opens in a new tab).
Используйте приведенный ниже код, чтобы вызвать функцию message в нашем смарт-контракте и прочитать начальное сообщение:
1// interact.js23// ...45async 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.js23// ...45async function main() {6 const message = await helloWorldContract.message()7 console.log("Сообщение: " + message)89 console.log("Обновление сообщения...")10 const tx = await helloWorldContract.update("Это новое сообщение.")11 await tx.wait()12}13main()Показать всеОбратите внимание, что в строке 11 мы вызываем .wait() для возвращенного объекта транзакции. Это гарантирует, что наш скрипт дождется майнинга транзакции в блокчейне перед выходом из функции. Если вызов .wait() не включен, скрипт может не увидеть обновленное значение message в контракте.
Прочитайте новое сообщение
Вы должны быть в состоянии повторить предыдущий шаг, чтобы прочитать обновленное значение message. Потратьте немного времени и посмотрите, сможете ли вы внести необходимые изменения, чтобы вывести это новое значение!
Если вам нужна подсказка, вот как должен выглядеть ваш файл interact.js на данном этапе:
1// interact.js23const API_KEY = process.env.API_KEY4const PRIVATE_KEY = process.env.PRIVATE_KEY5const CONTRACT_ADDRESS = process.env.CONTRACT_ADDRESS67const contract = require("../artifacts/contracts/HelloWorld.sol/HelloWorld.json")89// провайдер - Alchemy10const alchemyProvider = new ethers.providers.AlchemyProvider(11 (network = "goerli"),12 API_KEY13)1415// подписант - вы16const signer = new ethers.Wallet(PRIVATE_KEY, alchemyProvider)1718// экземпляр контракта19const helloWorldContract = new ethers.Contract(20 CONTRACT_ADDRESS,21 contract.abi,22 signer23)2425async function main() {26 const message = await helloWorldContract.message()27 console.log("Сообщение: " + message)2829 console.log("Обновление сообщения...")30 const tx = await helloWorldContract.update("это новое сообщение")31 await tx.wait()3233 const newMessage = await helloWorldContract.message()34 console.log("Новое сообщение: " + newMessage)35}3637main()Показать всеТеперь просто запустите скрипт, и вы сможете увидеть старое сообщение, статус обновления и новое сообщение, выведенные в ваш терминал!
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.js23require("dotenv").config()4require("@nomiclabs/hardhat-ethers")5require("@nomiclabs/hardhat-etherscan")67const { API_URL, PRIVATE_KEY, ETHERSCAN_API_KEY } = process.env89module.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 для Etherscan21 // Получите его на 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-address3для проверки на Etherscan. Ожидание результата проверки...456Контракт 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-filesnpm 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.js23import React from "react"4import { useEffect, useState } from "react"5import {6 helloWorldContract,7 connectWallet,8 updateMessage,9 loadCurrentMessage,10 getCurrentWalletConnected,11} from "./util/interact.js"1213import alchemylogo from "./alchemylogo.svg"Показать всеДалее у нас есть переменные состояния, которые мы будем обновлять после определенных событий.
1// HelloWorld.js23//Переменные состояния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.js23//вызывается только один раз4useEffect(async () => {5 //TODO: реализовать6}, [])78function addSmartContractListener() {9 //TODO: реализовать10}1112function addWalletListener() {13 //TODO: реализовать14}1516const connectWalletPressed = async () => {17 //TODO: реализовать18}1920const onUpdatePressed = async () => {21 //TODO: реализовать22}Показать всеuseEffect(opens in a new tab) — это хук React, который вызывается после рендеринга вашего компонента. Поскольку в него передается пустой массив[]в качестве свойства (см. строку 4), он будет вызываться только при первом рендеринге компонента. Здесь мы загрузим текущее сообщение, хранящееся в нашем смарт-контракте, вызовем наши прослушиватели смарт-контракта и кошелька и обновим наш пользовательский интерфейс, чтобы отразить, подключен ли уже кошелек.addSmartContractListener— эта функция настраивает прослушиватель, который будет отслеживать событиеUpdatedMessagesнашего контракта HelloWorld и обновлять наш пользовательский интерфейс при изменении сообщения в нашем смарт-контракте.addWalletListener— эта функция настраивает прослушиватель, который обнаруживает изменения в состоянии кошелька MetaMask пользователя, например, когда пользователь отключает свой кошелек или переключает адреса.connectWalletPressed— эта функция будет вызываться для подключения кошелька MetaMask пользователя к нашему децентрализованному приложению.onUpdatePressed— эта функция будет вызываться, когда пользователь захочет обновить сообщение, хранящееся в смарт-контракте.
Ближе к концу этого файла находится пользовательский интерфейс нашего компонента.
1// HelloWorld.js23//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>1718 <h2 style={{ paddingTop: "50px" }}>Текущее сообщение:</h2>19 <p>{message}</p>2021 <h2 style={{ paddingTop: "18px" }}>Новое сообщение:</h2>2223 <div>24 <input25 type="text"26 placeholder="Обновите сообщение в вашем смарт-контракте."27 onChange={(e) => setNewMessage(e.target.value)}28 value={newMessage}29 />30 <p id="status">{status}</p>3132 <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.js23//export const helloWorldContract;45export const loadCurrentMessage = async () => {}67export const connectWallet = async () => {}89const getCurrentWalletConnected = async () => {}1011export 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-web3Alchemy 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.js23require("dotenv").config()4const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY5const { createAlchemyWeb3 } = require("@alch/alchemy-web3")6const web3 = createAlchemyWeb3(alchemyKey)78//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.js23const contractABI = require("../contract-abi.json")4const contractAddress = "0x6f3f635A9762B47954229Ea479b4541eAF402A6A"Теперь мы наконец-то можем раскомментировать нашу переменную helloWorldContract и загрузить смарт-контракт с помощью нашей конечной точки AlchemyWeb3:
1// interact.js2export const helloWorldContract = new web3.eth.Contract(3 contractABI,4 contractAddress5)Подводя итог, первые 12 строк вашего interact.js теперь должны выглядеть так:
1// interact.js23require("dotenv").config()4const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY5const { createAlchemyWeb3 } = require("@alch/alchemy-web3")6const web3 = createAlchemyWeb3(alchemyKey)78const contractABI = require("../contract-abi.json")9const contractAddress = "0x6f3f635A9762B47954229Ea479b4541eAF402A6A"1011export const helloWorldContract = new web3.eth.Contract(12 contractABI,13 contractAddress14)Показать всеТеперь, когда мы загрузили наш контракт, мы можем реализовать нашу функцию loadCurrentMessage!
Реализация loadCurrentMessage в вашем файле interact.js
Эта функция очень проста. Мы сделаем простой асинхронный вызов web3 для чтения из нашего контракта. Наша функция вернет сообщение, хранящееся в смарт-контракте:
Обновите loadCurrentMessage в вашем файле interact.js до следующего:
1// interact.js23export const loadCurrentMessage = async () => {4 const message = await helloWorldContract.methods.message().call()5 return message6}Поскольку мы хотим отображать этот смарт-контракт в нашем пользовательском интерфейсе, давайте обновим функцию useEffect в нашем компоненте HelloWorld.js до следующего:
1// HelloWorld.js23//вызывается только один раз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.sol23// Указывает версию Solidity, используя семантическое версионирование.4// Узнайте больше: https://solidity.readthedocs.io/en/v0.5.10/layout-of-source-files.html#pragma5pragma solidity ^0.7.3;67// Определяет контракт с именем `HelloWorld`.8// Контракт — это набор функций и данных (его состояние). После развертывания контракт находится по определенному адресу в блокчейне Ethereum. Узнайте больше: https://solidity.readthedocs.io/en/v0.5.10/structure-of-a-contract.html9contract HelloWorld {1011 // Генерируется при вызове функции update12 //События смарт-контракта — это способ сообщить из вашего контракта во фронтенд вашего приложения о том, что что-то произошло в блокчейне. Фронтенд может «прослушивать» определенные события и предпринимать действия, когда они происходят.13 event UpdatedMessages(string oldStr, string newStr);1415 // Объявляет переменную состояния `message` типа `string`.16 // Переменные состояния — это переменные, значения которых постоянно хранятся в хранилище контракта. Ключевое слово `public` делает переменные доступными извне контракта и создает функцию, которую другие контракты или клиенты могут вызывать для доступа к значению.17 string public message;1819 // Подобно многим объектно-ориентированным языкам на основе классов, конструктор — это специальная функция, которая выполняется только при создании контракта.20 // Конструкторы используются для инициализации данных контракта. Узнайте больше:https://solidity.readthedocs.io/en/v0.5.10/contracts.html#constructors21 constructor(string memory initMessage) {2223 // Принимает строковый аргумент `initMessage` и устанавливает значение в переменную хранилища контракта `message`).24 message = initMessage;25 }2627 // Публичная функция, которая принимает строковый аргумент и обновляет переменную хранилища `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.js23function 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.js23useEffect(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.js23export 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 obj14 } 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.js23const 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.js23export 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.js23useEffect(async () => {4 const message = await loadCurrentMessage()5 setMessage(message)6 addSmartContractListener()78 const { address, status } = await getCurrentWalletConnected()9 setWallet(address)10 setStatus(status)11}, [])Показать всеОбратите внимание: мы используем ответ на наш вызов getCurrentWalletConnected для обновления наших переменных состояния walletAddress и status.
Теперь, когда вы добавили этот код, давайте попробуем обновить окно нашего браузера.
Отличнооооо! На кнопке должно быть указано, что вы подключены, и показан предварительный просмотр адреса вашего подключенного кошелька — даже после обновления!
Реализуйте addWalletListener
Последним шагом в настройке нашего кошелька в децентрализированном приложении является реализация прослушивателя кошелька, чтобы наш пользовательский интерфейс обновлялся при изменении состояния нашего кошелька, например, когда пользователь отключает или переключает учетные записи.
В вашем файле HelloWorld.js измените вашу функцию addWalletListener следующим образом:
1// HelloWorld.js23function 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.js23useEffect(async () => {4 const message = await loadCurrentMessage()5 setMessage(message)6 addSmartContractListener()78 const { address, status } = await getCurrentWalletConnected()9 setWallet(address)10 setStatus(status)1112 addWalletListener()13}, [])Показать всеВот и все! Мы успешно завершили программирование всей функциональности нашего кошелька! Теперь перейдем к нашей последней задаче: обновлению сообщения, хранящегося в нашем смарт-контракте!
Шаг 6: Реализуйте функцию updateMessage
Итак, друзья, мы на финишной прямой! В updateMessage вашего файла interact.js мы сделаем следующее:
- Убедитесь, что сообщение, которое мы хотим опубликовать в нашем смарт-контакте, является действительным
- Подписать нашу транзакцию с помощью MetaMask
- Вызвать эту функцию из нашего фронтенд-компонента
HelloWorld.js
Это не займет много времени; давайте завершим это децентрализованное приложение!
Обработка ошибок ввода
Естественно, имеет смысл иметь некоторую обработку ошибок ввода в начале функции.
Мы хотим, чтобы наша функция завершалась раньше, если не установлено расширение MetaMask, не подключен кошелек (т. е. переданный address является пустой строкой) или message является пустой строкой. Давайте добавим следующую обработку ошибок в updateMessage:
1// interact.js23export const updateMessage = async (address, message) => {4 if (!window.ethereum || address === null) {5 return {6 status:7 "💡 Подключите свой кошелек MetaMask, чтобы обновить сообщение в блокчейне.",8 }9 }1011 if (message.trim() === "") {12 return {13 status: "❌ Ваше сообщение не может быть пустой строкой.",14 }15 }16}Показать всеТеперь, когда у нас есть правильная обработка ошибок ввода, пришло время подписать транзакцию через MetaMask!
Подписание нашей транзакции
Если вы уже знакомы с традиционными транзакциями Ethereum в web3, код, который мы напишем дальше, будет вам очень знаком. Ниже вашего кода обработки ошибок ввода добавьте следующее в updateMessage:
1// interact.js23//настройка параметров транзакции4const transactionParameters = {5 to: contractAddress, // Обязательно, кроме случаев публикации контракта.6 from: address, // должен совпадать с активным адресом пользователя.7 data: helloWorldContract.methods.update(message).encodeABI(),8}910//подписание транзакции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.js23export const updateMessage = async (address, message) => {4 //обработка ошибок ввода5 if (!window.ethereum || address === null) {6 return {7 status:8 "💡 Подключите свой кошелек MetaMask, чтобы обновить сообщение в блокчейне.",9 }10 }1112 if (message.trim() === "") {13 return {14 status: "❌ Ваше сообщение не может быть пустой строкой.",15 }16 }1718 //настройка параметров транзакции19 const transactionParameters = {20 to: contractAddress, // Обязательно, кроме случаев публикации контракта.21 from: address, // должен совпадать с активным адресом пользователя.22 data: helloWorldContract.methods.update(message).encodeABI(),23 }2425 //подписание транзакции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.js23const 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 г.





