Wprowadzenie
W większości zdecentralizowanych aplikacji portfele kryptograficzne są wymagane aby użytkownicy mogli dokonywać w nich interakcji z blockchain. Aby to było możliwe developerzy zajmujący się implementacją warstwy frontend muszą dokonać integracji z aplikacjami portfelowymi użytkowników. Poniższy artykuł poświęcony jest developerem zajmującym się tworzeniem interfejsów użytkownika z pomocą biblioteki React. Jest to poradnik krok po kroku jak dokonać integracji aplikacji reactowej z wtyczką do przeglądarki obecnie jednego z najbardziej popularnych porteli jakim jest MetaMask.
Podstawowa aplikacja w React
Kod źródłowy aplikacji napisanej na potrzeby tego tutariala znajduje się pod tym linkiem. Do stworzenia aplikacji wykorzystałem następujące biblioteki:
Do pracy z tym poradnikiem można wykorzystać aplikację zamieszczoną pod linkiem wyżej, bądź samodzielnie skonfigurować aplikację w React z pomocą np. create-react-app lub vite.
Gdy nasza aplikacja jest już skonfigurowana, należy upewnić się że mamy zainstalowane wszystkie niezbędne zależności, aby tego dokonać należy wykonać komendę
npm install wagmi ethers
Do przygotowania aplikacji skorzystałem równiez bliblioteki komponentów o nazwie Material-ui, jeśli również chcesz z niej skorzystać zainstaluj następujące paczki komendą:
npm install @mui/material @emotion/react @emotion/styled
Po ukończonej konfiguracji i zainstalowaniu wszystkich niezbędnych zależności możemy przejść do kolejnego punktu.
Biblioteka Wagmi
Do integracji z aplikacją portfelową MetaMask wykorzystamy dedykowaną bibliotekę do React o nazwie wagmi zawierającą pokaźną liczbę hooków oraz funkcji potrzebnych w codziennej pracy przy interakcjach z blockchain w aplikacjach frontendowych.
Pierwszym krokiem będzie konfiguracja biblioteki, aby tego dokonać należy opakować naszą aplikację w komponent WagmiConfig przekazując zmienną client z naszą konfiguracją:
import { WagmiConfig, createClient } from "wagmi";
import { getDefaultProvider } from "ethers";
import { Home } from "./pages";
import "./styles.css";
const client = createClient({
autoConnect: true,
provider: getDefaultProvider()
});
export default function App() {
return (
<WagmiConfig client={client}>
<Home />
</WagmiConfig>
);
}
Wszystkie dostępne opcje konfiguracyjne znajdziejsz pod tym linkiem w oficjalnej dokumentacji wagmi
Podłączenie portfela MetaMask
Po ukończonej konfiguracji bliblioteki wagmi możemy przejść do tworzenia komponentu odpowiedzialnego za połączenie z naszym portfelem. W implementacji pomocne będą hooki dostępne w blibliotece.
Aby uzyskać dostęp do funkcji, która umożliwi nam wykonanie requestu o podłączenie portfela należy skorzystać z hooka useConnect(). Aby wskazać, że portfelem, z którym chcemy się połączoyć jest MetaMask, w obiekcie konfiguracyjnym do hooka pod kluczem connecter należy przekazać utworzoną instancję klasy InjectedConnector
import { useConnect } from "wagmi";
import { InjectedConnector } from "wagmi/dist/connectors/injected";
...
const { connect } = useConnect({
connector: new InjectedConnector()
});
...
Hook zwraca nam funkcję connect, którą możemy wywołać np. po kliknięciu przycisku.
...
<Button onClick={() => connect()}>Connect</Button>
...
Aby otrzymać informacje o podłączonym portfelu bądź stanie jego podłączenia, można skorzystać z hooka useAccount(), który zwraca nam m.in. takie informacje jak:
- adres podłączonego portfela
- to czy akcją podłączania portfela jest w trakcie
- Czy aktualnie w aplikacji portfel użytkownika jest podłączony
...
const { address, isConnected, isConnecting } = useAccount();
...
Jeśli użytkownikowi naszej aplikacji udało się podłączyć portfel należy umożliwić mu również jego odłączenie, do tego należy skorzystać z funkcji disconnect do której możemy się dostać z pomocą hooka useDisconnect()
...
const { disconnect } = useDisconnect();
...
Z pomocą tych trzech prostych hooków jesteśmy w stanie obsłużyć podłącznie portfela. Pełny kod źródłowy komponentu obsługującego podłączanie z przykładowej aplikacjij:
import { useConnect, useDisconnect, useAccount } from "wagmi";
import { InjectedConnector } from "wagmi/dist/connectors/injected";
import { Card, Button, Heading } from "../../components";
import Typography from "@mui/material/Typography";
import { WalletInfo } from "./WalletInfo";
export const WalletConnect = () => {
const { isConnected, isConnecting } = useAccount();
const { connect } = useConnect({
connector: new InjectedConnector()
});
const { disconnect } = useDisconnect();
return (
<Card>
<Heading sx={{ mb: 2 }}>
{isConnected ? "Your connected wallet:" : "Connect your MetaMask"}
</Heading>
{isConnecting && <Typography>Connecting...</Typography>}
{isConnected ? (
<>
<WalletInfo />
<Button sx={{ mt: 2 }} onClick={() => disconnect()}>
Disconnect
</Button>
</>
) : (
<Button
disabled={isConnecting}
sx={{ mt: 2 }}
onClick={() => connect()}
>
Connect
</Button>
)}
</Card>
);
};
Na powyższym przykładzie znajduje się komponent <WalletInfo />, który posłuży nam do wyświetlenia informacji o podłączonym portfelu, jego utworzeniem zajmiemy się w kolejnym kroku
Wyświetlanie informacji o podłączonym portfelu
Kolejnym krokiem będzie wyświetlenie użytkownikowi informacji o podłączonym portfelu takich jak:
- Adres portfela
- Aktualny balans ETH na portfelu
W tym celu przygotujemy dwa proste komponenty <WalletAddress/> oraz <WalletBalance/>, które następnie umieścimy w komponencie <WalletInfo/>:
Adres aktualnie podłączonego portfela
import { WalletAddress } from "./WalletAddress";
import { WalletBalance } from "./WalletBalance";
export const WalletInfo = () => {
return (
<div>
<walletaddress />
<walletbalance />
</div>
);
};
import { useAccount } from "wagmi";
import Typography from "@mui/material/Typography";
export const WalletAddress = () => {
const { address } = useAccount();
return (
<typography>
<strong>Address: </strong>
{address}
</typography>
);
};
W celu wyświetlenia podłączonego portfela skorzystamy z wcześniej wspomnianego hooka useAccount(), który zwraca nam zmienną address. Implementacja prostego komponentu do wyświetlenia adresu wygląda następująco:
import { useAccount } from "wagmi";
import Typography from "@mui/material/Typography";
export const WalletAddress = () => {
const { address } = useAccount();
return (
<typography>
<strong>Address: </strong>
{address}
</typography>
);
};
Balans aktualnie podłączonego portfela
Biblioteka wagmi posiada również hooka useBalance(), który znacznie ułatwi nam proces pobierania aktualnego balansu portfela. Proces pobierania tej wartości z blockchain jest asynchroniczny, więc hook ten zwraca na m.in. takie informacje w zmiennych jak:
- isLoading - czy pobieranie balansu jest w trakcie
- isFetched - czy balans portfela został pobrany
- isError - czy podczas pobierania danych wystąpił błąd
- data - obiekt zawierający takie pola jak:
- value - balans użytkownika w jednostach WEI
- formatted - balans użytkownika sformatowany do jednostek ETH
- symbol - Symbol aktywa dla którego został pobrany balans
- decimals - Liczba miejsc po przecinku jakie może posiadać liczba opisująca ilość danego aktywa
W celu lepszego zrozumienia czym jest jednostka WEI, ETH bądź parametr decimals zachęcam do zapoznania się z tymi artykułami:
- Crypto Denominations Explained: BTC & Satoshis; ETH & Gwei
- Knows “Token Decimals”: When 1 million tokens does not always mean there is only 1 million tokens in total.
Aby wskazać, dla jakiego portfela chcemy pobrać balans środków, przy wywołaniu hooka musimy przekazać adres tego portfela jako parametr w następujący sposób, aby to zrobić możemy skorzystać ponownie z hooka useAccount() z poprzedniego kroku:
...
const { address } = useAccount();
const { isLoading, isFetched, isError, data } = useBalance({ address });
...
Dzięki tym informacjom jesteśmy w stanie zaimplementować cały komponent z obsługą procesu ładowania danych:
import { useAccount, useBalance } from "wagmi";
import Typography from "@mui/material/Typography";
import Skeleton from "@mui/material/Skeleton";
export const WalletBalance = () => {
const { address } = useAccount();
const { isLoading, isFetched, isError, data } = useBalance({ address });
return (
<typography>
{isLoading && <skeleton width="{200}" />}
{isFetched && (
<>
<strong>Balance: </strong>
{data?.formatted} {data?.symbol}
</>
)}
{isError && "Fetching balance failed!"}
</typography>
);
};
Podsumowanie
Przedstawiona aplikacja to tylko przykład, w produkcyjnych aplikacjach developerzy często muszą się mierzyć z integracją wielu aplikacji portfelowych, wspieraniem połączenia na wielu sieciach blockchain oraz interakcjami podłączonych portfeli z smart kontraktami. Wszystkie te funkcjonalności oraz znacznie więcej wspiera biblioteka wagmi zaprezentowana w tym turorialu. Dlatego też zachęcam do przestudiowania dokumentacji tej biblioteki w celu zapoznania się, jakie możliwości oferuje.