web3 — 3D серия web3

Серия 3D web3
Это последний пост из серии 3D-web3.

1 — Vite config и базовый three.js
2 — Three.js (fiber & drei)
3 — Физика пушки
4 — 3D web — react-web3

Стабильная версия — v6, в настоящее время v8 находится в бета-версии.
Разрабатывается руководителем инженерного отдела Uniswap Ноа Зинсмайстером.

На высоком уровне, web3-react — это машина состояний, которая гарантирует, что определенные ключевые части данных (например, текущий счет пользователя), относящиеся к вашему dApp, поддерживаются в актуальном состоянии. Для этого web3-react использует Context, чтобы эффективно хранить эти данные и вводить их туда, где они нужны в вашем приложении.

Web3-react v6 использует Context для эффективного хранения этих данных и внедрения их в любое место приложения.

Полезные ссылки:
Исходный код Uniswap/web3-react
Документы Uniswap/web3-react
Как использовать Web3React в вашем следующем проекте

Существует еще несколько библиотек для создания web3 окружения, например:
Rainbowkit || Wagmi || Scaffold-eth || useDApp || web3modal || Web3-UI

Мы используем Ethers.js за сценой. Для того чтобы поддерживать контекст и легко соединяться с блокчейном в нашем DApp. Также для подключения различных видов блокчейн-провайдеров, кошельков или цепочек и для более эффективного запроса к блокчейну.

В любом случае, можно построить непосредственно весь DApp, используя эфириум.

Мы будем использовать:

кошелек MaskMask на стороне пользователя
Библиотеки веб-клиента Ethereum — «@web3-react», «ethersproject».

  • От «@web3-react»: Web3ReactProvider, context, useWeb3React, hooks, InjectedConnector. Для подключения кошелька и получения данных из блокчейна. (построено поверх «ethers»).
  • От «@ethersproject»: Contract, Web3Provider. Для отправки транзакций в блокчейн.
  • Для прослушивания транслируемых событий от провайдера мы используем библиотеку «events».
npm i @web3-react/core @web3-react/injected-connector
npm i @ethersproject/contracts @ethersproject/providers
npm i events
Вход в полноэкранный режим Выход из полноэкранного режима

В этой демонстрации мы развертываем один и тот же токен в тестовой сети BSC и Мумбаи (тестовая сеть Polygon).

Во-первых, нам необходимо установить в браузере расширение metamask, TrustWallet (WalletConnect) или coinbase.

@Notice WalletConnect устанавливает зашифрованное соединение между вашим кошельком и DApp. Используйте его с Ex. с «Trust Wallet».

Вы можете проверить все детали css в git. Эта статья посвящена web3 соединению.

Шаг 1_ Создайте контекст web3 для всех дочерних компонентов

Добавьте провайдера контекста

web3-react полагается на существование Web3ReactProvider в корне вашего приложения.

Ему требуется единственный реквизит getLibrary, который отвечает за инстанцирование объекта библиотеки удобств web3 из низкоуровневого провайдера.


import React, { useEffect } from 'react';
import { Web3ReactProvider } from '@web3-react/core'
import { Web3Provider } from '@ethersproject/providers'

function getLibrary(provider) {
    const library = new Web3Provider(provider)
    library.pollingInterval = 12000
    return library
}

function Web3ContextProvider({ children }) {

    return (
        <Web3ReactProvider getLibrary={getLibrary}>
            {children}
        </Web3ReactProvider>
    )
}

export default Web3ContextProvider
Вход в полноэкранный режим Выход из полноэкранного режима

Добавить в App.jsx

import Web3ContextProvider from './web3/Web3ContextProvider';
import ConnectWallet from './web3/ConnectWallet';

return (
...
<Web3ContextProvider style={{ height: '15vh' }} className='header'>
                    <ConnectWallet />
</Web3ContextProvider>
...
)
Войти в полноэкранный режим Выйти из полноэкранного режима

Шаг 2_ Создание объекта Web3 и определение методов

Создайте ConnectWallet.jsx
Используйте «useWeb3React» для подключения к блокчейну с помощью «InjectedConnector».
С помощью MetaMask вы можете связаться с провайдером по «windows.ethereum».

Предусмотрите кнопку для подключения и отключения кошелька и еще одну для смены текущей цепочки.

import { useEffect } from 'react'
import { useWeb3React } from '@web3-react/core'
import { InjectedConnector } from '@web3-react/injected-connector'
import "../App.css"
import "../Button.css"
import "../Select.css"
import { changeChainById } from "./transaction/chains"
import ClaimToken from "./ClaimToken"

const ConnectWallet = () => {

    const injectedConnector = new InjectedConnector({
        supportedChainIds: [1, 97, 80001],
    })

    const { chainId, account, activate, active, library, deactivate, connector } = useWeb3React()

    const activateWallet = () => {
        activate(injectedConnector)
    }
    const deactivateWallet = () => {
        deactivate(injectedConnector)
    }
    const changeChain = (_chainID) => {
        changeChainById(_chainID)
    }

    useEffect(() => {
        if (!chainId) return
        document.getElementById('select-form').value = chainId
    }, [chainId])

    return (
        <main className="web3-navbar">
            <h2 >Welcome to 3D web3 series</h2>
            <div className='connect-box'>
                <b>ChainId: {chainId}</b>
                <div>Account: {account}</div>
                {active ? (
                    <button type="button" className='button-4' onClick={deactivateWallet}>
                        Disconnect
                    </button>
                ) : (
                    <button type="button" className='button-3' onClick={activateWallet}>
                        Connect Wallet
                    </button>
                )}
            </div>
            <div className='box'>
                <select id='select-form' onChange={e => {
                    let _chainID = e.target.value
                    changeChain(_chainID)
                }}>
                    <option key={1} value={1}>Ethereum Chain</option>
                    <option key={97} value={97}>BSC testnet</option>
                    <option key={80001} value={80001}>Mumbai testnet</option>
                </select>
            </div>
            <div>
                <ClaimToken
                    account={account}
                    chainId={chainId}
                />
            </div>
        </main>
    )
}

export default ConnectWallet
Вход в полноэкранный режим Выход из полноэкранного режима

Шаг 3_ Методы добавления и переключения между цепочками.

Для переключения между различными блокчейнами мы используем встроенные методы API metamask RPC.

Вызовите метод «wallet_switchEthereumChain», чтобы запросить пользователя сменить цепочку.

Если у пользователя не настроена конкретная цепочка, мы отлавливаем и вызываем метод «wallet_addEthereumChain», чтобы запросить пользователя добавить выбранную цепочку.

@Notice. Используйте tryCatch. Есть несколько повторяющихся ошибок, которые нужно обработать здесь

Для того чтобы изменить/добавить цепочку, мы должны предоставить информацию о цепочке. Проверьте код.

export const changeChainById = async (chainID) => {
  if (!window.ethereum)
    return alert("install metamask extension in your browser");
  try {
    await ethereum.request({
      method: "wallet_switchEthereumChain",
      params: [{ chainId: chains[chainID].chainId }],
    });
  } catch (switchError) {
    // This error code indicates that the chain has not been added to MetaMask.
    if (switchError.code === 4902) {
      try {
        await ethereum.request({
          method: "wallet_addEthereumChain",
          params: [chains[chainID]],
        });
      } catch (addError) {
        console.log("error: ", addError);
        if (ex.code === 32002)
          return alert("already pending request from user in metamask");
        else
          return alert(
            "Disconnect wallet from metamask configuration and try again!"
          );
      }
    }
    // handle other "switch" errors
  }
  return;
};

const ETH = {
  name: "Ether",
  symbol: "ETH",
  decimals: 18,
};
const MATIC = {
  name: "Matic",
  symbol: "MATIC",
  decimals: 18,
};
const BNB = {
  name: "Binance",
  symbol: "BNB",
  decimals: 18,
};

const chains = {
  1: {
    chainId: "0x1",
    chainName: "Ethereum mainnet",
    nativeCurrency: ETH,
    rpcUrls: [
      import.meta.env.VITE_APP_INFURA_KEY
        ? `https://mainnet.infura.io/v3/${import.meta.env.VITE_APP_INFURA_KEY}`
        : undefined,
      import.meta.env.VITE_APP_ALCHEMY_KEY
        ? `https://eth-mainnet.alchemyapi.io/v2/${
            import.meta.env.VITE_APP_ALCHEMY_KEY
          }`
        : undefined,
      "https://cloudflare-eth.com",
    ].filter((url) => url !== undefined),
    blockExplorerUrls: ["https://etherscan.com/"],
  },
  97: {
    chainId: "0x61",
    chainName: "Binance Testnet",
    nativeCurrency: BNB,
    rpcUrls: [
      "https://data-seed-prebsc-1-s1.binance.org:8545/",
      "https://data-seed-prebsc-2-s1.binance.org:8545/",
      "http://data-seed-prebsc-1-s2.binance.org:8545/",
      "https://data-seed-prebsc-2-s3.binance.org:8545/",
    ],
    // rpcUrls: 'https://data-seed-prebsc-1-s1.binance.org:8545',
    blockExplorerUrls: ["https://testnet.bscscan.com/"],
  },
  80001: {
    chainId: "0x13881",
    chainName: "Polygon Mumbai",
    nativeCurrency: MATIC,
    rpcUrls: [
      import.meta.env.VITE_APP_INFURA_KEY
        ? `https://polygon-mumbai.infura.io/v3/${
            import.meta.env.VITE_APP_INFURA_KEY
          }`
        : undefined,
    ].filter((url) => url !== undefined),
    blockExplorerUrls: ["https://mumbai.polygonscan.com/"],
  },
};

Вход в полноэкранный режим Выход из полноэкранного режима

Шаг 4_ Определение транзакций

Создайте компонент ClaimToken.jsx для определения пользовательского интерфейса.


import { burnToken, claimToken } from './transaction/transaction'
import "../App.css"

export default function TransactionMetaMask(props) {

    const claimTokenTx = () => {
        if (props.chainId === 97 || props.chainId === 80001) {
            claimToken(props.provider, props.account, props.chainId, 1)
        } else {
            scrollTo(0, 0)
            alert('Tokens are only available in BSC and Polygon testnets')
        }
    }
    const burnTokenTx = () => {
        if (props.chainId === 97 || props.chainId === 80001) {
            burnToken(props.provider, props.account, props.chainId, 1)
        } else {
            scrollTo(0, 0)
            alert('Tokens are only available in BSC and Polygon testnets')
        }
    }

    return (
        <div className='token-buttons'>
            <button type="button" className='button-3' onClick={claimTokenTx}>
                Claim Token
            </button>
            <button type="button" className='button-3' onClick={burnTokenTx}>
                Burn Token
            </button>
        </div>
    )
}

Войдите в полноэкранный режим Выйти из полноэкранного режима

Чтобы иметь возможность отправить транзакцию для изменения данных блокчейна, импортируйте провайдера непосредственно из «@ethersproject/providers», чтобы иметь возможность создать объект «signer».

Теперь, используя адрес смарт-контракта, ABI и signer, создайте объект «Contract» (готовый к взаимодействию с методами контракта).

import { Contract } from "@ethersproject/contracts";
import { Web3Provider } from "@ethersproject/providers";

// Same ABI for all SC living in EVM compatible networks
export const contractAbi = [...];

const contractsAddress = {
  80001: "0x41e6913ce749018910e45980996dac1f99012c96", // MUMBAI
  97: "0x6ec4c5ce6cc67729d89785f715e103e5981c9780", // BSC Test
};
// TODO
export const getContract = (chainId) => {
  // using ethersproject to set signer using default provider
  const provider = new Web3Provider(window.ethereum);
  const signer = provider.getSigner();

  const contractAddress = contractsAddress[chainId];

  const contract = new Contract(contractAddress, contractAbi, signer);
  return contract;
};

Войдите в полноэкранный режим Выйти из полноэкранного режима

Наконец, отправьте асинхронный RPC и отловите все ошибки.

import { getContract } from "./contract";

// writeToContractUsingWeb3React
const claimToken = async (account, chainId, amount) => {
  try {
    const myContract = getContract(chainId);
    // Metamask calculates gas, but, for walletConnect and coinbase we need to set gas limit
    const overrides = {
      gasLimit: 230000,
    };
    const txResponse = await myContract.mint(account, amount, overrides);
    const txReceipt = await txResponse.wait();
    console.log(txReceipt);
    // alert(txReceipt);
  } catch (ex) {
    console.log(ex);
    if (ex.code === 32002)
      return alert("already pending request from user in metamask");

    if (ex.code === 4001) return alert("User denied transaction signature");
    return alert('"Connect / Disconnect" your wallet and try again.');
  }
};

const burnToken = async (chainId, amount) => {
  try {
    const myContract = getContract(chainId);
    // Metamask calculates gas, but, for walletConnect and coinbase we need to set gas limit
    const overrides = {
      gasLimit: 230000,
    };
    const txResponse = await myContract.burn(amount, overrides);
    const txReceipt = await txResponse.wait();
    console.log(txReceipt);
    // alert(txReceipt);
  } catch (ex) {
    console.log(ex);
    if (ex.code === 32002)
      return alert("already pending request from user in metamask");
    if (ex.code === 4001) return alert("User denied transaction signature");
    return alert('"Connect / Disconnect" your wallet and try again.');
  }
};

export { claimToken, burnToken };

Войти в полноэкранный режим Выход из полноэкранного режима

Теперь форкните git-репо и попробуйте localy.

npm install

// add to .env.local
// VITE_APP_INFURA_KEY
// VITE_APP_ALCHEMY_KEY

npm run dev
Войти в полноэкранный режим Выход из полноэкранного режима

Или посмотрите демо-версию Code Sand Box.
Провайдер RPC может быть отключен время от времени.
В этом случае предоставьте свой собственный бесплатный RPC API из infura или alchemy.

Адрес токена BSC testnet, bscscan
Адрес токена Мумбаи, polygonscan

Надеюсь, это было полезно.

Оцените статью
devanswers.ru
Добавить комментарий