Wprowadzenie
Jest to druga część naszego praktycznego mini kursu Web3 w której budujemy zdecentralizowaną aplikację wyświetlającą wiadomości od użytkowników, którzy za taką możliwość zapłacili tokenami. W pierwszym artykule stworzyliśmy smart kontrakt w języku Solidity, który został opublikowany w sieci testowej Ethereum Goerli. Dodatkowo skonfigurowaliśmy portfel kryptowalut MetaMask i utworzyliśmy konto deweloperskie Alchemy. Są to niezbędne kroki, które należy wykonać przed przystąpieniem do tego artykułu.
W aktualnej części frontendowej zbudujemy interfejs graficzny nawiązujący interakcję ze smart kontraktem w sieci blockchain. W tym przypadku smart kontrakt możemy traktować jak backend tradycyjnej aplikacji internetowej.
Stos technologiczny
- React – Biblioteka JavaScript wykorzystywana do tworzenia interfejsów użytkownika
- TypeScript – Język programowania oparty na JavaScript
- Ethers.js – Biblioteka komunikująca się z blockchainem Ethereum
- Mui – Biblioteka gotowych komponentów UI zgodna z wytycznymi Google
Cały kod który będziemy pisać jest dostępny w naszym repozytorium GitHub. Jeśli nie masz ochoty przechodzić krok po kroku przez proces tworzenia aplikacji, wystarczy sklonować repo i przeczytać pliki README.md:
git clone https://github.com/Chainkraft/message.git
Wymagania
Jeśli przeszedłeś przez wszystkie kroki pierwszej części naszego kursu Web3 to poniższą listę wymagań możesz pominąć.
- Node.js w wersji >= 14
- Portfel Ethereum
- Testowe tokeny sieci Ethereum Goerli
- Konto deweloperske Alchemy
Konfiguracja środowiska
Create-react-app
Aplikację frontendową skonfigurujemy za pomocą jednej komendy z wykorzystaniem narzędzia Create React App. Dzięki temu bardziej skupimy się na rozwiązaniu problemu niż na konfigurowaniu Reacta i różnych zależności.
npx create-react-app message-frontend --template typescript
Create-react-app
skonfiguruje automatycznie takie zależności jak: Webpack, Babel, ESLint, TypeScript. Jeżeli chcemy mieć bezpośrednią kontrolę nad wszystkim co się dzieje, możemy całkowicie odpiąć narzędzie za pomocą komendy npm run eject
. Komenda przeniesie zależności, konfiguracje oraz skrypty bezpośrednio do naszego projektu (proces nieodwracalny).
Przechodzimy do folderu z wygenerowanym projektem i uruchamiamy aplikację w trybie deweloperskim.
cd message-frontend
npm start
Domyślna aplikacja powinna być dostępna pod adresem http://localhost:3000. Strona zostanie automatycznie odświeżona, jeśli dokonamy zmian w plikach. Ewentualne błędy będą logowane do konsoli deweloperskiej przeglądarki (F12).
Ethers.js
Dodajemy bibliotekę do komunikacji z siecią blockchain:
npm install ethers @typechain/ethers-v5
Material UI
Biblioteka MUI pozwoli nam szybko zbudować interfejs graficzny za pomocą gotowych komponentów.
npm install @mui/material @emotion/react @emotion/styled @mui/icons-material
dotenv
Create-react-app domyślnie dodaje zależność biblioteki dotenv którą poznaliśmy w części związanej ze smart kontraktem. Tworzymy plik .env
w głównym folderze projektu i uzupełniamy zmienne środowiskowe podając wartości z poprzedniego artykułu. Nowością jest zmienna REACT_APP_NETWORK
, gdzie wpisujemy nazwę sieci testowej goerli
.
REACT_APP_NETWORK = "goerli" // Nazwa testowej sieci Ethereum
REACT_APP_API_KEY = "<secret>" // Alchemy API Key
REACT_APP_API_URL = "<secret>" // Alchemy API Url
REACT_APP_PRIVATE_KEY = "<secret>" // Klucz prywatny portfela MetaMask
REACT_APP_CONTRACT_ADDRESS = "<secret>" // Adres naszego smart kontraktu
Tworzenie interfejsu graficznego
Na początek skonfigurujemy w projekcie funkcje pomocnicze, które ułatwią interakcję ze smart kontraktem oraz utworzymy plik opisujący wygląd smart kontraktu, tzw. ABI.
Tworzymy folder src/config
, a w nim plik Blockchain.ts
. Plik zawiera pomocnicze zmienne oraz funkcje, które ułatwią tworzenie instancji klasy Provider
oraz Contract
.
import {ethers} from "ethers";
import MESSAGE_ABI from "./Message.abi.json"; // za chwilę dodamy plik ABI
import {Provider} from "@ethersproject/abstract-provider";
// Provider to klasa która jest warstwą abstrakcji dla połączenia z siecią Ethereum
// Provider Alchemy pozwala na dostęp do sieci Blockchain bez konieczności posiadania osobistego portfela.
// Dzięki temu, użytkownik który nie posiada np. MetaMask zobaczy aktualną wiadomość po wejściu na stronę.
const getAlchemyProvider = () => new ethers.providers.AlchemyProvider(
process.env.REACT_APP_NETWORK,
process.env.REACT_APP_API_KEY
);
// Provider Web3Provider wykorzystuje plugin przeglądarki MetaMask do połączenia z siecią Ethereum.
// Zmienna window.ethereum jest dostępna jeśli przeglądarka posiada zainstalowany plugin portfela.
const getWalletProvider = () => new ethers.providers.Web3Provider(window.ethereum)
// Contract to klasa która jest warstwą abstrakcji smart kontraktu w sieci Ethereum.
// Dzięki temu możemy komunikować się ze smart kontraktem jak ze zwykłym obiektem JavaScript.
const getMessageContract = (provider: Provider) =>
new ethers.Contract(
process.env.REACT_APP_CONTRACT_ADDRESS, // adres naszego smart kontraktu
MESSAGE_ABI.abi, // ABI kontraktu z poprzedniego artykułu
provider // provider jako dynamiczny parametr funkcji
);
export {getAlchemyProvider, getWalletProvider, getMessageContract};
Aby umożliwić interakcję instancji klasy Contract z naszym smart kontraktem w sieci blockchain musimy przekazać informację jak wygląda nasz kontrakt za pomocą tzw. ABI (Application Binary Interface). W poprzednim artykule zbudowaliśmy smart kontrakt Solidity. Jego plik ABI znajdziemy w tamtym projekcie w lokalizacji: /artifacts/contracts/Message.sol/Message.json
(po zbudowaniu lub opublikowaniu kontraktu). Tworzymy plik Message.abi.json
w folderze src/config
i kopiujemy zawartość pliku Message.json
:
{
"_format": "hh-sol-artifact-1",
"contractName": "Message",
"sourceName": "contracts/Message.sol",
"abi": [
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "oldFeeRate",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "newFeeRate",
"type": "uint256"
}
],
"name": "FeeRateSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "string",
"name": "message",
"type": "string"
},
{
"indexed": true,
"internalType": "address",
"name": "author",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "price",
"type": "uint256"
}
],
"name": "MessageSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "oldOwner",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "fee",
"type": "uint256"
}
],
"name": "Withdrawal",
"type": "event"
},
{
"inputs": [],
"name": "author",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_address",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "feeParts",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "feeRate",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "message",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "price",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_feeRate",
"type": "uint256"
}
],
"name": "setFeeRate",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "_message",
"type": "string"
}
],
"name": "setMessage",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_owner",
"type": "address"
}
],
"name": "setOwner",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "withdraw",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
],
"bytecode": "0x60806040523480156200001157600080fd5b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506040518060400160405280600b81526020017f48656c6c6f20776f726c6400000000000000000000000000000000000000000081525060049080519060200190620000e0929190620000e7565b50620001fc565b828054620000f59062000197565b90600052602060002090601f01602090048101928262000119576000855562000165565b82601f106200013457805160ff191683800117855562000165565b8280016001018555821562000165579182015b828111156200016457825182559160200191906001019062000147565b5b50905062000174919062000178565b5090565b5b808211156200019357600081600090555060010162000179565b5090565b60006002820490506001821680620001b057607f821691505b60208210811415620001c757620001c6620001cd565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b611221806200020c6000396000f3fe60806040526004361061009c5760003560e01c806370a082311161006457806370a08231146101515780638da5cb5b1461018e578063978bbdb9146101b9578063a035b1fe146101e4578063a6c3e6b91461020f578063e21f37ce1461023a5761009c565b806313af4035146100a1578063368b8772146100ca5780633ccfd60b146100e657806345596e2e146100fd57806357526b3f14610126575b600080fd5b3480156100ad57600080fd5b506100c860048036038101906100c39190610aff565b610265565b005b6100e460048036038101906100df9190610b28565b6103b6565b005b3480156100f257600080fd5b506100fb610547565b005b34801561010957600080fd5b50610124600480360381019061011f9190610b6d565b61075d565b005b34801561013257600080fd5b5061013b61087d565b6040516101489190610ee9565b60405180910390f35b34801561015d57600080fd5b5061017860048036038101906101739190610aff565b610883565b6040516101859190610ee9565b60405180910390f35b34801561019a57600080fd5b506101a36108cc565b6040516101b09190610d9c565b60405180910390f35b3480156101c557600080fd5b506101ce6108f0565b6040516101db9190610ee9565b60405180910390f35b3480156101f057600080fd5b506101f96108f6565b6040516102069190610ee9565b60405180910390f35b34801561021b57600080fd5b506102246108fc565b6040516102319190610d9c565b60405180910390f35b34801561024657600080fd5b5061024f610922565b60405161025c9190610e17565b60405180910390f35b3373ffffffffffffffffffffffffffffffffffffffff1660008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146102f3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102ea90610e89565b60405180910390fd5b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08160008054906101000a900473ffffffffffffffffffffffffffffffffffffffff166040516103aa929190610db7565b60405180910390a15050565b60035434116103fa576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103f190610e69565b60405180910390fd5b60035460056000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461046d9190610f69565b925050819055503460038190555033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508181600491906104cd9291906109e8565b50600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f6c33a7aaf998952608f89344f6a3cbf2b2ffbb5b1ce184ef16e84e499a1f817a600460035460405161053b929190610e39565b60405180910390a25050565b6000600560003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490506000610596826109b0565b9050600081836105a6919061104a565b90506000600560003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550600060025411156106705781600560008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546106689190610f69565b925050819055505b60003373ffffffffffffffffffffffffffffffffffffffff168260405161069690610d87565b60006040518083038185875af1925050503d80600081146106d3576040519150601f19603f3d011682016040523d82523d6000602084013e6106d8565b606091505b505090508061071c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161071390610ea9565b60405180910390fd5b7fdf273cb619d95419a9cd0ec88123a0538c85064229baa6363788f743fff90deb33838560405161074f93929190610de0565b60405180910390a150505050565b3373ffffffffffffffffffffffffffffffffffffffff1660008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146107eb576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107e290610e89565b60405180910390fd5b612710811115610830576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161082790610ec9565b60405180910390fd5b60006002549050816002819055507f959ec4191db1bd972bfbc60dc7d735d4cfb897ca3fd297f4ebd6ee918feb84d481600254604051610871929190610f04565b60405180910390a15050565b61271081565b6000600560008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60025481565b60035481565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6004805461092f906110ed565b80601f016020809104026020016040519081016040528092919081815260200182805461095b906110ed565b80156109a85780601f1061097d576101008083540402835291602001916109a8565b820191906000526020600020905b81548152906001019060200180831161098b57829003601f168201915b505050505081565b60008060025411156109de57612710600254836109cd9190610ff0565b6109d79190610fbf565b90506109e3565b600090505b919050565b8280546109f4906110ed565b90600052602060002090601f016020900481019282610a165760008555610a5d565b82601f10610a2f57803560ff1916838001178555610a5d565b82800160010185558215610a5d579182015b82811115610a5c578235825591602001919060010190610a41565b5b509050610a6a9190610a6e565b5090565b5b80821115610a87576000816000905550600101610a6f565b5090565b600081359050610a9a816111bd565b92915050565b60008083601f840112610ab257600080fd5b8235905067ffffffffffffffff811115610acb57600080fd5b602083019150836001820283011115610ae357600080fd5b9250929050565b600081359050610af9816111d4565b92915050565b600060208284031215610b1157600080fd5b6000610b1f84828501610a8b565b91505092915050565b60008060208385031215610b3b57600080fd5b600083013567ffffffffffffffff811115610b5557600080fd5b610b6185828601610aa0565b92509250509250929050565b600060208284031215610b7f57600080fd5b6000610b8d84828501610aea565b91505092915050565b610b9f8161107e565b82525050565b6000610bb082610f42565b610bba8185610f58565b9350610bca8185602086016110ba565b610bd3816111ac565b840191505092915050565b60008154610beb816110ed565b610bf58186610f58565b94506001821660008114610c105760018114610c2257610c55565b60ff1983168652602086019350610c55565b610c2b85610f2d565b60005b83811015610c4d57815481890152600182019150602081019050610c2e565b808801955050505b50505092915050565b6000610c6b601083610f58565b91507f4e6f7420656e6f756768206574686572000000000000000000000000000000006000830152602082019050919050565b6000610cab600a83610f58565b91507f4f6e6c79206f776e6572000000000000000000000000000000000000000000006000830152602082019050919050565b6000610ceb601783610f58565b91507f4661696c75726521204574686572206e6f742073656e740000000000000000006000830152602082019050919050565b6000610d2b600083610f4d565b9150600082019050919050565b6000610d45601383610f58565b91507f46656520726174652061626f76652031303025000000000000000000000000006000830152602082019050919050565b610d81816110b0565b82525050565b6000610d9282610d1e565b9150819050919050565b6000602082019050610db16000830184610b96565b92915050565b6000604082019050610dcc6000830185610b96565b610dd96020830184610b96565b9392505050565b6000606082019050610df56000830186610b96565b610e026020830185610d78565b610e0f6040830184610d78565b949350505050565b60006020820190508181036000830152610e318184610ba5565b905092915050565b60006040820190508181036000830152610e538185610bde565b9050610e626020830184610d78565b9392505050565b60006020820190508181036000830152610e8281610c5e565b9050919050565b60006020820190508181036000830152610ea281610c9e565b9050919050565b60006020820190508181036000830152610ec281610cde565b9050919050565b60006020820190508181036000830152610ee281610d38565b9050919050565b6000602082019050610efe6000830184610d78565b92915050565b6000604082019050610f196000830185610d78565b610f266020830184610d78565b9392505050565b60008190508160005260206000209050919050565b600081519050919050565b600081905092915050565b600082825260208201905092915050565b6000610f74826110b0565b9150610f7f836110b0565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610fb457610fb361111f565b5b828201905092915050565b6000610fca826110b0565b9150610fd5836110b0565b925082610fe557610fe461114e565b5b828204905092915050565b6000610ffb826110b0565b9150611006836110b0565b9250817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048311821515161561103f5761103e61111f565b5b828202905092915050565b6000611055826110b0565b9150611060836110b0565b9250828210156110735761107261111f565b5b828203905092915050565b600061108982611090565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60005b838110156110d85780820151818401526020810190506110bd565b838111156110e7576000848401525b50505050565b6000600282049050600182168061110557607f821691505b602082108114156111195761111861117d565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000601f19601f8301169050919050565b6111c68161107e565b81146111d157600080fd5b50565b6111dd816110b0565b81146111e857600080fd5b5056fea26469706673582212206dabf31eb1621c76473da86b9848d45ff4743cc12466351578a2669ab9f3cf6d64736f6c63430008000033",
"deployedBytecode": "0x60806040526004361061009c5760003560e01c806370a082311161006457806370a08231146101515780638da5cb5b1461018e578063978bbdb9146101b9578063a035b1fe146101e4578063a6c3e6b91461020f578063e21f37ce1461023a5761009c565b806313af4035146100a1578063368b8772146100ca5780633ccfd60b146100e657806345596e2e146100fd57806357526b3f14610126575b600080fd5b3480156100ad57600080fd5b506100c860048036038101906100c39190610aff565b610265565b005b6100e460048036038101906100df9190610b28565b6103b6565b005b3480156100f257600080fd5b506100fb610547565b005b34801561010957600080fd5b50610124600480360381019061011f9190610b6d565b61075d565b005b34801561013257600080fd5b5061013b61087d565b6040516101489190610ee9565b60405180910390f35b34801561015d57600080fd5b5061017860048036038101906101739190610aff565b610883565b6040516101859190610ee9565b60405180910390f35b34801561019a57600080fd5b506101a36108cc565b6040516101b09190610d9c565b60405180910390f35b3480156101c557600080fd5b506101ce6108f0565b6040516101db9190610ee9565b60405180910390f35b3480156101f057600080fd5b506101f96108f6565b6040516102069190610ee9565b60405180910390f35b34801561021b57600080fd5b506102246108fc565b6040516102319190610d9c565b60405180910390f35b34801561024657600080fd5b5061024f610922565b60405161025c9190610e17565b60405180910390f35b3373ffffffffffffffffffffffffffffffffffffffff1660008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146102f3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102ea90610e89565b60405180910390fd5b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08160008054906101000a900473ffffffffffffffffffffffffffffffffffffffff166040516103aa929190610db7565b60405180910390a15050565b60035434116103fa576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103f190610e69565b60405180910390fd5b60035460056000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461046d9190610f69565b925050819055503460038190555033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508181600491906104cd9291906109e8565b50600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f6c33a7aaf998952608f89344f6a3cbf2b2ffbb5b1ce184ef16e84e499a1f817a600460035460405161053b929190610e39565b60405180910390a25050565b6000600560003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490506000610596826109b0565b9050600081836105a6919061104a565b90506000600560003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550600060025411156106705781600560008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546106689190610f69565b925050819055505b60003373ffffffffffffffffffffffffffffffffffffffff168260405161069690610d87565b60006040518083038185875af1925050503d80600081146106d3576040519150601f19603f3d011682016040523d82523d6000602084013e6106d8565b606091505b505090508061071c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161071390610ea9565b60405180910390fd5b7fdf273cb619d95419a9cd0ec88123a0538c85064229baa6363788f743fff90deb33838560405161074f93929190610de0565b60405180910390a150505050565b3373ffffffffffffffffffffffffffffffffffffffff1660008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146107eb576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107e290610e89565b60405180910390fd5b612710811115610830576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161082790610ec9565b60405180910390fd5b60006002549050816002819055507f959ec4191db1bd972bfbc60dc7d735d4cfb897ca3fd297f4ebd6ee918feb84d481600254604051610871929190610f04565b60405180910390a15050565b61271081565b6000600560008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60025481565b60035481565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6004805461092f906110ed565b80601f016020809104026020016040519081016040528092919081815260200182805461095b906110ed565b80156109a85780601f1061097d576101008083540402835291602001916109a8565b820191906000526020600020905b81548152906001019060200180831161098b57829003601f168201915b505050505081565b60008060025411156109de57612710600254836109cd9190610ff0565b6109d79190610fbf565b90506109e3565b600090505b919050565b8280546109f4906110ed565b90600052602060002090601f016020900481019282610a165760008555610a5d565b82601f10610a2f57803560ff1916838001178555610a5d565b82800160010185558215610a5d579182015b82811115610a5c578235825591602001919060010190610a41565b5b509050610a6a9190610a6e565b5090565b5b80821115610a87576000816000905550600101610a6f565b5090565b600081359050610a9a816111bd565b92915050565b60008083601f840112610ab257600080fd5b8235905067ffffffffffffffff811115610acb57600080fd5b602083019150836001820283011115610ae357600080fd5b9250929050565b600081359050610af9816111d4565b92915050565b600060208284031215610b1157600080fd5b6000610b1f84828501610a8b565b91505092915050565b60008060208385031215610b3b57600080fd5b600083013567ffffffffffffffff811115610b5557600080fd5b610b6185828601610aa0565b92509250509250929050565b600060208284031215610b7f57600080fd5b6000610b8d84828501610aea565b91505092915050565b610b9f8161107e565b82525050565b6000610bb082610f42565b610bba8185610f58565b9350610bca8185602086016110ba565b610bd3816111ac565b840191505092915050565b60008154610beb816110ed565b610bf58186610f58565b94506001821660008114610c105760018114610c2257610c55565b60ff1983168652602086019350610c55565b610c2b85610f2d565b60005b83811015610c4d57815481890152600182019150602081019050610c2e565b808801955050505b50505092915050565b6000610c6b601083610f58565b91507f4e6f7420656e6f756768206574686572000000000000000000000000000000006000830152602082019050919050565b6000610cab600a83610f58565b91507f4f6e6c79206f776e6572000000000000000000000000000000000000000000006000830152602082019050919050565b6000610ceb601783610f58565b91507f4661696c75726521204574686572206e6f742073656e740000000000000000006000830152602082019050919050565b6000610d2b600083610f4d565b9150600082019050919050565b6000610d45601383610f58565b91507f46656520726174652061626f76652031303025000000000000000000000000006000830152602082019050919050565b610d81816110b0565b82525050565b6000610d9282610d1e565b9150819050919050565b6000602082019050610db16000830184610b96565b92915050565b6000604082019050610dcc6000830185610b96565b610dd96020830184610b96565b9392505050565b6000606082019050610df56000830186610b96565b610e026020830185610d78565b610e0f6040830184610d78565b949350505050565b60006020820190508181036000830152610e318184610ba5565b905092915050565b60006040820190508181036000830152610e538185610bde565b9050610e626020830184610d78565b9392505050565b60006020820190508181036000830152610e8281610c5e565b9050919050565b60006020820190508181036000830152610ea281610c9e565b9050919050565b60006020820190508181036000830152610ec281610cde565b9050919050565b60006020820190508181036000830152610ee281610d38565b9050919050565b6000602082019050610efe6000830184610d78565b92915050565b6000604082019050610f196000830185610d78565b610f266020830184610d78565b9392505050565b60008190508160005260206000209050919050565b600081519050919050565b600081905092915050565b600082825260208201905092915050565b6000610f74826110b0565b9150610f7f836110b0565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610fb457610fb361111f565b5b828201905092915050565b6000610fca826110b0565b9150610fd5836110b0565b925082610fe557610fe461114e565b5b828204905092915050565b6000610ffb826110b0565b9150611006836110b0565b9250817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048311821515161561103f5761103e61111f565b5b828202905092915050565b6000611055826110b0565b9150611060836110b0565b9250828210156110735761107261111f565b5b828203905092915050565b600061108982611090565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60005b838110156110d85780820151818401526020810190506110bd565b838111156110e7576000848401525b50505050565b6000600282049050600182168061110557607f821691505b602082108114156111195761111861117d565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000601f19601f8301169050919050565b6111c68161107e565b81146111d157600080fd5b50565b6111dd816110b0565b81146111e857600080fd5b5056fea26469706673582212206dabf31eb1621c76473da86b9848d45ff4743cc12466351578a2669ab9f3cf6d64736f6c63430008000033",
"linkReferences": {},
"deployedLinkReferences": {}
}
W pliku src/config/Blockchain.ts
korzystamy ze zmiennej window.ethereum
, aby dobrać się do obiektu portfela MetaMask. Zmienna ta nie jest oficjalnie dodana do interfejsu Window. Z tego powodu IDE (np. Visual Studio, IntelliJ) mogą wyświetlać błędy. Aby temu zaradzić rozszerzymy interfejs o brakujący typ, przy okazji dodając również typy naszych zmiennych środowiskowych. Tworzymy folder src/types
, a w nim plik: index.d.ts
:
import { MetaMaskInpageProvider } from "@metamask/providers";
declare global {
interface Window {
ethereum?: MetaMaskInpageProvider;
}
namespace NodeJS {
interface ProcessEnv {
REACT_APP_NETWORK: string;
REACT_APP_API_KEY: string;
REACT_APP_API_URL: string;
REACT_APP_PRIVATE_KEY: string;
REACT_APP_CONTRACT_ADDRESS: string;
}
}
}
Następnie przejdziemy do tworzenia właściwych komponentów graficznych w React. Komentarze do kodu HTML pominiemy. Staraliśmy się maksymalnie uprościć aplikację.
Tworzymy folder src/components
, a w nim plik DisplayMessage.tsx
zawierający kod:
import React, {useEffect, useState} from "react";
import {Backdrop, CircularProgress, Container, Typography,} from "@mui/material";
import {getAlchemyProvider, getMessageContract} from "../config/Blockchain";
// Definiujemy komponent typu funkcyjnego
const DisplayMessage = () => {
// Zmienna stanowa przechowująca aktualną wiadomość ze smart kontraktu
const [message, setMessage] = useState("");
// Zmienna stanowa obsługująca pasek ładowania podczas czytania danych z sieci blockchain
const [loading, setLoading] = useState(true);
// Tzw. Hook, który odpali się raz po wyrenderowaniu komponentu.
useEffect(() => {
// Tworzymy instancję connectora naszego smart kontraktu
// Providerem jest Alchemy, który umożliwia dostęp read-only do danych blockchain (nie potrzebujemy portfela)
const contract = getMessageContract(getAlchemyProvider());
// Jest to pierwsze pobranie aktualnej wiadomości zaraz po wejściu użytkownika na stronę
// Wywołujemy funkcję message() smart kontraktu i wynik przypisujemy do zmiennej stanowej
contract.message().then((newMessage: string) => {
setMessage(newMessage);
setLoading(false);
}).catch(console.error);
// Jeśli w między czasie zostanie opublikowana nowa wiadomość to zostanie ona również wyświetlona
// Tworzymy słuchacza zdarzeń, który będzie monitorował wywołanie zdarzenia MessageSet w kontrakcie
contract.on("MessageSet", (newMessage: string) => {
setMessage(newMessage);
});
}, []);
return (
<div>
{loading ? <Loader/> : null}
<Container
sx={{
height: "100vh",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}>
<Typography variant="h1" align="center" color="#f9a822">
{message}
</Typography>
</Container>
</div>
);
};
const Loader = () => {
return (
<Backdrop open={true}>
<CircularProgress size="5em"/>
</Backdrop>
);
};
export default DisplayMessage;
Skoro mamy już zaimplementowane wyświetlanie wiadomości, to musimy jeszcze obsłużyć dodanie nowej wiadomości z poziomu aplikacji.
Tworzymy nowy plik AddMessage.tsx
w folderze src/components
z poniższym kodem:
import React, {useState} from "react";
import {BigNumber, ethers} from "ethers";
import Button from "@mui/material/Button";
import AddIcon from "@mui/icons-material/Add";
import {Alert, CircularProgress, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Fab, FormControl, Input, InputLabel, Snackbar} from "@mui/material";
import {getMessageContract, getWalletProvider} from "../config/Blockchain";
const AddMessage = () => {
// Zmienna stanowa "form" to obiekt przechowujący różne pola formularza
const [form, setForm] = useState({
minPrice: BigNumber.from(0), // Minimalna cena do zapłaty, aby opublikować nową wiadomość
price: BigNumber.from(0), // Cena nowej wiadomości
message: "", // Nowa wiadomość
error: "" // Ewentualne błędy zwracane przez smart kontrakt
});
// Zmienna stanowa obsługująca pasek ładowania podczas czytania danych z sieci blockchain
const [loading, setLoading] = useState(false);
// Zmienna stanowa obsługująca pasek ładowania podczas publikowania nowej wiadomości do blockchain
const [processingTx, setProcessingTx] = useState(false);
// Zmienna stanowa obsługująca otwieranie okienka popup z formularzem
const [showDialog, setShowDialog] = useState(false);
// Zmienna stanowa obsługująca przypadek, gdy użytkownik nie ma pluginu MetaMask
const [walletError, setWalletError] = useState(false);
// Funkcja obsługująca otwieranie okna popup z formularzem dodawania nowej wiadomości
const handleDialogOpen = async () => {
// Sprawdzamy czy użytkownik ma zainstalowany plugin MetaMask
// Jeśli nie, wyświetlimy błąd na stronie i użytkownik nie doda wiadomości
if(window.ethereum === undefined) {
setWalletError(true);
return;
}
setLoading(true);
// Pobieramy Provider MetaMask
const provider = getWalletProvider();
// Wywołanie metody eth_requestAccounts portfela MetaMask otworzy plugin w przeglądarce
// użytkownika i poprosi o potwierdzenie dostępu.
// Możemy to porównać do opcji "Zaloguj za pomocą MetaMask" uzyskując dostęp do adresu portfela.
await provider.send("eth_requestAccounts", []);
// Tworzymy instancję connectora naszego smart kontraktu
// Tym razem providerem jest portfel MetaMask, którym będziemy podpisywać transakcje
const contract = getMessageContract(provider);
// Aktualizujemy zmienne stanowe
setForm({
...form,
// Pobieramy informację o "cenie" aktualnej wiadomości
// Użytkownik musi zapłacić więcej, jeśli chce dodać swoją wiadomość
minPrice: await contract.price()
});
setLoading(false);
setShowDialog(true);
};
// Funkcja obsługująca wysyłanie nowej wiadomości do smart kontraktu
const handlePublish = async () => {
// Blokujemy przycisk dodawania na czas przetwarzania transakcji
setProcessingTx(true);
// Pobieramy Provider MetaMask
const provider = getWalletProvider();
// Pobieramy adres portfela użytkownika, którym będziemy podpisywać transakcję blockchain
const signer = provider.getSigner();
// Tworzymy instancję connectora smart kontraktu połączonego z portfelem MetaMask (dostęp read-only)
const contract = getMessageContract(provider);
// Łączymy kontrakt z Signer dzięki czemu będziemy mogli podpisywać transakcje (dostęp read-write)
const contractWithSigner = contract.connect(signer);
// Wywołujemy funkcję setMessage smart kontraktu przekazując jako parametry nową wiadomość
// oraz zadaną ilość tokenów z konta Signer
await contractWithSigner.setMessage(form.message, {value: form.price})
.then(handleDialogClose)
.catch((error: any) => setForm({...form, error: error.error.message}))
.finally(() => setProcessingTx(false));
};
// Funkcja obsługująca zmianę wiadomości w formularzu
const handleMessageChange = (e: React.ChangeEvent<HTMLInputElement>) =>
// Ustawiamy zmienną stanową z nową wiadomością
setForm({...form, message: e.target.value});
// Funkcja obsługująca zmianę ceny w formularzu
const handlePriceChange = (e: React.ChangeEvent<HTMLInputElement>) =>
// Ustawiamy zmienną stanową z ceną w jednostce WEI
setForm({...form, price: ethers.utils.parseEther(e.target.value)});
// Funkcja zamykająca okno popup
const handleDialogClose = () => setShowDialog(false);
return (
<div>
<Fab
onClick={handleDialogOpen}
color="primary"
aria-label="add"
sx={{
position: "absolute",
bottom: 20,
right: 20,
}}>
{!loading && <AddIcon/>}
{loading && <CircularProgress sx={{color: "red"}}/>}
</Fab>
<Dialog open={showDialog} onClose={handleDialogClose}>
<DialogTitle>Publish message</DialogTitle>
<DialogContent>
{form.error && (
<Alert severity="error" sx={{marginBottom: 2}}>
{form.error}
</Alert>
)}
<DialogContentText>
You need to pay more than {ethers.utils.formatEther(form.minPrice)} ETH to outbid the current message.
</DialogContentText>
<FormControl fullWidth variant="standard" margin="normal">
<InputLabel htmlFor="standard-adornment-amount" required>
The message
</InputLabel>
<Input
autoFocus
required
type="text"
value={form.message}
onChange={handleMessageChange} />
</FormControl>
<FormControl fullWidth variant="standard" margin="normal">
<InputLabel htmlFor="standard-adornment-amount" required>
Price
</InputLabel>
<Input
required
type="number"
value={ethers.utils.formatEther(form.price)}
onChange={handlePriceChange} />
</FormControl>
</DialogContent>
<DialogActions>
<Button onClick={handlePublish} disabled={processingTx}>
{!processingTx && "Publish"}
{processingTx && "Publishing..."}
</Button>
</DialogActions>
</Dialog>
<Snackbar open={walletError} autoHideDuration={6000}>
<Alert severity="error">
Install MetaMask plugin to add a message
</Alert>
</Snackbar>
</div>
);
};
export default AddMessage;
Na koniec musimy wyświetlić nasze dwa komponenty w głównym komponencie App
oraz dodać drobne zmiany kosmetyczne, aby stworzyć ładny interfejs graficzny wykorzystujący MUI.
Modyfikujemy plik src/App.tsx
:
import React from "react";
import DisplayMessage from "./components/DisplayMessage";
import AddMessage from "./components/AddMessage";
import {Container, createTheme, CssBaseline, ThemeProvider} from "@mui/material";
function App() {
const theme = createTheme({
palette: {
primary: {
main: "#f9a822",
},
background: {
default: "#1a1a2e",
},
},
});
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<Container
style={{
width: "100vw",
height: "100vh",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}>
<DisplayMessage></DisplayMessage>
<AddMessage></AddMessage>
</Container>
</ThemeProvider>
);
}
export default App;
Modyfikujemy plik public/index.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="Demo application providing smooth dive into Web3 application development" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"/>
<title>The message</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
Przeszliśmy przez wszystkie niezbędne kroki, aby poprawnie wyświetlić aktualną wiadomość ze smart kontraktu oraz dodać nową (jeśli mamy wystarczające środki na koncie naszego krypto portfela). Jeżeli nie posiadacie wystarczającej ilości tokenów testowych ETH w sieci Goerli, możecie doładować portfel z przykładowego faucetu.
npm start
Ostatecznie powinniśmy zobaczyć piękną aplikację z odczytaną wiadomością bezpośrednio ze smart kontraktu. Po kliknięciu w przycisk +
możemy przesłać nową wiadomość, która zostanie wyświetlona automatycznie po dodaniu bloku z naszą transakcją do blockchaina (do kilkudziesięciu sekund w sieci Goerli).
Podsumowanie
Zbudowany interfejs graficzny nie wykorzystuje wszystkich mechanizmów zaimplementowanych w smart kontrakcie. Brakuje m.in zwracania środków do autorów starych wiadomości. Jeśli czujesz się na siłach zapraszamy do forkowania naszego projektu na GitHub!
Jest to ostatnia część naszego mini kursu wprowadzającego do technologii Web3. Jeśli był on dla Ciebie wartościowy i nie jesteś jeszcze naszym subskrybentem – zapraszamy do dołączenia poniżej. Jest to dla nas najwyższa forma uznania i motywacja do pracy.