Перейти до основного контенту

Смарт-контракт Hello World для початківців — повний стек

Solidity
Hardhat
Alchemy
Смарт-контракти
розгортання
провідник блоків
використання
транзакції
Початківець
nstrike2
25 жовтня 2021 р.
40 читається за хвилину

Цей посібник для вас, якщо ви новачок у розробці блокчейнів і не знаєте, з чого почати або як розгортати смарт-контракти та взаємодіяти з ними. Ми пройдемо процес створення та розгортання простого смарт-контракту в тестовій мережі 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 знайдіть спадний список Apps на панелі навігації та натисніть Create App.

Створення застосунку Hello World

Дайте своєму застосунку назву «Hello World» і напишіть короткий опис. Виберіть Staging як своє середовище та Goerli як мережу.

Створення застосунку Hello World

Примітка: обов’язково виберіть Goerli, інакше цей посібник не працюватиме.

Натисніть Create app. Ваш застосунок з’явиться в таблиці нижче.

Створення облікового запису Ethereum

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

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

Крок 4. Додайте ефір (ether) із крана (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 і натисніть Send Request. Ви побачите відповідь, схожу на фрагмент коду нижче.

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 \"Помилка: тест не вказано\" && exit 1"
20 },
21 "author": "",
22 "license": "ISC"
23}
Показати все

Підтвердіть package.json, і можемо продовжувати!

Крок 7. Завантаження Hardhat

Hardhat є середовищем розробки для компіляції, розгортання, тестування та налагодження вашого програмного забезпечення Ethereum. Це допомагає розробникам створювати смарт-контракти та dapp локально перед розгортанням у реальний ланцюжок.

Усередині нашого проєкту hello-world запустіть:

1npm install --save-dev hardhat

Перегляньте цю сторінку, щоб дізнатися більше про інструкції з установки (opens in a new tab).

Крок 8. Створення проєкту Hardhat

У папці нашого проєкту hello-world запустіть:

1npx hardhat

Потім ви маєте побачити вітальне повідомлення та вибір подальших бажаних дій. Оберіть "Створити порожній 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 // Викликається, коли викликається функція оновлення
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 // Почати розгортання, повертаючи проміс, який перетворюється на об'єкт контракту
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.

Щоб зрозуміти, що відбувається «під капотом», перейдімо на вкладку «Провідник» на нашій інформаційній панелі 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, який ми створили раніше.

Нам потрібно буде додати визначення для нашого Alchemy API_KEY та 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Нове повідомлення: Це нове повідомлення.

Під час виконання цього скрипта ви можете помітити, що крок Оновлення повідомлення... займає деякий час, перш ніж завантажиться нове повідомлення. Це пов'язано з процесом майнінгу; якщо вам цікаво відстежувати транзакції під час їхнього майнінгу, відвідайте мемпул Alchemy (opens in a new tab), щоб побачити статус транзакції. Якщо транзакцію відхилено, корисно також перевірити Goerli Etherscan (opens in a new tab) і знайти хеш вашої транзакції.

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

Ви виконали всю важку роботу, щоб втілити в життя свій смарт-контракт; тепер настав час поділитися ним зі світом!

Підтвердивши свій смарт-контракт на Etherscan, будь-хто зможе переглянути ваш вихідний код і взаємодіяти з вашим смарт-контрактом. Почнімо!

Крок 1: Створіть ключ API у своєму акаунті Etherscan

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

Якщо у вас ще немає акаунта Etherscan, зареєструйтеся (opens in a new tab).

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

На сторінці вашого профілю ви повинні побачити бічну панель навігації. На бічній панелі навігації виберіть API Keys. Далі натисніть кнопку «Додати», щоб створити новий ключ API, назвіть свій застосунок hello-world і натисніть кнопку Створити новий ключ API.

Ваш новий ключ 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 DEPLOYED_CONTRACT_ADDRESS '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 до вашого dapp
  • Читання даних з вашого смарт-контракту за допомогою Alchemy Web3 (opens in a new tab) API
  • Підписувати транзакції Ethereum за допомогою MetaMask

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

Як попередня умова, ви повинні мати початкове розуміння React. Якщо ні, ми рекомендуємо пройти офіційний Вступ до React (opens in a new tab).

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

Спочатку перейдіть до репозиторію hello-world-part-four на GitHub (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- рядок, який зберігає корисне повідомлення, що направляє користувача, як взаємодіяти з dapp
  • 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 користувача до нашого dapp.
  • onUpdatePressed - ця функція буде викликана, коли користувач захоче оновити повідомлення, що зберігається в смарт-контракті.

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

1// HelloWorld.js
2
3//інтерфейс нашого компонента
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 користувача в кнопці з ID "walletButton;" в іншому випадку вона просто говорить "Підключити гаманець".
  • У рядку 17 ми відображаємо поточне повідомлення, що зберігається в смарт-контракті, яке міститься в рядку message.
  • У рядках 23-26 ми використовуємо контрольований компонент (opens in a new tab) для оновлення нашої змінної стану newMessage, коли змінюється ввід у текстовому полі.

Крім наших змінних стану, ви також побачите, що функції connectWalletPressed та onUpdatePressed викликаються при натисканні кнопок з ID publishButton та walletButton відповідно.

Нарешті, давайте розберемося, куди додається цей компонент HelloWorld.js.

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

І останнє, але не менш важливе, давайте розглянемо ще один наданий вам файл, interact.js.

Файл interact.js

Оскільки ми хочемо дотримуватися парадигми M-V-C (opens in a new tab), нам знадобиться окремий файл, який містить усі наші функції для керування логікою, даними та правилами нашого dapp, а потім мати змогу експортувати ці функції до нашого фронтенду (наш компонент 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 користувача до нашого dapp.
  • getCurrentWalletConnected - ця функція перевірить, чи обліковий запис Ethereum вже підключено до нашого dapp під час завантаження сторінки, і відповідно оновить наш інтерфейс.
  • updateMessage - ця функція оновить повідомлення, що зберігається в смарт-контракті. Вона зробить виклик запису до смарт-контракту Hello World, тому гаманець MetaMask користувача повинен буде підписати транзакцію Ethereum, щоб оновити повідомлення.

Тепер, коли ми розуміємо, з чим маємо справу, давайте з'ясуємо, як читати з нашого смарт-контракту!

Крок 3: Читання з вашого смарт-контракту

Щоб читати з вашого смарт-контракту, вам потрібно успішно налаштувати:

  • API-з'єднання з ланцюгом Ethereum
  • Завантажений екземпляр вашого смарт-контракту
  • Функція для виклику функції вашого смарт-контракту
  • Слухач для відстеження оновлень, коли дані, які ви читаєте зі смарт-контракту, змінюються

Це може здатися великою кількістю кроків, але не хвилюйтеся! Ми проведемо вас через кожен з них крок за кроком! :)

Встановлення API-з'єднання з ланцюгом Ethereum

Отже, пам'ятаєте, як у частині 2 цього посібника ми використовували наш ключ Alchemy Web3 для читання з нашого смарт-контракту (opens in a new tab)? Вам також знадобиться ключ Alchemy Web3 у вашому dapp для читання з ланцюга.

Якщо у вас його ще немає, спочатку встановіть 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 та інші важливі переваги, щоб полегшити ваше життя як розробника web3. Він розроблений так, щоб вимагати мінімальної конфігурації, щоб ви могли одразу почати використовувати його у своєму додатку!

Потім встановіть пакет dotenv (opens in a new tab) у вашому каталозі проєкту, щоб у нас було безпечне місце для зберігання нашого ключа API після того, як ми його отримаємо.

1npm install dotenv --save

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

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

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

Тепер ми готові налаштувати нашу кінцеву точку Alchemy Web3 у нашому dapp! Давайте повернемося до нашого файлу 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, ми можемо використовувати метод контракту (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 // Викликається, коли викликається функція оновлення
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}, [])

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

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

Крок 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 і натисніть кнопку "Відправити запит", ви повинні побачити таку відповідь:

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

ПРИМІТКА: Цей результат у wei, а не в eth. Wei використовується в якості найменшого номіналу ether. Перетворення з wei в eth: 1 eth = 10¹⁸ wei. Отже, якщо ми перетворимо 0xde0b6b3a7640000 у десяткове число, ми отримаємо 1*10¹⁸, що дорівнює 1 eth.

Фух! Наші підроблені гроші усі там! 🤑

Крок 5: Підключіть MetaMask до вашого інтерфейсу

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

Функція 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 є порожнім рядком, а об'єкт JSX status повідомляє, що користувач повинен встановити MetaMask.

Тепер, якщо window.ethereum присутній, ось тут і починається найцікавіше.

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

  • Якщо користувач вирішить підключитися, method: "eth_requestAccounts" поверне масив, який містить усі адреси облікових записів користувача, підключені до dapp. Загалом, наша функція 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-C!

У connectWalletPressed ми просто робимо await-виклик нашої імпортованої функції connectWallet і, використовуючи її відповідь, оновлюємо наші змінні status та walletAddress за допомогою їхніх хуків стану.

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

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

Якщо у вас встановлено MetaMask, вам буде запропоновано підключити свій гаманець до вашого dapp. Прийміть запрошення на підключення.

Ви повинні побачити, що кнопка гаманця тепер відображає, що ваша адреса підключена! Такссс 🔥

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

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

Функція 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, які зараз підключені до нашого dapp.

Щоб побачити цю функцію в дії, давайте викличемо її в нашій функції 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

Останній крок у налаштуванні гаманця нашого dapp — це реалізація прослуховувача гаманця, щоб наш інтерфейс користувача оновлювався при зміні стану нашого гаманця, наприклад, коли користувач відключається або перемикає облікові записи.

У вашому файлі 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, зокрема, коли користувач підключає додатковий обліковий запис до dapp, перемикає облікові записи або відключає обліковий запис. Якщо підключено хоча б один обліковий запис, змінна стану 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

Це не займе багато часу; давайте завершимо цей dapp!

Обробка помилок введення

Природно, має сенс мати якусь обробку помилок введення на початку функції.

Ми захочемо, щоб наша функція завершувалася раніше, якщо не встановлено розширення 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!

Підписання нашої транзакції

Якщо ви вже знайомі з традиційними транзакціями web3 Ethereum, код, який ми напишемо далі, буде вам дуже знайомий. Нижче вашого коду обробки помилок введення додайте наступне до 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}

Це надзвичайно чисто та просто. І вгадайте що... ВАШ DAPP ГОТОВИЙ!!!

Спробуйте натиснути кнопку Оновити!

Створіть свій власний dapp

Вуууу, ви дійшли до кінця посібника! Підсумовуючи, ви навчилися:

  • Підключати гаманець MetaMask до вашого dapp проєкту
  • Читання даних з вашого смарт-контракту за допомогою Alchemy Web3 (opens in a new tab) API
  • Підписувати транзакції Ethereum за допомогою MetaMask

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

Після завершення цього посібника, повідомте нам про свій досвід або якщо у вас є відгуки, позначивши нас у Twitter @alchemyplatform (opens in a new tab)!

Останні оновлення сторінки: 26 лютого 2026 р.

Чи була ця інструкція корисною?