Inteligentny kontrakt „Witaj świecie” dla początkujących – Fullstack
Ten przewodnik jest dla Ciebie, jeśli jesteś nowy w tworzeniu blockchaina i nie wiesz, od czego zacząć lub jak wdrażać i wchodzić w interakcję z inteligentnymi kontraktami. Przeprowadzimy Cię przez proces tworzenia i wdrażania prostego inteligentnego kontraktu w sieci testowej Goerli przy użyciu MetaMask (opens in a new tab), Solidity (opens in a new tab), Hardhat (opens in a new tab) i Alchemy (opens in a new tab).
Do ukończenia tego samouczka potrzebne będzie konto Alchemy. Zarejestruj się, aby założyć darmowe konto (opens in a new tab).
Jeśli masz jakieś pytania, skontaktuj się z nami na Discordzie Alchemy (opens in a new tab)!
Część 1 – Utwórz i wdróż swój inteligentny kontrakt za pomocą Hardhat
Połącz się z siecią Ethereum
Istnieje wiele sposobów na wysyłanie żądań do łańcucha Ethereum. Dla uproszczenia, użyjemy darmowego konta na Alchemy, platformie deweloperskiej i interfejsie API blockchaina, które pozwalają nam komunikować się z łańcuchem Ethereum bez konieczności samodzielnego uruchamiania węzła. Alchemy posiada również narzędzia deweloperskie do monitorowania i analityki. Skorzystamy z nich w tym samouczku, aby zrozumieć, co dzieje się pod maską podczas wdrażania naszego inteligentnego kontraktu.
Utwórz swoją aplikację i klucz API
Po utworzeniu konta Alchemy, można wygenerować klucz API, tworząc aplikację. Umożliwi to wysyłanie żądań do sieci testowej Goerli. Jeśli nie znasz sieci testowych, możesz przeczytać przewodnik Alchemy dotyczący wyboru sieci (opens in a new tab).
Na pulpicie nawigacyjnym Alchemy znajdź menu rozwijane Aplikacje w pasku nawigacyjnym i kliknij Utwórz aplikację.
Nadaj swojej aplikacji nazwę „Hello World” i wpisz krótki opis. Wybierz Staging jako środowisko i Goerli jako sieć.
Uwaga: upewnij się, że wybrałeś Goerli, w przeciwnym razie ten samouczek nie zadziała.
Kliknij Utwórz aplikację. Twoja aplikacja pojawi się w poniższej tabeli.
Utwórz konto Ethereum
Aby wysyłać i odbierać transakcje, potrzebujesz konta Ethereum. Użyjemy MetaMask, wirtualnego portfela w przeglądarce, który pozwala użytkownikom zarządzać adresem konta Ethereum.
Możesz pobrać i utworzyć konto MetaMask za darmo tutaj (opens in a new tab). Podczas tworzenia konta, lub jeśli już je posiadasz, upewnij się, że przełączyłeś się na „Sieć testową Goerli” w prawym górnym rogu (aby nie mieć do czynienia z prawdziwymi pieniędzmi).
Krok 4: Dodaj ether z Faucet
Aby wdrożyć swój inteligentny kontrakt w sieci testowej, będziesz potrzebować trochę fałszywych ETH. Aby uzyskać ETH w sieci Goerli, przejdź do Faucet Goerli i wprowadź adres swojego konta Goerli. Należy pamiętać, że krany Goerli mogą być ostatnio nieco zawodne - sprawdź stronę sieci testowych, aby uzyskać listę opcji do wypróbowania:
Uwaga: ze względu na przeciążenie sieci może to trochę potrwać.
Krok 5: Sprawdź swoje saldo
Aby dwukrotnie sprawdzić, czy ETH znajduje się w Twoim portfelu, wykonaj żądanie eth_getBalance (opens in a new tab) za pomocą narzędzia kompozytora Alchemy (opens in a new tab). Zwróci to ilość ETH w naszym portfelu. Aby dowiedzieć się więcej, sprawdź krótki samouczek Alchemy na temat korzystania z narzędzia kompozytora (opens in a new tab).
Wprowadź swój adres konta MetaMask i kliknij Wyślij żądanie. Zobaczysz odpowiedź, która wygląda jak poniższy fragment kodu.
1{ "jsonrpc": "2.0", "id": 0, "result": "0x2B5E3AF16B1880000" }Uwaga: Ten wynik jest w wei, nie w ETH. Wei jest używane jako najmniejsza jednostka etheru.
Uff! Wszystkie nasze fałszywe pieniądze są na miejscu.
Krok 6: Zainicjuj nasz projekt
Najpierw musimy utworzyć folder dla naszego projektu. Przejdź do wiersza poleceń i wprowadź następujące informacje.
1mkdir hello-world2cd hello-worldTeraz, gdy jesteśmy w folderze naszego projektu, użyjemy npm init, aby zainicjować projekt.
Jeśli nie masz jeszcze zainstalowanego npm, postępuj zgodnie z tymi instrukcjami, aby zainstalować Node.js i npm (opens in a new tab).
Dla celów tego samouczka nie ma znaczenia, jak odpowiesz na pytania inicjujące. Oto, jak zrobiliśmy to dla odniesienia:
1package name: (hello-world)2version: (1.0.0)3description: hello world smart contract4entry point: (index.js)5test command:6git repository:7keywords:8author:9license: (ISC)1011About to write to /Users/.../.../.../hello-world/package.json:1213{14 "name": "hello-world",15 "version": "1.0.0",16 "description": "hello world smart contract",17 "main": "index.js",18 "scripts": {19 "test": "echo \"Error: no test specified\" && exit 1"20 },21 "author": "",22 "license": "ISC"23}Pokaż wszystkoZatwierdź plik package.json i gotowe!
Krok 7: Pobierz Hardhat
Hardhat to środowisko programistyczne do kompilacji, wdrażania, testowania i debugowania oprogramowania Ethereum. Pomaga deweloperom w tworzeniu inteligentnych kontraktów i dapek lokalnie przed wdrożeniem ich na żywym łańcuchu.
W naszym projekcie hello-world uruchom:
1npm install --save-dev hardhatSprawdź tę stronę, aby uzyskać więcej szczegółów na temat instrukcji instalacji (opens in a new tab).
Krok 8: Utwórz projekt Hardhat
W naszym folderze projektu hello-world uruchom:
1npx hardhatPowinieneś wtedy zobaczyć wiadomość powitalną i opcję wyboru tego, co chcesz zrobić. Wybierz „utwórz pusty 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👷 Witamy w Hardhat v2.0.11 👷1112Co chcesz zrobić? …13Utwórz przykładowy projekt14❯ Utwórz pusty plik hardhat.config.js15ZamknijPokaż wszystkoSpowoduje to wygenerowanie pliku hardhat.config.js w projekcie. Użyjemy tego później w samouczku, aby określić konfigurację naszego projektu.
Krok 9: Dodaj foldery projektu
Aby utrzymać porządek w projekcie, utwórzmy dwa nowe foldery. W wierszu poleceń przejdź do katalogu głównego projektu hello-world i wpisz:
1mkdir contracts2mkdir scripts- w
contracts/będziemy przechowywać plik z kodem naszego inteligentnego kontraktu hello world - w
scripts/będziemy przechowywać skrypty do wdrażania naszego kontraktu i interakcji z nim
Krok 10: Napisz nasz kontrakt
Możesz zadać sobie pytanie, kiedy napiszemy kod? Nadszedł czas!
Otwórz projekt hello-world w swoim ulubionym edytorze. Inteligentne kontrakty najczęściej pisane są w Solidity, którego użyjemy do napisania naszego inteligentnego kontraktu.
- Przejdź do folderu
contractsi utwórz nowy plik o nazwieHelloWorld.sol - Poniżej znajduje się przykładowy inteligentny kontrakt Witaj Świecie, którego będziemy używać w tym samouczku. Skopiuj poniższą zawartość do pliku
HelloWorld.sol.
Uwaga: Pamiętaj, aby przeczytać komentarze, aby zrozumieć, co robi ten kontrakt.
1// Określa wersję Solidity, używając semantycznego wersjonowania.2// Dowiedz się więcej: https://solidity.readthedocs.io/en/v0.5.10/layout-of-source-files.html#pragma3pragma solidity >=0.7.3;45// Definiuje kontrakt o nazwie `HelloWorld`.6// Kontrakt jest zbiorem funkcji i danych (jego stanu). Po wdrożeniu kontrakt znajduje się pod określonym adresem w blockchainie Ethereum. Dowiedz się więcej: https://solidity.readthedocs.io/en/v0.5.10/structure-of-a-contract.html7contract HelloWorld {89 // Emitowane, gdy wywoływana jest funkcja aktualizacji10 // Zdarzenia inteligentnych kontraktów to sposób, w jaki kontrakt komunikuje, że coś wydarzyło się na blockchainie do front-endu aplikacji, który może „nasłuchiwać” określonych zdarzeń i podejmować działania, gdy one wystąpią.11 event UpdatedMessages(string oldStr, string newStr);1213 // Deklaruje zmienną stanu `message` typu `string`.14 // Zmienne stanu to zmienne, których wartości są trwale przechowywane w pamięci kontraktu. Słowo kluczowe `public` udostępnia zmienne spoza kontraktu i tworzy funkcję, którą inne kontrakty lub klienci mogą wywołać w celu uzyskania dostępu do wartości.15 string public message;1617 // Podobnie jak w wielu językach obiektowych opartych na klasach, konstruktor jest specjalną funkcją, która jest wykonywana tylko podczas tworzenia kontraktu.18 // Konstruktory służą do inicjalizacji danych kontraktu. Dowiedz się więcej: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#constructors19 constructor(string memory initMessage) {2021 // Akceptuje argument ciągu znaków `initMessage` i ustawia wartość w zmiennej pamięci kontraktu `message`).22 message = initMessage;23 }2425 // Funkcja publiczna, która akceptuje argument w postaci ciągu znaków i aktualizuje zmienną pamięci masowej `message`.26 function update(string memory newMessage) public {27 string memory oldMsg = message;28 message = newMessage;29 emit UpdatedMessages(oldMsg, newMessage);30 }31}Pokaż wszystkoJest to podstawowy inteligentny kontrakt, który przechowuje wiadomość po utworzeniu. Można go zaktualizować, wywołując funkcję update.
Krok 11: Podłącz MetaMask i Alchemy do swojego projektu
Stworzyliśmy portfel MetaMask, konto Alchemy i napisaliśmy nasz inteligentny kontrakt, teraz nadszedł czas, aby połączyć te trzy elementy.
Każda transakcja wysłana z Twojego portfela wymaga podpisu za pomocą Twojego unikalnego klucza prywatnego. Aby zapewnić naszemu programowi to uprawnienie, możemy bezpiecznie przechowywać nasz klucz prywatny w pliku środowiskowym. Będziemy tutaj również przechowywać klucz API dla Alchemy.
Aby dowiedzieć się więcej o wysyłaniu transakcji, sprawdź ten samouczek (opens in a new tab) na temat wysyłania transakcji za pomocą web3.
Najpierw zainstaluj pakiet dotenv w katalogu swojego projektu:
1npm install dotenv --saveNastępnie utwórz plik .env w głównym katalogu projektu. Dodaj do niego swój klucz prywatny MetaMask i adres URL HTTP Alchemy API.
Twój plik środowiskowy musi mieć nazwę .env, w przeciwnym razie nie zostanie rozpoznany jako plik środowiskowy.
Nie nazywaj go process.env lub .env-custom ani w żaden inny sposób.
- Postępuj zgodnie z tymi instrukcjami (opens in a new tab), aby wyeksportować swój klucz prywatny
- Poniżej dowiesz się, jak uzyskać adres URL interfejsu API HTTP Alchemy
Twój plik .env powinien wyglądać następująco:
1API_URL = "https://eth-goerli.alchemyapi.io/v2/your-api-key"2PRIVATE_KEY = "your-metamask-private-key"Aby faktycznie połączyć je z naszym kodem, odwołamy się do tych zmiennych w naszym pliku hardhat.config.js w kroku 13.
Krok 12: Zainstaluj Ethers.js
Ethers.js to biblioteka, która ułatwia interakcję i wysyłanie żądań do Ethereum poprzez opakowanie standardowych metod JSON-RPC (opens in a new tab) w bardziej przyjazne dla użytkownika metody.
Hardhat pozwala nam na integrację wtyczek (opens in a new tab) w celu uzyskania dodatkowych narzędzi i rozszerzonej funkcjonalności. Skorzystamy z wtyczki Ethers (opens in a new tab) do wdrażania kontraktów.
W katalogu projektu wpisz:
npm install --save-dev @nomiclabs/hardhat-ethers "ethers@^5.0.0"Krok 13: Zaktualizuj hardhat.config.js
Do tej pory dodaliśmy kilka zależności i wtyczek, teraz musimy zaktualizować hardhat.config.js, aby nasz projekt wiedział o wszystkich.
Zaktualizuj swój hardhat.config.js, aby wyglądał następująco:
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}Pokaż wszystkoKrok 14: Skompiluj nasz kontrakt
Aby upewnić się, że wszystko do tej pory działa, skompilujmy nasz kontrakt. Zadanie compile jest jednym z wbudowanych zadań hardhat.
Z wiersza poleceń uruchom:
npx hardhat compileMożesz otrzymać ostrzeżenie o SPDX license identifier not provided in source file, ale nie musisz się tym martwić - miejmy nadzieję, że wszystko inne wygląda dobrze! Jeśli nie, zawsze możesz napisać wiadomość na discordzie Alchemy (opens in a new tab).
Krok 15: Napisz nasz skrypt wdrożeniowy
Teraz, gdy nasz kontrakt jest napisany, a nasz plik konfiguracyjny jest gotowy, nadszedł czas, aby napisać nasz skrypt wdrażający kontrakt.
Przejdź do folderu scripts/ i utwórz nowy plik o nazwie deploy.js, dodając do niego następującą zawartość:
1async function main() {2 const HelloWorld = await ethers.getContractFactory("HelloWorld")34 // Rozpocznij wdrażanie, zwracając obietnicę, która rozwiązuje się do obiektu kontraktu5 const hello_world = await HelloWorld.deploy("Hello World!")6 console.log("Contract deployed to address:", hello_world.address)7}89main()10 .then(() => process.exit(0))11 .catch((error) => {12 console.error(error)13 process.exit(1)14 })Pokaż wszystkoHardhat wykonuje niesamowitą robotę, wyjaśniając, co robi każda z tych linii kodu w swoim samouczku dotyczącym kontraktów (opens in a new tab), a my przyjęliśmy ich wyjaśnienia tutaj.
1const HelloWorld = await ethers.getContractFactory("HelloWorld")ContractFactory w ethers.js jest abstrakcją używaną do wdrażania nowych inteligentnych kontraktów, więc HelloWorld jest tutaj fabryką (opens in a new tab) dla instancji naszego kontraktu witaj świecie. Podczas korzystania z wtyczki hardhat-ethers, instancje ContractFactory i Contract są domyślnie połączone z pierwszym sygnatariuszem (właścicielem).
1const hello_world = await HelloWorld.deploy()Wywołanie deploy() w ContractFactory rozpocznie wdrażanie i zwróci Promise, które rozwiąże się do obiektu Contract. Jest to obiekt, który ma metodę dla każdej z naszych funkcji inteligentnego kontraktu.
Krok 16: Wdróż nasz kontrakt
Jesteśmy wreszcie gotowi do wdrożenia naszego inteligentnego kontraktu! Przejdź do wiersza poleceń i uruchom:
npx hardhat run scripts/deploy.js --network goerliPowinieneś wtedy zobaczyć coś takiego:
Kontrakt wdrożony pod adresem: 0x6cd7d44516a20882cEa2DE9f205bF401c0d23570Proszę zapisać ten adres. Będziemy go używać w dalszej części samouczka.
Jeśli przejdziemy do Goerli etherscan (opens in a new tab) i wyszukamy adres naszego kontraktu, powinniśmy zobaczyć, że został on pomyślnie wdrożony. Transakcja będzie wyglądać mniej więcej tak:
Adres From powinien pasować do adresu konta MetaMask, a adres To będzie zawierał informację Contract Creation. Jeśli klikniemy w transakcję, zobaczymy adres naszego kontraktu w polu Do.
Gratulacje! Właśnie wdrożyłeś inteligentny kontrakt w sieci testowej Ethereum.
Aby zrozumieć, co dzieje się pod maską, przejdź do zakładki Eksplorator w naszym pulpicie nawigacyjnym Alchemy (opens in a new tab). Jeśli masz wiele aplikacji Alchemy, upewnij się, że filtrujesz według aplikacji i wybierz Hello World.
Tutaj zobaczysz kilka metod JSON-RPC, które Hardhat/Ethers stworzył dla nas pod maską, gdy wywołaliśmy funkcję .deploy(). Dwie ważne metody to eth_sendRawTransaction (opens in a new tab), czyli żądanie zapisu naszego kontraktu w łańcuchu Goerli, oraz eth_getTransactionByHash (opens in a new tab), czyli żądanie odczytania informacji o naszej transakcji na podstawie jej hasza. Aby dowiedzieć się więcej o wysyłaniu transakcji, sprawdź nasz samouczek na temat wysyłania transakcji za pomocą Web3.
Część 2: Interakcja z Twoim inteligentnym kontraktem
Teraz, gdy pomyślnie wdrożyliśmy inteligentny kontrakt w sieci Goerli, nauczmy się, jak wchodzić z nim w interakcję.
Utwórz plik interact.js
To jest plik, w którym napiszemy nasz skrypt interakcji. Będziemy używać biblioteki Ethers.js, którą zainstalowałeś wcześniej w części 1.
W folderze scripts/ utwórz nowy plik o nazwie interact.js i dodaj następujący kod:
1// interact.js23const API_KEY = process.env.API_KEY4const PRIVATE_KEY = process.env.PRIVATE_KEY5const CONTRACT_ADDRESS = process.env.CONTRACT_ADDRESSZaktualizuj swój plik .env
Będziemy używać nowych zmiennych środowiskowych, więc musimy je zdefiniować w pliku .env, który utworzyliśmy wcześniej.
Będziemy musieli dodać definicję dla naszego API_KEY Alchemy i CONTRACT_ADDRESS, gdzie został wdrożony nasz inteligentny kontrakt.
Plik .env powinien wyglądać następująco:
# .envAPI_URL = "https://eth-goerli.alchemyapi.io/v2/<your-api-key>"API_KEY = "<your-api-key>"PRIVATE_KEY = "<your-metamask-private-key>"CONTRACT_ADDRESS = "0x<your contract address>"Pobierz swoje ABI kontraktu
Nasze kontraktu to interfejs do interakcji z naszym inteligentnym kontraktem. Hardhat automatycznie generuje ABI i zapisuje go w HelloWorld.json. Aby użyć ABI, będziemy musieli przeanalizować zawartość, dodając następujące wiersze kodu do naszego pliku interact.js:
1// interact.js2const contract = require("../artifacts/contracts/HelloWorld.sol/HelloWorld.json")Jeśli chcesz zobaczyć ABI, możesz je wydrukować w swojej konsoli:
1console.log(JSON.stringify(contract.abi))Aby zobaczyć swoje ABI wydrukowane na konsoli, przejdź do terminala i uruchom:
npx hardhat run scripts/interact.jsUtwórz instancję swojego kontraktu
Aby wejść w interakcję z naszym kontraktem, musimy stworzyć jego instancję w naszym kodzie. Aby to zrobić za pomocą Ethers.js, będziemy musieli pracować z trzema koncepcjami:
- Dostawca - dostawca węzła, który daje Ci dostęp do odczytu i zapisu do blockchaina
- Sygnatariusz - reprezentuje konto Ethereum, które może podpisywać transakcje
- Kontrakt - obiekt Ethers.js reprezentujący określony kontrakt wdrożony w łańcuchu
Użyjemy ABI kontraktu z poprzedniego kroku, aby stworzyć instancję naszego kontraktu:
1// interact.js23// Dostawca4const alchemyProvider = new ethers.providers.AlchemyProvider(5 (network = "goerli"),6 API_KEY7)89// Sygnatariusz10const signer = new ethers.Wallet(PRIVATE_KEY, alchemyProvider)1112// Kontrakt13const helloWorldContract = new ethers.Contract(14 CONTRACT_ADDRESS,15 contract.abi,16 signer17)Pokaż wszystkoDowiedz się więcej o Dostawcach, Sygnatariuszach i Kontraktach w dokumentacji ethers.js (opens in a new tab).
Przeczytaj wiadomość init
Pamiętasz, jak wdrożyliśmy nasz kontrakt z initMessage = "Hello world!"? Teraz odczytamy tę wiadomość zapisaną w naszym inteligentnym kontrakcie i wydrukujemy ją na konsoli.
W języku JavaScript funkcje asynchroniczne są używane podczas interakcji z sieciami. Aby dowiedzieć się więcej o funkcjach asynchronicznych, przeczytaj ten artykuł (opens in a new tab).
Użyj poniższego kodu, aby wywołać funkcję message w naszym inteligentnym kontrakcie i odczytać wiadomość init:
1// interact.js23// ...45asynchroniczna funkcja main() {6 const message = await helloWorldContract.message()7 console.log("Wiadomość to: " + message)8}9main()Pokaż wszystkoPo uruchomieniu pliku za pomocą npx hardhat run scripts/interact.js w terminalu powinniśmy zobaczyć taką odpowiedź:
1Wiadomość to: Witaj świecie!Gratulacje! Właśnie pomyślnie odczytałeś dane inteligentnego kontraktu z blockchaina Ethereum, brawo!
Zaktualizuj wiadomość
Zamiast tylko odczytywać wiadomość, możemy również zaktualizować wiadomość zapisaną w naszym inteligentnym kontrakcie za pomocą funkcji update! Całkiem fajne, prawda?
Aby zaktualizować wiadomość, możemy bezpośrednio wywołać funkcję update na naszym obiekcie Contract:
1// interact.js23// ...45asynchroniczna funkcja main() {6 const message = await helloWorldContract.message()7 console.log("Wiadomość to: " + message)89 console.log("Aktualizowanie wiadomości...")10 const tx = await helloWorldContract.update("To jest nowa wiadomość.")11 await tx.wait()12}13main()Pokaż wszystkoZauważ, że w linii 11, wywołujemy .wait() na zwróconym obiekcie transakcji. To zapewnia, że nasz skrypt czeka na wydobycie transakcji na blockchainie przed zakończeniem funkcji. Jeśli wywołanie .wait() nie zostanie uwzględnione, skrypt może nie zobaczyć zaktualizowanej wartości message w kontrakcie.
Przeczytaj nową wiadomość
Powinieneś być w stanie powtórzyć poprzedni krok, aby odczytać zaktualizowaną wartość message. Poświęć chwilę i sprawdź, czy możesz dokonać niezbędnych zmian, aby wydrukować tę nową wartość!
Jeśli potrzebujesz podpowiedzi, oto jak powinien wyglądać twój plik interact.js w tym momencie:
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// dostawca - Alchemy10const alchemyProvider = new ethers.providers.AlchemyProvider(11 (network = "goerli"),12 API_KEY13)1415// podpisujący - ty16const signer = new ethers.Wallet(PRIVATE_KEY, alchemyProvider)1718// instancja kontraktu19const helloWorldContract = new ethers.Contract(20 CONTRACT_ADDRESS,21 contract.abi,22 signer23)2425asynchroniczna funkcja main() {26 const message = await helloWorldContract.message()27 console.log("Wiadomość to: " + message)2829 console.log("Aktualizowanie wiadomości...")30 const tx = await helloWorldContract.update("this is the new message")31 await tx.wait()3233 const newMessage = await helloWorldContract.message()34 console.log("Nowa wiadomość to: " + newMessage)35}3637main()Pokaż wszystkoTeraz po prostu uruchom skrypt, a powinieneś zobaczyć starą wiadomość, status aktualizacji i nową wiadomość wydrukowaną w terminalu!
npx hardhat run scripts/interact.js --network goerli
1Wiadomość to: Witaj świecie!2Aktualizowanie wiadomości...3Nowa wiadomość to: To jest nowa wiadomość.Podczas uruchamiania tego skryptu możesz zauważyć, że krok Aktualizowanie wiadomości... zajmuje chwilę, zanim załaduje się nowa wiadomość. Jest to spowodowane procesem wydobycia. Jeśli jesteś ciekaw śledzenia transakcji podczas ich wydobywania, odwiedź mempool Alchemy (opens in a new tab), aby zobaczyć status transakcji. Jeśli transakcja zostanie odrzucona, warto również sprawdzić Goerli Etherscan (opens in a new tab) i wyszukać swój hasz transakcji.
Część 3: Opublikuj swój inteligentny kontrakt w Etherscan
Wykonałeś całą ciężką pracę, aby ożywić swój inteligentny kontrakt, teraz nadszedł czas, aby podzielić się nim ze światem!
Weryfikując swój inteligentny kontrakt w Etherscan, każdy może zobaczyć jego kod źródłowy i wejść z nim w interakcję. Zaczynajmy!
Krok 1: Wygeneruj klucz API na swoim koncie Etherscan
Klucz API Etherscan jest niezbędny do zweryfikowania, czy jesteś właścicielem inteligentnego kontraktu, który próbujesz opublikować.
Jeśli nie masz jeszcze konta Etherscan, załóż konto (opens in a new tab).
Po zalogowaniu znajdź swoją nazwę użytkownika w pasku nawigacyjnym, najedź na nią i wybierz przycisk Mój profil.
Na stronie profilu powinieneś zobaczyć boczny pasek nawigacyjny. Z bocznego paska nawigacyjnego wybierz Klucze API. Następnie naciśnij przycisk „Dodaj”, aby utworzyć nowy klucz API, nazwij swoją aplikację hello-world i naciśnij przycisk Utwórz nowy klucz API.
Nowy klucz API powinien pojawić się w tabeli kluczy API. Skopiuj klucz API do schowka.
Następnie musimy dodać klucz API Etherscan do naszego pliku .env.
Po dodaniu, twój plik .env powinien wyglądać tak:
1API_URL = "https://eth-goerli.alchemyapi.io/v2/your-api-key"2PUBLIC_KEY = "your-public-account-address"3PRIVATE_KEY = "your-private-account-address"4CONTRACT_ADDRESS = "your-contract-address"5ETHERSCAN_API_KEY = "your-etherscan-key"Inteligentne kontrakty wdrożone za pomocą Hardhat
Zainstaluj hardhat-etherscan
Publikowanie kontraktu w Etherscan za pomocą Hardhat jest proste. Na początek musisz zainstalować wtyczkę hardhat-etherscan. hardhat-etherscan automatycznie zweryfikuje kod źródłowy inteligentnego kontraktu i ABI w Etherscan. Aby to dodać, w katalogu hello-world uruchom:
1npm install --save-dev @nomiclabs/hardhat-etherscanPo zainstalowaniu, dołącz następującą instrukcję na początku pliku hardhat.config.js i dodaj opcje konfiguracyjne 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 // Twój klucz API dla Etherscan21 // Uzyskaj go na https://etherscan.io/22 apiKey: ETHERSCAN_API_KEY,23 },24}Pokaż wszystkoZweryfikuj swój inteligentny kontrakt na Etherscan
Upewnij się, że wszystkie pliki są zapisane, a wszystkie zmienne .env są poprawnie skonfigurowane.
Uruchom zadanie verify, podając adres kontraktu i sieć, w której jest wdrożony:
1npx hardhat verify --network goerli DEPLOYED_CONTRACT_ADDRESS 'Hello World!'Upewnij się, że DEPLOYED_CONTRACT_ADDRESS to adres Twojego wdrożonego inteligentnego kontraktu w sieci testowej Goerli. Ponadto ostatni argument ('Hello World!') musi być tą samą wartością ciągu znaków, która została użyta podczas kroku wdrażania w części 1.
Jeśli wszystko pójdzie dobrze, zobaczysz następujący komunikat w terminalu:
1Pomyślnie przesłano kod źródłowy dla kontraktu2contracts/HelloWorld.sol:HelloWorld at 0xdeployed-contract-address3do weryfikacji na Etherscan. Oczekiwanie na wynik weryfikacji...456Pomyślnie zweryfikowano kontrakt HelloWorld na Etherscan.7https://goerli.etherscan.io/address/<contract-address>#contractsGratulacje! Twój kod inteligentnego kontraktu jest w Etherscan!
Sprawdź swój inteligentny kontrakt w Etherscan!
Po przejściu do linku podanego w terminalu, powinieneś być w stanie zobaczyć kod inteligentnego kontraktu i ABI opublikowane na Etherscan!
Hura - udało Ci się, mistrzu! Teraz każdy może wywołać lub zapisać do Twojego inteligentnego kontraktu! Nie możemy się doczekać, co zbudujesz dalej!
Część 4 – Integracja inteligentnego kontraktu z frontendem
Po ukończeniu tego samouczka dowiesz się, jak:
- Połącz portfel MetaMask z Twoją dappką
- Odczytaj dane ze swojego inteligentnego kontraktu za pomocą interfejsu API Alchemy Web3 (opens in a new tab)
- Podpisz transakcje Ethereum za pomocą MetaMask
Dla tej dapki użyjemy React (opens in a new tab) jako naszego frameworka frontendowego. Należy jednak zauważyć, że nie będziemy spędzać dużo czasu na omawianiu jego podstaw, ponieważ skupimy się głównie na wprowadzaniu funkcjonalności Web3 do naszego projektu.
Jako warunek wstępny, powinieneś mieć podstawową wiedzę na temat React. Jeśli nie, polecamy ukończenie oficjalnego samouczka Wprowadzenie do React (opens in a new tab).
Klonowanie plików startowych
Najpierw przejdź do repozytorium GitHub hello-world-part-four (opens in a new tab), aby pobrać pliki startowe dla tego projektu i sklonować to repozytorium na swój komputer lokalny.
Otwórz sklonowane repozytorium lokalnie. Zauważ, że zawiera on dwa foldery: starter-files i completed.
starter-files- będziemy pracować w tym katalogu, połączymy interfejs użytkownika z Twoim portfelem Ethereum i inteligentnym kontraktem, który opublikowaliśmy w Etherscan w części 3.completedzawiera cały ukończony samouczek i powinien być używany tylko jako odniesienie, jeśli utkniesz.
Następnie otwórz swoją kopię starter-files w ulubionym edytorze kodu, a następnie przejdź do folderu src.
Cały kod, który napiszemy, będzie znajdować się w folderze src. Będziemy edytować komponent HelloWorld.js i pliki JavaScript util/interact.js, aby nadać naszemu projektowi funkcjonalność Web3.
Sprawdź pliki startowe
Zanim zaczniemy kodować, przeanalizujmy, co jest dla nas dostępne w plikach startowych.
Uruchomienie projektu React
Zacznijmy od uruchomienia projektu React w naszej przeglądarce. Piękno React polega na tym, że gdy nasz projekt jest już uruchomiony w przeglądarce, wszelkie zapisane przez nas zmiany będą na bieżąco aktualizowane w przeglądarce.
Aby uruchomić projekt, przejdź do katalogu głównego folderu starter-files i uruchom npm install w terminalu, aby zainstalować zależności projektu:
cd starter-filesnpm installPo zakończeniu instalacji uruchom npm start w terminalu:
npm startSpowoduje to otwarcie http://localhost:3000/ (opens in a new tab) w przeglądarce, gdzie zobaczysz frontend naszego projektu. Powinien on składać się z jednego pola (miejsce do aktualizacji wiadomości przechowywanej w inteligentnym kontrakcie), przycisku „Połącz portfel” i przycisku „Aktualizuj”.
Jeśli spróbujesz kliknąć którykolwiek z przycisków, zauważysz, że nie działają – to dlatego, że wciąż musimy zaprogramować ich funkcjonalność.
Komponent HelloWorld.js
Wróćmy do folderu src w naszym edytorze i otwórzmy plik HelloWorld.js. Jest bardzo ważne, abyśmy zrozumieli wszystko w tym pliku, ponieważ jest to główny komponent React, nad którym będziemy pracować.
Na górze tego pliku zauważysz, że mamy kilka instrukcji importu, które są niezbędne do uruchomienia naszego projektu, w tym bibliotekę React, hooki useEffect i useState, niektóre elementy z ./util/interact.js (opiszemy je bardziej szczegółowo wkrótce!) i logo 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"Pokaż wszystkoNastępnie mamy nasze zmienne stanu, które będziemy aktualizować po określonych zdarzeniach.
1// HelloWorld.js23// Zmienne stanu4const [walletAddress, setWallet] = useState("")5const [status, setStatus] = useState("")6const [message, setMessage] = useState("Brak połączenia z siecią.") 7const [newMessage, setNewMessage] = useState("")Oto, co reprezentuje każda ze zmiennych:
walletAddress- ciąg znaków, który przechowuje adres portfela użytkownikastatus- ciąg znaków, który przechowuje pomocną wiadomość, która prowadzi użytkownika, jak wchodzić w interakcję z dappmessage- ciąg znaków, który przechowuje bieżącą wiadomość w inteligentnym kontrakcienewMessage- ciąg znaków, który przechowuje nową wiadomość, która zostanie zapisana w inteligentnym kontrakcie
Po zmiennych stanu zobaczysz pięć niezaimplementowanych funkcji: useEffect, addSmartContractListener, addWalletListener, connectWalletPressed i onUpdatePressed. Poniżej wyjaśnimy, co robią:
1// HelloWorld.js23// wywoływane tylko raz4useEffect(async () => {5 // TODO: zaimplementuj6}, [])78function addSmartContractListener() {9 // TODO: zaimplementuj10}1112function addWalletListener() {13 // TODO: zaimplementuj14}1516const connectWalletPressed = async () => {17 // TODO: zaimplementuj18}1920const onUpdatePressed = async () => {21 // TODO: zaimplementuj22}Pokaż wszystkouseEffect(opens in a new tab) - to hook React, który jest wywoływany po wyrenderowaniu komponentu. Ponieważ ma przekazaną pustą tablicę[]jako właściwość (patrz linia 4), będzie wywoływany tylko podczas pierwszego renderowania komponentu. Tutaj załadujemy bieżącą wiadomość zapisaną w naszym inteligentnym kontrakcie, wywołamy nasze inteligentne kontrakty i nasłuchiwacze portfela, a także zaktualizujemy nasz interfejs użytkownika, aby odzwierciedlić, czy portfel jest już podłączony.addSmartContractListener- ta funkcja konfiguruje nasłuchiwacza, który będzie obserwował zdarzenieUpdatedMessagesnaszego kontraktu HelloWorld i aktualizował nasz interfejs użytkownika, gdy wiadomość w naszym inteligentnym kontrakcie ulegnie zmianie.addWalletListener- ta funkcja konfiguruje nasłuchiwacza, który wykrywa zmiany w stanie portfela MetaMask użytkownika, np. gdy użytkownik odłącza portfel lub zmienia adresy.connectWalletPressed- ta funkcja zostanie wywołana w celu połączenia portfela MetaMask użytkownika z naszą dapp.onUpdatePressed- ta funkcja zostanie wywołana, gdy użytkownik będzie chciał zaktualizować wiadomość zapisaną w inteligentnym kontrakcie.
Pod koniec tego pliku mamy interfejs użytkownika naszego komponentu.
1// HelloWorld.js23// interfejs użytkownika naszego komponentu4return (5 <div id="container">6 <img id="logo" src={alchemylogo}></img>7 <button id="walletButton" onClick={connectWalletPressed}>8 {walletAddress.length > 0 ? (9 "Połączono: " +10 String(walletAddress).substring(0, 6) +11 "..." +12 String(walletAddress).substring(38)13 ) : (14 <span>Połącz portfel</span>15 )}16 </button>1718 <h2 style={{ paddingTop: "50px" }}>Bieżąca wiadomość:</h2>19 <p>{message}</p>2021 <h2 style={{ paddingTop: "18px" }}>Nowa wiadomość:</h2>2223 <div>24 <input25 type="text"26 placeholder="Zaktualizuj wiadomość w swoim inteligentnym kontrakcie."27 onChange={(e) => setNewMessage(e.target.value)}28 value={newMessage}29 />30 <p id="status">{status}</p>3132 <button id="publishButton" onClick={onUpdatePressed}>33 Zaktualizuj34 </button>35</div>36 37</div>38)Pokaż wszystkoJeśli dokładnie przeanalizujesz ten kod, zauważysz, gdzie używamy naszych różnych zmiennych stanu w naszym interfejsie użytkownika:
- W liniach 6-12, jeśli portfel użytkownika jest podłączony (tj.
walletAddress.length > 0), wyświetlamy skróconą wersjęwalletAddressużytkownika w przycisku o identyfikatorze „walletButton”; w przeciwnym razie jest tam po prostu napis „Połącz portfel”. - W linii 17 wyświetlamy bieżącą wiadomość zapisaną w inteligentnym kontrakcie, która jest przechwytywana w ciągu znaków
message. - W liniach 23-26 używamy komponentu kontrolowanego (opens in a new tab), aby zaktualizować naszą zmienną stanu
newMessage, gdy zmienia się wpis w polu tekstowym.
Oprócz naszych zmiennych stanu, zobaczysz również, że funkcje connectWalletPressed i onUpdatePressed są wywoływane, gdy odpowiednio klikane są przyciski o identyfikatorach publishButton i walletButton.
Na koniec zajmijmy się tym, gdzie ten komponent HelloWorld.js jest dodawany.
Jeśli przejdziesz do pliku App.js, który jest głównym komponentem w React, który działa jako kontener dla wszystkich innych komponentów, zobaczysz, że nasz komponent HelloWorld.js jest wstrzykiwany w linii 7.
Na koniec sprawdźmy jeszcze jeden plik, który został dla Ciebie przygotowany, plik interact.js.
Plik interact.js
Ponieważ chcemy trzymać się paradygmatu M-V-C (opens in a new tab), będziemy chcieli mieć osobny plik, który zawiera wszystkie nasze funkcje do zarządzania logiką, danymi i regułami naszej dapki, a następnie będziemy mogli wyeksportować te funkcje do naszego frontendu (naszego komponentu HelloWorld.js).
👆🏽 To jest dokładny cel naszego pliku interact.js!
Przejdź do folderu util w swoim katalogu src, a zauważysz, że dołączyliśmy plik o nazwie interact.js, który będzie zawierał wszystkie nasze funkcje i zmienne interakcji z inteligentnym kontraktem i portfelem.
1// interact.js23//export const helloWorldContract;45export const loadCurrentMessage = async () => {}67export const connectWallet = async () => {}89const getCurrentWalletConnected = async () => {}1011export const updateMessage = async (message) => {}Pokaż wszystkoNa początku pliku zauważysz, że skomentowaliśmy obiekt helloWorldContract. Później w tym samouczku odkomentujemy ten obiekt i utworzymy instancję naszego inteligentnego kontraktu w tej zmiennej, którą następnie wyeksportujemy do naszego komponentu HelloWorld.js.
Cztery niezaimplementowane funkcje po naszym obiekcie helloWorldContract robią następujące rzeczy:
loadCurrentMessage- ta funkcja obsługuje logikę ładowania bieżącej wiadomości zapisanej w inteligentnym kontrakcie. Wywoła ona żądanie odczytu do inteligentnego kontraktu Witaj Świecie za pomocą interfejsu API Alchemy Web3 (opens in a new tab).connectWallet- ta funkcja połączy MetaMask użytkownika z naszą dappką.getCurrentWalletConnected- ta funkcja sprawdzi, czy konto Ethereum jest już połączone z naszą dappką podczas ładowania strony i odpowiednio zaktualizuje nasz interfejs użytkownika.updateMessage- ta funkcja zaktualizuje wiadomość zapisaną w inteligentnym kontrakcie. Wykona ona żądanie zapisu do inteligentnego kontraktu Witaj Świecie, więc portfel MetaMask użytkownika będzie musiał podpisać transakcję Ethereum, aby zaktualizować wiadomość.
Teraz, gdy rozumiemy, z czym pracujemy, dowiedzmy się, jak czytać z naszego inteligentnego kontraktu!
Krok 3: Odczyt z Twojego inteligentnego kontraktu
Aby odczytać dane z inteligentnego kontraktu, musisz pomyślnie skonfigurować:
- Połączenie API z łańcuchem Ethereum
- Załadowana instancja Twojego inteligentnego kontraktu
- Funkcja do wywołania funkcji inteligentnego kontraktu
- Nasłuchiwacz do obserwowania aktualizacji, gdy zmieniają się dane, które odczytujesz z inteligentnego kontraktu
To może brzmieć jak wiele kroków, ale nie martw się! Przeprowadzimy Cię przez każdy z nich krok po kroku! :)
Ustanów połączenie API z łańcuchem Ethereum
Pamiętasz, jak w części 2 tego samouczka użyliśmy naszego klucza Alchemy Web3 do odczytu z naszego inteligentnego kontraktu (https://docs.alchemy.com/alchemy/tutorials/hello-world-smart-contract/interacting-with-a-smart-contract#step-1-install-web3-library (opens in a new tab))? Będziesz również potrzebować klucza Alchemy Web3 w swojej dapce, aby czytać z łańcucha.
Jeśli go jeszcze nie masz, najpierw zainstaluj Alchemy Web3 (opens in a new tab), przechodząc do katalogu głównego starter-files i uruchamiając następujące polecenie w terminalu:
1npm install @alch/alchemy-web3Alchemy Web3 (opens in a new tab) to nakładka na Web3.js (opens in a new tab), zapewniająca ulepszone metody API i inne kluczowe korzyści, które ułatwiają życie dewelopera web3. Został zaprojektowany tak, aby wymagał minimalnej konfiguracji, dzięki czemu możesz od razu zacząć go używać w swojej aplikacji!
Następnie zainstaluj pakiet dotenv (opens in a new tab) w katalogu projektu, abyśmy mieli bezpieczne miejsce do przechowywania naszego klucza API po jego pobraniu.
1npm install dotenv --saveDla naszej dapki będziemy używać naszego klucza API Websockets zamiast klucza API HTTP, ponieważ pozwoli nam to skonfigurować nasłuchiwacza, który wykrywa, kiedy zmienia się wiadomość zapisana w inteligentnym kontrakcie.
Po uzyskaniu klucza API, utwórz plik .env w swoim katalogu głównym i dodaj do niego adres URL Alchemy Websockets. Następnie plik .env powinien wyglądać następująco:
1REACT_APP_ALCHEMY_KEY = wss://eth-goerli.ws.alchemyapi.io/v2/<key>Teraz jesteśmy gotowi do skonfigurowania naszego punktu końcowego Alchemy Web3 w naszej dapce! Wróćmy do naszego pliku interact.js, który jest zagnieżdżony w naszym folderze util i dodajmy następujący kod na początku pliku:
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;Powyżej najpierw zaimportowaliśmy klucz Alchemy z naszego pliku .env, a następnie przekazaliśmy nasz alchemyKey do createAlchemyWeb3, aby ustanowić nasz punkt końcowy Alchemy Web3.
Gdy ten punkt końcowy jest gotowy, nadszedł czas, aby załadować nasz inteligentny kontrakt!
Ładowanie Twojego inteligentnego kontraktu Witaj Świecie
Aby załadować swój inteligentny kontrakt Witaj Świecie, będziesz potrzebować jego adresu kontraktu i ABI, które można znaleźć w Etherscan, jeśli ukończyłeś część 3 tego samouczka.
Jak uzyskać ABI kontraktu z Etherscan
Jeśli pominąłeś część 3 tego samouczka, możesz użyć kontraktu HelloWorld z adresem 0x6f3f635A9762B47954229Ea479b4541eAF402A6A (opens in a new tab). Jego ABI można znaleźć tutaj (opens in a new tab).
ABI kontraktu jest niezbędne do określenia, którą funkcję kontrakt wywoła, a także do zapewnienia, że funkcja zwróci dane w oczekiwanym formacie. Po skopiowaniu naszego ABI kontraktu, zapiszmy go jako plik JSON o nazwie contract-abi.json w swoim katalogu src.
Twój plik contract-abi.json powinien być przechowywany w folderze src.
Mając do dyspozycji adres kontraktu, ABI i punkt końcowy Alchemy Web3, możemy użyć metody kontraktu (opens in a new tab), aby załadować instancję naszego inteligentnego kontraktu. Zaimportuj ABI kontraktu do pliku interact.js i dodaj adres kontraktu.
1// interact.js23const contractABI = require("../contract-abi.json")4const contractAddress = "0x6f3f635A9762B47954229Ea479b4541eAF402A6A"Możemy teraz wreszcie odkomentować naszą zmienną helloWorldContract i załadować inteligentny kontrakt za pomocą naszego punktu końcowego AlchemyWeb3:
1// interact.js2export const helloWorldContract = new web3.eth.Contract(3 contractABI,4 contractAddress5)Podsumowując, pierwsze 12 linii pliku interact.js powinno teraz wyglądać tak:
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)Pokaż wszystkoTeraz, gdy nasz kontrakt jest załadowany, możemy zaimplementować naszą funkcję loadCurrentMessage!
Implementacja loadCurrentMessage w pliku interact.js
Ta funkcja jest super prosta. Zrobimy proste asynchroniczne wywołanie web3, aby odczytać z naszego kontraktu. Nasza funkcja zwróci wiadomość zapisaną w inteligentnym kontrakcie:
Zaktualizuj loadCurrentMessage w swoim pliku interact.js do następującej postaci:
1// interact.js23export const loadCurrentMessage = async () => {4 const message = await helloWorldContract.methods.message().call()5 return message6}Ponieważ chcemy wyświetlić ten inteligentny kontrakt w naszym interfejsie użytkownika, zaktualizujmy funkcję useEffect w naszym komponencie HelloWorld.js do następującej postaci:
1// HelloWorld.js23// wywoływane tylko raz4useEffect(async () => {5 const message = await loadCurrentMessage()6 setMessage(message)7}, [])Zauważ, że chcemy, aby nasza funkcja loadCurrentMessage była wywoływana tylko raz podczas pierwszego renderowania komponentu. Wkrótce zaimplementujemy addSmartContractListener, aby automatycznie aktualizować interfejs użytkownika po zmianie wiadomości w inteligentnym kontrakcie.
Zanim przejdziemy do naszego nasłuchiwacza, sprawdźmy, co mamy do tej pory! Zapisz pliki HelloWorld.js i interact.js, a następnie przejdź do http://localhost:3000/ (opens in a new tab)
Zauważysz, że bieżąca wiadomość nie brzmi już „Brak połączenia z siecią”. Zamiast tego odzwierciedla ona wiadomość zapisaną w inteligentnym kontrakcie. Super!
Twój interfejs użytkownika powinien teraz odzwierciedlać wiadomość zapisaną w inteligentnym kontrakcie
A propos tego nasłuchiwacza...
Zaimplementuj addSmartContractListener
Jeśli przypomnisz sobie plik HelloWorld.sol, który napisaliśmy w części 1 tej serii samouczków (opens in a new tab), przypomnisz sobie, że istnieje zdarzenie inteligentnego kontraktu o nazwie UpdatedMessages, które jest emitowane po wywołaniu funkcji update naszego inteligentnego kontraktu (patrz linie 9 i 27):
1// HelloWorld.sol23// Określa wersję Solidity, używając semantycznego wersjonowania.4// Dowiedz się więcej: https://solidity.readthedocs.io/en/v0.5.10/layout-of-source-files.html#pragma5pragma solidity ^0.7.3;67// Definiuje kontrakt o nazwie `HelloWorld`.8// Kontrakt jest zbiorem funkcji i danych (jego stanu). Po wdrożeniu kontrakt znajduje się pod określonym adresem w blockchainie Ethereum. Dowiedz się więcej: https://solidity.readthedocs.io/en/v0.5.10/structure-of-a-contract.html9contract HelloWorld {1011 // Emitowane, gdy wywoływana jest funkcja aktualizacji12 // Zdarzenia inteligentnych kontraktów to sposób, w jaki kontrakt komunikuje, że coś wydarzyło się na blockchainie do front-endu aplikacji, który może „nasłuchiwać” określonych zdarzeń i podejmować działania, gdy one wystąpią.13 event UpdatedMessages(string oldStr, string newStr);1415 // Deklaruje zmienną stanu `message` typu `string`.16 // Zmienne stanu to zmienne, których wartości są trwale przechowywane w pamięci kontraktu. Słowo kluczowe `public` udostępnia zmienne spoza kontraktu i tworzy funkcję, którą inne kontrakty lub klienci mogą wywołać w celu uzyskania dostępu do wartości.17 string public message;1819 // Podobnie jak w wielu językach obiektowych opartych na klasach, konstruktor jest specjalną funkcją, która jest wykonywana tylko podczas tworzenia kontraktu.20 // Konstruktory służą do inicjalizacji danych kontraktu. Dowiedz się więcej: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#constructors21 constructor(string memory initMessage) {2223 // Akceptuje argument ciągu znaków `initMessage` i ustawia wartość w zmiennej pamięci kontraktu `message`).24 message = initMessage;25 }2627 // Funkcja publiczna, która akceptuje argument w postaci ciągu znaków i aktualizuje zmienną pamięci masowej `message`.28 function update(string memory newMessage) public {29 string memory oldMsg = message;30 message = newMessage;31 emit UpdatedMessages(oldMsg, newMessage);32 }33}Pokaż wszystkoZdarzenia inteligentnych kontraktów to sposób, w jaki kontrakt komunikuje, że coś się stało (tj. miało miejsce zdarzenie) na blockchainie do Twojej aplikacji front-endowej, która może „nasłuchiwać” określonych zdarzeń i podejmować działania, gdy one wystąpią.
Funkcja addSmartContractListener będzie konkretnie nasłuchiwać zdarzenia UpdatedMessages naszego inteligentnego kontraktu Witaj Świecie i aktualizować nasz interfejs użytkownika, aby wyświetlić nową wiadomość.
Zmodyfikuj addSmartContractListener do następującej postaci:
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("🎉 Twoja wiadomość została zaktualizowana!")11 }12 })13}Pokaż wszystkoPrzeanalizujmy, co się dzieje, gdy nasłuchiwacz wykryje zdarzenie:
- Jeśli wystąpi błąd podczas emitowania zdarzenia, zostanie on odzwierciedlony w interfejsie użytkownika za pośrednictwem naszej zmiennej stanu
status. - W przeciwnym razie użyjemy zwróconego obiektu
data.data.returnValuesto tablica indeksowana od zera, w której pierwszy element tablicy przechowuje poprzednią wiadomość, a drugi element przechowuje zaktualizowaną. W sumie, w przypadku pomyślnego zdarzenia, ustawimy nasz ciąg znakówmessagena zaktualizowaną wiadomość, wyczyścimy ciąg znakównewMessagei zaktualizujemy naszą zmienną stanustatus, aby odzwierciedlić, że nowa wiadomość została opublikowana w naszym inteligentnym kontrakcie.
Na koniec wywołajmy naszego nasłuchiwacza w naszej funkcji useEffect, aby został zainicjowany podczas pierwszego renderowania komponentu HelloWorld.js. W sumie Twoja funkcja useEffect powinna wyglądać tak:
1// HelloWorld.js23useEffect(async () => {4 const message = await loadCurrentMessage()5 setMessage(message)6 addSmartContractListener()7}, [])Teraz, gdy jesteśmy w stanie czytać z naszego inteligentnego kontraktu, byłoby wspaniale dowiedzieć się, jak do niego pisać! Jednak, aby pisać do naszej dapki, musimy najpierw mieć do niej podłączony portfel Ethereum.
Więc następnie zajmiemy się konfiguracją naszego portfela Ethereum (MetaMask), a następnie podłączeniem go do naszej dapki!
Krok 4: Skonfiguruj swój portfel Ethereum
Aby cokolwiek zapisać w łańcuchu Ethereum, użytkownicy muszą podpisywać transakcje za pomocą kluczy prywatnych swojego wirtualnego portfela. W tym samouczku użyjemy MetaMask (opens in a new tab), wirtualnego portfela w przeglądarce używanego do zarządzania adresem konta Ethereum, ponieważ znacznie ułatwia to podpisywanie transakcji dla użytkownika końcowego.
Jeśli chcesz dowiedzieć się więcej o tym, jak działają transakcje w Ethereum, sprawdź tę stronę od Ethereum Foundation.
Pobierz MetaMask
Możesz pobrać i utworzyć konto MetaMask za darmo tutaj (opens in a new tab). Podczas tworzenia konta, lub jeśli już je posiadasz, upewnij się, że przełączyłeś się na „Sieć testową Goerli” w prawym górnym rogu (aby nie mieć do czynienia z prawdziwymi pieniędzmi).
Dodaj ether z Faucet
Aby podpisać transakcję na blockchainie Ethereum, będziemy potrzebować trochę fałszywego Eth. Aby uzyskać Eth, możesz przejść do FaucETH (opens in a new tab) i wprowadzić swój adres konta Goerli, kliknąć „Poproś o fundusze”, następnie wybrać „Ethereum Testnet Goerli” w menu rozwijanym i na koniec ponownie kliknąć przycisk „Poproś o fundusze”. Wkrótce powinieneś zobaczyć Eth na swoim koncie MetaMask!
Sprawdź swoje saldo
Aby sprawdzić, czy nasze saldo jest na miejscu, wykonajmy żądanie eth_getBalance (opens in a new tab) za pomocą narzędzia kompozytora Alchemy (opens in a new tab). Zwróci to ilość Eth w naszym portfelu. Po wprowadzeniu adresu konta MetaMask i kliknięciu „Wyślij żądanie” powinieneś zobaczyć następującą odpowiedź:
1{"jsonrpc": "2.0", "id": 0, "result": "0xde0b6b3a7640000"}UWAGA: ten wynik jest w wei, a nie w eth. Wei jest używany jako najmniejsza jednostka etheru. Konwersja z wei na eth to: 1 eth = 10¹⁸ wei. Więc jeśli przekonwertujemy 0xde0b6b3a7640000 na system dziesiętny, otrzymamy 1*10¹⁸, co równa się 1 eth.
Uff! Nasze fałszywe pieniądze są na miejscu! 🤑
Krok 5: Podłącz MetaMask do swojego interfejsu użytkownika
Teraz, gdy nasz portfel MetaMask jest skonfigurowany, połączmy z nim naszą dapką!
Funkcja connectWallet
W naszym pliku interact.js zaimplementujmy funkcję connectWallet, którą następnie możemy wywołać w naszym komponencie HelloWorld.js.
Zmodyfikujmy connectWallet do następującej postaci:
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: "👆🏽 Wpisz wiadomość w polu tekstowym powyżej.",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 Musisz zainstalować MetaMask, wirtualny portfel Ethereum, w swojej29 przeglądarce.30 </a>31 </p>32 </span>33 ),34 }35 }36}Pokaż wszystkoWięc co dokładnie robi ten gigantyczny blok kodu?
Po pierwsze, sprawdza, czy window.ethereum jest włączone w przeglądarce.
window.ethereum to globalny interfejs API wstrzykiwany przez MetaMask i innych dostawców portfeli, który pozwala stronom internetowym na żądanie dostępu do kont Ethereum użytkowników. Jeśli zostanie zatwierdzony, może odczytywać dane z blockchainów, z którymi użytkownik jest połączony, i sugerować, aby użytkownik podpisywał wiadomości i transakcje. Sprawdź dokumentację MetaMask (opens in a new tab), aby uzyskać więcej informacji!
Jeśli window.ethereum nie jest obecne, oznacza to, że MetaMask nie jest zainstalowany. Powoduje to zwrócenie obiektu JSON, w którym zwrócony adres jest pustym ciągiem, a obiekt status JSX informuje, że użytkownik musi zainstalować MetaMask.
Teraz, jeśli window.ethereum jest obecne, to wtedy robi się ciekawie.
Używając pętli try/catch, spróbujemy połączyć się z MetaMask, wywołując window.ethereum.request({ method: "eth_requestAccounts" }); (opens in a new tab). Wywołanie tej funkcji otworzy MetaMask w przeglądarce, gdzie użytkownik zostanie poproszony o podłączenie swojego portfela do Twojej dapki.
- Jeśli użytkownik zdecyduje się połączyć,
method: "eth_requestAccounts"zwróci tablicę zawierającą wszystkie adresy kont użytkownika, które połączyły się z dappką. W sumie nasza funkcjaconnectWalletzwróci obiekt JSON, który zawiera pierwszyadresw tej tablicy (patrz linia 9) oraz wiadomośćstatus, która prosi użytkownika o napisanie wiadomości do inteligentnego kontraktu. - Jeśli użytkownik odrzuci połączenie, obiekt JSON będzie zawierał pusty ciąg dla zwróconego
adresuoraz komunikatstatus, który odzwierciedla, że użytkownik odrzucił połączenie.
Teraz, gdy napisaliśmy tę funkcję connectWallet, następnym krokiem jest wywołanie jej w naszym komponencie HelloWorld.js.
Dodaj funkcję connectWallet do swojego komponentu interfejsu użytkownika HelloWorld.js
Przejdź do funkcji connectWalletPressed w HelloWorld.js i zaktualizuj ją do następującej postaci:
1// HelloWorld.js23const connectWalletPressed = async () => {4 const walletResponse = await connectWallet()5 setStatus(walletResponse.status)6 setWallet(walletResponse.address)7}Zauważ, jak większość naszej funkcjonalności jest abstrahowana od naszego komponentu HelloWorld.js z pliku interact.js? Robimy tak, aby zachować zgodność z paradygmatem M-V-C!
W connectWalletPressed po prostu wykonujemy wywołanie await do naszej zaimportowanej funkcji connectWallet, a za pomocą jej odpowiedzi aktualizujemy nasze zmienne status i walletAddress za pomocą ich hooków stanu.
Teraz zapiszmy oba pliki (HelloWorld.js i interact.js) i przetestujmy nasz dotychczasowy interfejs użytkownika.
Otwórz przeglądarkę na stronie http://localhost:3000/ (opens in a new tab) i naciśnij przycisk „Połącz portfel” w prawym górnym rogu strony.
Jeśli masz zainstalowany MetaMask, powinieneś zostać poproszony o podłączenie swojego portfela do Twojej dapki. Zaakceptuj zaproszenie do połączenia.
Powinieneś zobaczyć, że przycisk portfela odzwierciedla teraz, że Twój adres jest połączony! Jeeee 🔥
Następnie spróbuj odświeżyć stronę... to jest dziwne. Nasz przycisk portfela prosi nas o podłączenie MetaMask, mimo że jest już podłączony...
Jednak nie ma się czego bać! Możemy łatwo to rozwiązać (zaadresować?) implementując getCurrentWalletConnected, który sprawdzi, czy adres jest już połączony z naszą dappką i odpowiednio zaktualizuje nasz interfejs użytkownika!
Funkcja getCurrentWalletConnected
Zaktualizuj swoją funkcję getCurrentWalletConnected w pliku interact.js do następującej postaci:
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: "👆🏽 Wpisz wiadomość w polu tekstowym powyżej.",13 }14 } else {15 return {16 address: "",17 status: "🦊 Połącz się z MetaMask za pomocą przycisku w prawym górnym rogu.",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 Musisz zainstalować MetaMask, wirtualny portfel Ethereum, w swojej35 przeglądarce.36 </a>37 </p>38 </span>39 ),40 }41 }42}Pokaż wszystkoTen kod jest bardzo podobny do funkcji connectWallet, którą właśnie napisaliśmy w poprzednim kroku.
Główna różnica polega na tym, że zamiast wywoływać metodę eth_requestAccounts, która otwiera MetaMask, aby użytkownik mógł połączyć swój portfel, tutaj wywołujemy metodę eth_accounts, która po prostu zwraca tablicę zawierającą adresy MetaMask aktualnie połączone z naszą dapką.
Aby zobaczyć tę funkcję w działaniu, wywołajmy ją w naszej funkcji useEffect naszego komponentu 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}, [])Pokaż wszystkoZauważ, że używamy odpowiedzi z naszego wywołania getCurrentWalletConnected, aby zaktualizować nasze zmienne stanu walletAddress i status.
Teraz, gdy dodałeś ten kod, spróbujmy odświeżyć okno przeglądarki.
Fajnieee! Przycisk powinien informować, że jesteś połączony i pokazywać podgląd adresu podłączonego portfela - nawet po odświeżeniu!
Zaimplementuj addWalletListener
Ostatnim krokiem w konfiguracji portfela naszej dapki jest zaimplementowanie nasłuchiwacza portfela, aby nasz interfejs użytkownika aktualizował się, gdy zmieni się stan naszego portfela, na przykład gdy użytkownik się rozłączy lub zmieni konto.
W pliku HelloWorld.js zmodyfikuj swoją funkcję addWalletListener w następujący sposób:
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("👆🏽 Wpisz wiadomość w polu tekstowym powyżej.")9 } else {10 setWallet("")11 setStatus("🦊 Połącz się z MetaMask za pomocą przycisku w prawym górnym rogu.")12 }13 })14 } else {15 setStatus(16 <p>17 {" "}18 🦊 <a target="_blank" href={`https://metamask.io/download`}>19 Musisz zainstalować MetaMask, wirtualny portfel Ethereum, w swojej przeglądarce.20 </a>21 </p>22 )23 }24}Pokaż wszystkoZałożę się, że w tym momencie nie potrzebujesz nawet naszej pomocy, aby zrozumieć, co się tutaj dzieje, ale dla porządku szybko to omówmy:
- Najpierw nasza funkcja sprawdza, czy
window.ethereumjest włączone (tj. MetaMask jest zainstalowany).- Jeśli nie jest, po prostu ustawiamy naszą zmienną stanu
statusna ciąg JSX, który prosi użytkownika o zainstalowanie MetaMask. - Jeśli jest włączone, ustawiamy nasłuchiwacz
window.ethereum.on("accountsChanged")w linii 3, który nasłuchuje zmian stanu w portfelu MetaMask, co obejmuje sytuacje, gdy użytkownik podłącza dodatkowe konto do dapki, zmienia konta lub odłącza konto. Jeśli co najmniej jedno konto jest połączone, zmienna stanuwalletAddressjest aktualizowana jako pierwsze konto w tablicykontazwróconej przez nasłuchiwacz. W przeciwnym raziewalletAddressjest ustawiany jako pusty ciąg.
- Jeśli nie jest, po prostu ustawiamy naszą zmienną stanu
Na koniec musimy wywołać ją w naszej funkcji 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}, [])Pokaż wszystkoI to wszystko! Pomyślnie ukończyliśmy programowanie całej naszej funkcjonalności portfela! Teraz przejdźmy do naszego ostatniego zadania: aktualizacji wiadomości zapisanej w naszym inteligentnym kontrakcie!
Krok 6: Zaimplementuj funkcję updateMessage
Dobra, ekipa, dotarliśmy do ostatniej prostej! W updateMessage w swoim pliku interact.js, zrobimy następujące rzeczy:
- Upewnij się, że wiadomość, którą chcemy opublikować w naszym inteligentnym kontakcie, jest ważna
- Podpisz naszą transakcję za pomocą MetaMask
- Wywołaj tę funkcję z naszego komponentu frontendowego
HelloWorld.js
To nie potrwa długo; dokończmy tę dappkę!
Obsługa błędów wejściowych
Naturalnie, sensowne jest posiadanie jakiejś formy obsługi błędów wejściowych na początku funkcji.
Będziemy chcieli, aby nasza funkcja zakończyła się wcześniej, jeśli nie ma zainstalowanego rozszerzenia MetaMask, nie jest podłączony portfel (tj. przekazany address jest pustym ciągiem znaków) lub message jest pustym ciągiem znaków. Dodajmy następującą obsługę błędów do updateMessage:
1// interact.js23export const updateMessage = async (address, message) => {4 if (!window.ethereum || address === null) {5 return {6 status:7 "💡 Połącz swój portfel MetaMask, aby zaktualizować wiadomość na blockchainie.",8 }9 }1011 if (message.trim() === "") {12 return {13 status: "❌ Twoja wiadomość nie może być pustym ciągiem znaków.",14 }15 }16}Pokaż wszystkoTeraz, gdy mamy właściwą obsługę błędów wejściowych, nadszedł czas, aby podpisać transakcję za pomocą MetaMask!
Podpisywanie naszej transakcji
Jeśli jesteś już zaznajomiony z tradycyjnymi transakcjami Ethereum web3, kod, który napiszemy dalej, będzie bardzo znajomy. Poniżej kodu obsługi błędów wejściowych dodaj następujący kod do updateMessage:
1// interact.js23// ustaw parametry transakcji4const transactionParameters = {5 to: contractAddress, // Wymagane z wyjątkiem publikacji kontraktów.6 from: address, // musi pasować do aktywnego adresu użytkownika.7 data: helloWorldContract.methods.update(message).encodeABI(),8}910// podpisz transakcję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 Zobacz status swojej transakcji na Etherscan!22 </a>23 <br />24 ℹ️ Gdy transakcja zostanie zweryfikowana przez sieć, wiadomość zostanie25 zaktualizowana automatycznie.26 </span>27 ),28 }29} catch (error) {30 return {31 status: "😥 " + error.message,32 }33}Pokaż wszystkoPrzeanalizujmy, co się dzieje. Najpierw ustawiamy parametry naszej transakcji, gdzie:
tookreśla adres odbiorcy (nasz inteligentny kontrakt)fromokreśla sygnatariusza transakcji, zmiennąaddress, którą przekazaliśmy do naszej funkcjidatazawiera wywołanie metodyupdatenaszego inteligentnego kontraktu Witaj Świecie, otrzymując jako dane wejściowe naszą zmienną ciągu znakówmessage
Następnie wykonujemy wywołanie await, window.ethereum.request, w którym prosimy MetaMask o podpisanie transakcji. Zauważ, że w liniach 11 i 12 określamy naszą metodę eth, eth_sendTransaction i przekazujemy nasze transactionParameters.
W tym momencie w przeglądarce otworzy się MetaMask i poprosi użytkownika o podpisanie lub odrzucenie transakcji.
- Jeśli transakcja się powiedzie, funkcja zwróci obiekt JSON, w którym ciąg JSX
statusprosi użytkownika o sprawdzenie Etherscan w celu uzyskania dalszych informacji o transakcji. - Jeśli transakcja się nie powiedzie, funkcja zwróci obiekt JSON, w którym ciąg znaków
statusprzekaże komunikat o błędzie.
W sumie nasza funkcja updateMessage powinna wyglądać tak:
1// interact.js23export const updateMessage = async (address, message) => {4 // obsługa błędów wejściowych5 if (!window.ethereum || address === null) {6 return {7 status:8 "💡 Połącz swój portfel MetaMask, aby zaktualizować wiadomość na blockchainie.",9 }10 }1112 if (message.trim() === "") {13 return {14 status: "❌ Twoja wiadomość nie może być pustym ciągiem znaków.",15 }16 }1718 // ustaw parametry transakcji19 const transactionParameters = {20 to: contractAddress, // Wymagane z wyjątkiem publikacji kontraktów.21 from: address, // musi pasować do aktywnego adresu użytkownika.22 data: helloWorldContract.methods.update(message).encodeABI(),23 }2425 // podpisz transakcję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 Zobacz status swojej transakcji na Etherscan!37 </a>38 <br />39 ℹ️ Gdy transakcja zostanie zweryfikowana przez sieć, wiadomość zostanie40 zaktualizowana automatycznie.41 </span>42 ),43 }44 } catch (error) {45 return {46 status: "😥 " + error.message,47 }48 }49}Pokaż wszystkoNa koniec musimy połączyć naszą funkcję updateMessage z naszym komponentem HelloWorld.js.
Połącz updateMessage z frontendem HelloWorld.js
Nasza funkcja onUpdatePressed powinna wykonać wywołanie await do zaimportowanej funkcji updateMessage i zmodyfikować zmienną stanu status, aby odzwierciedlić, czy nasza transakcja się powiodła, czy nie:
1// HelloWorld.js23const onUpdatePressed = async () => {4 const { status } = await updateMessage(walletAddress, newMessage)5 setStatus(status)6}To super czyste i proste. I zgadnij co... TWOJA DAPPKA JEST UKOŃCZONA!!!
Śmiało przetestuj przycisk Aktualizuj!
Stwórz swoją własną dappkę
Hura, dotarłeś do końca samouczka! Podsumowując, nauczyłeś się, jak:
- Podłącz portfel MetaMask do swojego projektu dapp
- Odczytaj dane ze swojego inteligentnego kontraktu za pomocą interfejsu API Alchemy Web3 (opens in a new tab)
- Podpisz transakcje Ethereum za pomocą MetaMask
Teraz jesteś w pełni wyposażony, aby zastosować umiejętności z tego samouczka do zbudowania własnego, niestandardowego projektu dapp! Jak zawsze, jeśli masz jakieś pytania, nie wahaj się skontaktować z nami w celu uzyskania pomocy na Discordzie Alchemy (opens in a new tab). 🧙♂️
Po ukończeniu tego samouczka daj nam znać, jak Ci poszło lub czy masz jakieś uwagi, oznaczając nas na Twitterze @alchemyplatform (opens in a new tab)!
Strona ostatnio zaktualizowana: 26 lutego 2026





