
React — это библиотека JavaScript для создания пользовательских интерфейсов. В этой статье я расскажу вам, как создать простое децентрализованное приложение с интерфейсом. Это поможет вам понять реальную реализацию Web3 в React и узнать, как внешние разработчики управляют состояниями и взаимодействуют с данными из смарт-контрактов.
Настройка и установка библиотек
Чтобы настроить и установить библиотеки, выполните следующие действия:
Шаг 1. Во-первых, нам нужно создать репозиторий в нашей учетной записи GitHub.
Шаг 2.Создайте новое приложение для реагирования. В этом уроке мы будем использовать TypeScript.
децентрализованное приложение npx create-react-app — машинописный шаблон
Шаг 3. Теперь мы переходим в нашу рабочую папку.
Теперь мы отправим наш локальный каталог в репозиторий GitHub выше, используя следующую команду git.
git init
git add README.md
git commit -m первая фиксация
git branch -M main
git remote add origin https://github. com/HoangMinhBK/decentralized-app.git
git push -u origin main
Теперь наш репозиторий готов.
Давайте добавим еще несколько важных библиотек, которые нам нужны для нашего проекта.
npm i @reduxjs/toolkit
npm i react-redux
npm i big-number
npm i web3
Создание децентрализованного приложения
Front-end разработчики, использующие ReactJS, знакомы с концепцией получения некоторых данных с сервера через API и их отображения на экране. В децентрализованном приложении идея очень похожа. В частности, мы взаимодействуем со смарт-контрактами через файл ABI, который позволяет вам узнать, какие функции содержит контракт и как данные считываются и возвращаются.
Децентрализованному приложению необходимо получить информацию об адресе и балансе кошелька. Кроме того, он должен иметь возможность отслеживать статус подключения, чтобы обновлять результат отображения в соответствии с действиями пользователя, такими как переключение учетных записей, отключение или повторное подключение их кошелька с нашего веб-сайта.
Прежде чем мы начнем, вам нужно скачать файл контракта ABI и поместить его в ранее созданную папку src. Ссылка на файл ABI: https://github.com/HoangMinhBK/decentralized-app/blob/main/src/BEP20_ABI.json
Подключение кошелька к EthereumJs
src/MyWallet.tsx
const address = useSelector((state: any) => state.wallet.address);
const tokenSymbol = useSelector((state: any) => state.wallet.symbol);
const tokenBalance = useSelector((state: any) => state.wallet.balance);
const connectionStatus = useSelector(
(state: any) => state.wallet.connectionStatus
);
const handleConnectWallet = async () => {
try {
const res = await (window as any).ethereum.request({
method: "eth_requestAccounts",
});
dispatch(connectWallet(res));
} catch (error) {
console.error("Some errors occurred!");
}
};
handleConnectWallet():
В этой функции сначала мы отправляем запрос на подключение к метамаске с помощью window.ethereum.request({method: «eth_requestAccounts»}). Эта функция вызывает всплывающие окна Metamask и просит нас войти в систему и предоставить доступ к нашему веб-сайту. Затем возвращает массив, в котором первым элементом является текущий активный аккаунт в нашем кошельке.
const handleDisconnectWallet = () => {
dispatch(disconnectWallet());
};
handleDisconnectWallet():
В этой функции мы сбрасываем состояние внутри хранилища избыточности до исходного состояния. Однако это работает только тогда, когда мы отключаем кошелек от веб-приложения (нажмите кнопку «Отключить кошелек» в веб-интерфейсе). Если мы хотим отключить кошелек непосредственно в Metamask, и наше приложение может распознать это действие для обновления пользовательского интерфейса, мы должны использовать другую функцию.
const handleAccountsList = async (addressList: Array<string>) => {
if (addressList.length === 0) {
handleDisconnectWallet();
} else handleConnectWallet();
};
useEffect(() => {
(window as any).ethereum.on("accountsChanged", handleAccountsList);
}, []);
useEffect(() => {
(window as any).ethereum.on("accountsChanged", handleAccountsList);
}, []);
обработка списка учетных записей():
Как упоминалось выше, нам нужна функция для обработки отключения от кошелька и переключения аккаунтов.
ethereum.on(‘accountsChanged’, (addressList) =› {
] // некоторые функции
«addressList» — это массив из 1 адреса аккаунта в вашем кошельке, подключенном к сайту и в данный момент активного. Массив будет автоматически обновляться при переключении на другую учетную запись в подключенных и будет пустым только тогда, когда нет подключенных учетных записей. В моем коде этот массив передается в функцию handleAccountsList(). Если addressList пуст, то мы вызываем handleDisconnectWallet(), в противном случае мы берем первый элемент массива и передаем его в handleConnectWallet() для переключения на эту учетную запись.
Получение информации о контракте с помощью Web3.Js
src/constants.ts
export const CONNECTION_STATUS = {
disconnected: "Not connected",
connected: "Connected",
};
export const CONTRACT_ADDRESS = "0x4abef176f22b9a71b45ddc6c4a115095d8761b37";
export const PROVIDER_URL = "https://data-seed-prebsc-1-s1.binance.org:8545/";
src/contract.ts
import abi from "./BEP20_ABI.json";
import { CONTRACT_ADDRESS, PROVIDER_URL} from "./constants";
const Web3 = require("web3");
const web3 = new Web3(PROVIDER_URL);
const contract = new web3.eth.Contract(abi, CONTRACT_ADDRESS);
Создает новый экземпляр контракта со всеми его методами и событиями, определенными в файле abi.
Параметры:
- Abi: JSON-интерфейс для создания экземпляра контракта.
- Адрес: адрес смарт-контракта для вызова.
Возвращает:
- Экземпляр контракта со всеми его методами и событиями.
Для получения подробной информации посетите документацию контракта Web3js.
Как мы можем узнать, какие методы предусмотрены контрактом? Мы можем использовать упомянутый файл ABI для их извлечения. Чтобы узнать больше об ABI, нажмите здесь.
В файле ABI у нас есть функция под названием «balanceOf», которая позволяет получить сумму токена контракта для адреса учетной записи. Для этого требуется адрес учетной записи в качестве входных данных и возвращается соответствующий баланс.
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
Зная метод контракта, мы можем построить функцию обратного вызова getTokenBalance следующим образом. Точно так же, согласно ABI, мы можем создавать любые функции, какие захотим (например, getTokenSymbol).
export const getTokenBalance = (accountAddress: string) =>
contract.methods
.balanceOf(accountAddress)
.call((err: any, result: any) => result);
export const getTokenSymbol = () =>
contract.methods.symbol().call((err: any, result: any) => result);
export default contract;
У вас может быть два или более адресов токенов и соответствующих контрактов, в зависимости от того, сколько типов токенов вы хотите показывать на своем веб-сайте. Например, у меня в кошельке Metamask 9990 TRAVA и 188 DAI, и я хочу показать их цифру в своем веб-приложении. Мне нужно создать контракты TRAVA и DAI. В этом уроке для простоты я показываю только данные о токенах TRAVA в моем кошельке.
src/redux/walletSlice.ts
interface Wallet {
connectionStatus: string;
address: string | undefined;
balance: number | undefined;
symbol: string | undefined;
}
const initialState = {
address: undefined,
balance: undefined,
connectionStatus: CONNECTION_STATUS.disconnected,
symbol: undefined,
} as Wallet;
Давайте определим начальное состояние, когда мы впервые запускаем наше приложение. Адрес, баланс и символ не определены, а состояние подключения — «Не подключено».
const walletSlice = createSlice({
name: "wallet",
initialState: initialState,
reducers: {
disconnectWallet(state) {
state.address = initialState.address;
state.symbol = initialState.symbol;
state.connectionStatus = initialState.connectionStatus;
state.balance = initialState.balance;
},
connectWallet(state, action) {
state.address = action.payload[0];
state.connectionStatus = CONNECTION_STATUS.connected;
},
getTokenSymbolAndBalance(state, action) {
state.balance = action.payload.balance;
state.symbol = action.payload.symbol;
},
},
});
export const { disconnectWallet, connectWallet, getTokenSymbolAndBalance } = walletSlice.actions;
export default walletSlice.reducer;
Здесь у нас есть три действия, а именно: отключить кошелек, подключить кошелек и получить TokenSymbolAndBalance. Эти действия используются для отправки для хранения данных, которые мы получаем из смарт-контракта.
const handleGetTokenBalanceAndSymbol = async (address: string) => {
try {
const symbol = await getTokenSymbol();
const balance = await getTokenBalance(address);
const formattedBalance = new BigNumber(balance).div(10**18).toNumber();
dispatch(
getTokenSymbolAndBalance({
symbol: symbol,
balance: formattedBalance,
})
);
} catch (err) {
console.error("Failed to get token symbol and balance");
}
};
Наконец, мы хотим получить символ и баланс кошелька. Для символа просто вызовите getTokenSymbol() в src/contract.ts и отправьте возвращенный результат в хранилище Redux.
Аналогичная процедура используется для получения баланса, но возвращаемое число является аномально значимым. Это потому, что смарт-контракты представляют собой фиксированное число, умножая его на 10^n на n, количество цифр после запятой. Например, если я хочу представить число с плавающей запятой 987,45 в смарт-контракте с десятичным числом n = 5, это число будет 98745000. В контрактах BEP20, как указано ранее в этом посте, их десятичное число равно 18.
Это означает, что когда мы получаем баланс из контракта, нам нужно разделить его на десятичную дробь контракта, используя библиотеку реагирования Bignumber.js. На самом деле, совершенно ненормально, когда кто-то видит, что у него в кармане 9999900000000000000000000 ETH из-за ошибки разработчика.
Наше приложение пока работает!
Расширенные возможности: Поиск информации по контракту BEP20
В этом разделе мы создаем форму, которая позволяет пользователям запрашивать смарт-контракт BEP20 для просмотра некоторой информации. Имея представление о смарт-контрактах выше, вы можете легко их создать. Вы можете увидеть код в моем репозитории GitHub для более подробной информации.
src/Explorer.tsx
Вы можете посетить исходный код приложения по адресу: https://github.com/HoangMinhBK/decentralized-app.
Заключение
Благодаря быстрому расширению и развитию децентрализованной архитектуры и блокчейна данные теперь распространяются и могут собираться с помощью обычных API-интерфейсов с централизованных серверов и различных сетевых узлов. Я надеюсь, что из этого руководства вы сможете получить общее представление о том, как создать простое децентрализованное приложение с помощью ReactJS и как интерфейсные клиенты взаимодействуют со смарт-контрактами.
Подробнее о технических инструкциях Front-end читайте здесь: https://techfi.tech/
Ссылка
[1] Web3.eth.Contract, web3js.readthedocs.io, по состоянию на 19 октября 2022 г.
[2] Что такое ABI смарт-контракта? Примеры и использование, alchemy.com, по состоянию на 19 октября 2022 г.
[3] API провайдера Ethereum, docs.metamask.io, по состоянию на 19 октября 2022 г.
[4] Nguyen Luu Hoang Minh GitHub, github.com/HoangMinhBK, по состоянию на 19 октября 2022 г.