头图

Pre-knowledge

  1. Have a preliminary understanding of the basic concepts of Ethereum
  2. Master react related knowledge
  3. Understand typescript syntax
  4. Understanding ethers.js library
  5. Understand web3js Library
  6. Understanding web3-react Library

Why is React not Vue ?

In the field of react there is an excellent library called web3-react , and there is also a cool UI library called web3modal that connects the react to the wallet. The connection process does not require us to operate. Both of these two libraries have been used on the largest trading websites. In addition to these excellent libraries, because the ecology of react has developed more vigorously than other frameworks abroad, it has led to all the well-known districts. The blockchain industry codes are all used react

web3js ehthers web3-react difference and connection

basic concept

web3js

Web3 is a library for communicating with the Ethereum blockchain and other blockchains running the Ethereum Virtual Machine, including Avalanche , Binance Smart chain and Solana .

web3.js has a main class called web3 . Most of the functionality of the library can be found in this class. The other 5 modules that make up web3js are:

  1. web3-eth : enables users of web3.js to interact with the Ethereum blockchain, for example:

    1. web3.eth.getBalance is used to obtain the Ethereum balance of an address in the specified block
    2. web3.eth.signTransaction is used to sign the transaction
    3. The role of web3.eth.sendSignedTransaction is to send the signed transaction to the Ethereum blockchain.
  2. web3-shh : Allows you to interact with the Whisper protocol. Whisper is a messaging protocol whose purpose is to easily broadcast messages as well as low-level asynchronous communication.
  3. web3-bzz : Allows you to interact with Swarm. Swarm is a decentralized storage platform and content distribution service that can be used to store files such as pictures or videos for decentralized applications.
  4. web3-net : Allows you to interact with the network properties of an Ethereum node.

    1. web3.*.net.getID return network ID
    2. web3.*.net.getPeerCount returns the number of peers connected to the node
  5. web3-utils : Provides utility functions that can be used in Ethereum DApps as well as other web3.js modules. Utility functions can be reused to make coding easier and are common in JavaScript and other programming languages. Web3-utils contains utility functions for converting numbers, verifying that values meet certain criteria, and searching datasets.

    1. web3.utils.toWei convert ether to Wei
    2. web3.utils.hexToNumberString Convert the hex value to a string
    3. web3.utils.isAddress Check if the specified string is a valid Ethereum address

web3-react

web3-react is a good React implementation of the popular library Web3

ehthers.js

Ethers.js is a JavaScript library that enables developers to interact with the Ethereum blockchain. This library contains utility functions from JavaScript and TypeScript , and all the functionality of the Ethereum wallet. Ethers.js was created with Ethers and is open source under the MIT license.

Similar to web3.js , ethers.js has four modules that make up the application programming interface ( API ).

  1. Ethers.provider : Encapsulates the connection to the Ethereum blockchain. It can be used to issue queries and send signed transactions, which will change the state of the blockchain.

    1. The purpose of ethers.providers.InfuraProvider is to allow you to establish a connection to the network of Ethereum nodes hosted by Infura
    2. ethers.provider.getBalance will get you the Ethereum balance of an address or block in the blockchain
    3. ethers.provider.resolve Name Service ( ENS ) name that will resolve to the Ethereum address

    Note: web3.js also has a provider for this purpose, located in the web3 base module. Ethers.js and web3.js are organized very differently, so although the functionality of the two libraries is very similar, there is not always a clear correspondence between modules.

  2. Ethers.contract : Deploy a smart contract and interact with it, the functions in this module are used to listen to events emitted from the smart contract, call functions provided by the smart contract, get information about the smart contract, and deploy the smart contract.
  3. Ethers.utils : Provides utility functions for formatting data and processing user input. Ethers.utils works in a similar way to web3-utils and simplifies the process of building decentralized applications.

    1. ethers.utils.getContractAddress extract the smart contract address from the transaction used to deploy the smart contract
    2. ethers.utils.computeAddress的函数来计算地址ethers.utils.formatEther所传递的Wei Ether Decimal string format
  4. Ethers.wallets : Ethers.wallet provides functionality very different from the other modules we've discussed so far. The purpose of Ethers.wallet is to allow you to connect to existing wallets (Ethereum addresses), create new wallets, and sign transactions.

    1. ethers.wallet.createRandom will create a random new account
    2. ethers.wallet.sign will sign the transaction and return the signed transaction as a hex string
    3. ethers.wallet.getBalance will give us the ethereum balance of the wallet address

Why import these libraries?

In fact, the wallet connection can also be written directly using the methods provided by web3 and meta mask , but there is a problem with this that many scenarios and multiple wallets need to be considered, which leads to a large amount of code and possible problems a lot of.

the difference

ehthers.jsweb3.js是, ethers.js ,而且可以搭配Hardhat工具使的The syntax has been further optimized.

connect

All three are JavaScript libraries that enable developers to interact with the Ethereum blockchain. Both of these libraries are practical and meet the needs of most Ethereum developers. web3-react Web3js a good React implementation of ---2661704c936c1c2f1c2437b96e6465eb---.

Basic Capability Encapsulation

WrappedWeb3ReactProvider

Global injection web3 instance, get instance in component through liabrary

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

// 获取web3实例的library
function getLibrary(provider: any): Web3Provider {
  const library = new Web3Provider(provider)
  // library.pollingInterval = 12000
  return library
}

function WrappedWeb3ReactProvider({ children }: { children: JSX.Element }) {
  return (
    <Web3ReactProvider getLibrary={getLibrary}>{children}</Web3ReactProvider>
  )
}

export default WrappedWeb3ReactProvider

Connector

 import { InjectedConnector } from '@web3-react/injected-connector'

export const injected = new InjectedConnector({
  // 支持的链ID
  // supportedChainIds: [56]
})

Contract

 import Web3 from 'web3'

/**
 * usage
 const contract = getContract(library, abi, address)
 contract.methods
 .exit()
 .send({
    from: account,
    })
 .on('transactionHash', (hash) => {
  })
 */

// ethers.Contract(address, abi, library.provider.singer)
export const getContract = (library: any, abi: any, address: string) => {
  const web3 = new Web3(library.provider)
  return new web3.eth.Contract(abi, address)
}

Switch network

Only BSC is supported in the following example

 import Web3 from 'web3'

const BSC_CHAIN_ID = 56

export const changeToBscNetwork = async (
  library: any,
  onError?: () => void
) => {
  try {
    await library.provider.request({
      method: 'wallet_switchEthereumChain',
      params: [{ chainId: Web3.utils.toHex(BSC_CHAIN_ID) }]
    })
  } catch (error: any) {
    if (error.code === 4902) {
      try {
        library.provider.request({
          jsonrpc: '2.0',
          method: 'wallet_addEthereumChain',
          params: [
            {
              chainId: '0x38',
              chainName: 'Binance Smart Chain Mainnet',
              rpcUrls: ['https://bsc-dataseed.binance.org/'],
              nativeCurrency: {
                name: 'BNB',
                symbol: 'BNB',
                decimals: 18
              },
              blockExplorerUrls: ['https://bscscan.com']
            }
          ],
          id: 0
        })
      } catch (e) {
        console.error('changeNetwork addEthereumChain error', e)
      }
    }
    onError?.()
    console.error('changeNetwork error', error)
  }
}

Common API

Documentation link

wallet_addEthereumChain

When adding a network and switching the network, the error returns code is 4902, which means that the network has not been added. The following is an example of adding bsc to the wallet network:

 library.provider.request({
  jsonrpc: '2.0',
  method: 'wallet_addEthereumChain',
  params: [
    {
      chainId: '0x38',
      chainName: 'Binance Smart Chain Mainnet',
      rpcUrls: ['https://bsc-dataseed.binance.org/'],
      nativeCurrency: {
        name: 'BNB',
        symbol: 'BNB',
        decimals: 18
      },
      blockExplorerUrls: ['https://bscscan.com']
    }
  ],
  id: 0
})

networkChanged

Monitor network changes

 library?.provider.on('networkChanged', (e: any) => {
  // 切换网络后,尝试连接
  console.log('networkChanged', e)
})

wallet_switchEthereumChain

Switch the network, the code refers to the basic capability package Switch network part of the code

eth_sendTransaction

Pull up the wallet to sign the transaction

 const params = [
  {
    from: account,
    to: CONTRACT,
    gas: web3.utils.toHex('76597'),
    gasPrice: web3.utils.toHex(web3.utils.toWei('5', 'gwei')),
    value: web3.utils.toHex(web3.utils.toWei(String(value))),
    data
  }
]

library.provider
  .request({
  method: 'eth_sendTransaction',
  params
})
  .then((result: any) => {
  console.log('Tx:', result)
})
  .catch((error: any) => {
  console.error(error.message)
})
  .finally(() => {
  setLoading(false)
})

Project combat

Project Introduction

Simulate PancakeSwap to realize a connection MetaMask wallet and realize the function of transaction on the chain BSC .

Create a new react project

 $ create-react-app web3-dapp-demo --template typescript
$ cd web3-dapp-demo
$ yarn start

Configuration Environment

web3-react bb305bfbe1491c29da909b51758cc1ba---和web3jsehthers.js等库后会报类似于---cdb9a52b7328c429e895ba8a69a194ab---的错误,视情况配置,如果没报错则Uncaught ReferenceError: process is not defined this configuration

 // config-overrides.js
// 先安装对应的依赖 url、fs、assert...

webpack: override(
  (config, env) => {
    config.resolve.fallback = {
      url: require.resolve('url'),
      fs: require.resolve('fs'),
      assert: require.resolve('assert'),
      crypto: require.resolve('crypto-browserify'),
      http: require.resolve('stream-http'),
      https: require.resolve('https-browserify'),
      os: require.resolve('os-browserify/browser'),
      buffer: require.resolve('buffer'),
      stream: require.resolve('stream-browserify')
    }
    config.plugins.push(
      new webpack.ProvidePlugin({
        process: 'process/browser',
        Buffer: ['buffer', 'Buffer']
      })
    )
    return config
  }

Configure the project entry file

antv , react18 ReactDOM.createRoot App antv Two identical charts.

 ReactDOM.render(
  <React.StrictMode>
    <Router>
      <Suspense fallback={<HomePage />}>
        <WrappedWeb3ReactProvider>
          <App />
        </WrappedWeb3ReactProvider>
      </Suspense>
    </Router>
  </React.StrictMode>,
  document.getElementById('root')
)

/**
 * react18 这种挂载方式会让antv重复渲染两个一模一样的图表,待官方更新,暂时先用回react17的方式渲染页面
 */
// root.render(
//   <React.StrictMode>
//     <Router>
//       <Suspense fallback={<HomePage />}>
//         <WrappedWeb3ReactProvider>
//           <App />
//         </WrappedWeb3ReactProvider>
//       </Suspense>
//     </Router>
//   </React.StrictMode>
// )

Write the HomePage view page

Because we have injected the web3 instance into Provider in the entry file, it can be obtained in any component of the project, if you need to get it window.ethereum global API 从liabrary.providerwindow.ethereumMetaMask插件注入到全局的一个对象,用于请求连接账户、获取用户连接The data of the chain (such as the return value and events after the transaction), and the status of the user's signature on the transaction. The existence of Provider can display objects for Ethereum users.

 import { useEffect, useState, type FC } from 'react'
import { useWeb3React } from '@web3-react/core'

import { BSC_CHAIN_ID } from '@common/constants'

import CheckNetwork from './components/CheckNetwork'
import SwapForm from './components/SwapForm'
import styles from './index.module.scss'

const HomePage: FC = () => {
  const [visible, setVisible] = useState(false)
  const { library } = useWeb3React()

  useEffect(() => {
    library?.provider.on('networkChanged', (e: any) => {
      // 切换网络后,尝试连接
      console.log('networkChanged', e, e === String(BSC_CHAIN_ID))
      setVisible(e !== String(BSC_CHAIN_ID))
    })
  }, [])

  return (
    <div className={styles.container}>
      {/* 交易Swap表单ui */}
      <SwapForm />
      {/* 检查当前网络是否是bsc,如果不是则断开连接或者switch到bsc */}
      <CheckNetwork visible={visible} onClose={() => setVisible(false)} />
    </div>
  )
}

export default HomePage

Swap form

Convert BNB to USDT at 1 : 5000

USDT is a contract we deployed by ourselves, but the name is the same as his, which is used for development and testing.

big.js Used for comparison and calculation of big data

 import { useWeb3React } from '@web3-react/core'
import Big from 'big.js'
import { type ChangeEvent, type FC, useEffect, useState } from 'react'
import { useForm } from 'react-hook-form'
import Web3 from 'web3'

import arrowDown from '@assets/images/arrow-down.png'
import contractAbi from '@common/abi/contractABI.json'
import { BSC_CHAIN_ID, CONTRACT } from '@common/constants'
import {
  changeToBscNetwork,
  cutZero,
  getContract,
  injected
} from '@common/utils'
import { useBalance } from '@common/hooks'

import styles from './index.module.scss'

const SwapForm: FC = () => {
  const [loading, setLoading] = useState(false)
  const { active, activate, library, account, chainId, deactivate } =
    useWeb3React()

  const web3 = new Web3(library?.provider)

  const {
    register,
    setValue,
    getValues,
    handleSubmit,
    formState: { errors }
  } = useForm()

  const balance = useBalance()

  // 监听网络变换,不是bsc就切换到bsc,否则断开连接
  useEffect(() => {
    if (chainId && chainId !== BSC_CHAIN_ID) {
      try {
        changeToBscNetwork(library, () => {
          deactivate()
        })
      } catch (ex) {
        console.log(ex)
      }
    }
  }, [chainId])

  const onBnbChange = (e: ChangeEvent<HTMLInputElement>) => {
    if (e.target.value) {
      if (new Big(e.target.value).mul(10e8).lt(10)) {
        setValue('usdt', null)
      } else {
        const val = cutZero(new Big(e.target.value).mul(5000).toFixed(8))
        setValue('usdt', !Number.isNaN(val) ? val : null)
      }
    } else {
      setValue('usdt', null)
    }
  }
  const onUsdtChange = (e: ChangeEvent<HTMLInputElement>) => {
    if (e.target.value) {
      if (new Big(e.target.value).div(5000).mul(10e8).lt(10)) {
        setValue('bnb', null)
      } else {
        const val = cutZero(new Big(e.target.value).div(5000).toFixed(8))
        setValue('bnb', !Number.isNaN(val) ? val : null)
      }
    } else {
      setValue('bnb', null)
    }
  }
  const run = async (value: number) => {
    setLoading(true)
    const myContract = getContract(library, contractAbi, CONTRACT)
    /**
     * 交易数据大部分都是写死的,参考给的示例: https://bscscan.com/tx/0x97af78f98cb6106314d19822c3e3782eba0e1945e0d45fb2193d0bfea5471094
     */
    const pairAddress = [
      '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c',
      '0x55d398326f99059fF775485246999027B3197955'
    ]
    const deadline = Math.floor(Date.now() / 1000) + 60 * 10 // this represents 10 mins of deadline
    // 调用合约,读取数据
    const data = await myContract.methods
      .swapExactETHForTokens(0, pairAddress, account, deadline)
      .encodeABI()

    const params = [
      {
        from: account,
        to: CONTRACT,
        gas: web3.utils.toHex('76597'),
        gasPrice: web3.utils.toHex(web3.utils.toWei('5', 'gwei')),
        value: web3.utils.toHex(web3.utils.toWei(String(value))),
        data
      }
    ]

    // 拉起钱包签名交易
    library.provider
      .request({
        method: 'eth_sendTransaction',
        params
      })
      .then((result: any) => {
        console.log('Tx:', result)
      })
      .catch((error: any) => {
        console.error(error.message)
      })
      .finally(() => {
        setLoading(false)
      })
  }

  const onCollect = (e: any) => {
    e.preventDefault()
    try {
      activate(injected)
    } catch (e) {
      console.log(e)
    }
  }

  const onSwap = () => {
    run(getValues().bnb)
  }

  return (
    <div className={styles.container}>
      <div className={styles.header}>
        <div className={styles.title}>Swap</div>
        <div className={styles.subTitle}>Trade tokens in an instant</div>
      </div>
      <form
        onSubmit={handleSubmit(() => {
          onSwap()
        })}
      >
        <label>
          <img
            src="https://assets.blocksec.com/image/1663207399693-2.png"
            alt=""
          />
          <span>BNB</span>
        </label>
        <input
          {...register('bnb', {
            required: true,
            validate: (val: any) => {
              if (/^(0|[1-9]\d*)(\.\d+)?$/.test(val)) {
                if (new Big(val).gt(new Big(balance ?? 0))) {
                  return 'Insufficient BNB balance'
                }
                return true
              }
              return false
            }
          })}
          onChange={onBnbChange}
        />
        {errors.bnb && (
          <p className={styles.errorValidTip}>
            {(errors.bnb as any)?.message || 'Can not be empty'}
          </p>
        )}
        <img src={arrowDown} alt="" className={styles.arrowDownIcon} />
        <label>
          <img
            src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xdAC17F958D2ee523a2206206994597C13D831ec7/logo.png"
            alt=""
          />
          <span>USDT</span>
        </label>
        <input
          {...register('usdt', {
            required: true,
            pattern: {
              value: /^(0|[1-9]\d*)(\.\d+)?$/,
              message: 'Please input the number'
            }
          })}
          onChange={onUsdtChange}
        />
        {errors.usdt && (
          <p className={styles.errorValidTip}>
            {(errors.usdt as any)?.message || 'Can not be empty'}
          </p>
        )}
        <div className={styles.displayItem}>
          <div className={styles.label}>Price</div>
          <div className={styles.value}>0.0002 BNB per USDT</div>
        </div>
        <div className={styles.displayItem}>
          <div className={styles.label}>Slippage Tolerance</div>
          <div className={styles.value}>0.5%</div>
        </div>
        {active ? (
          <button
            disabled={loading}
            type="submit"
            className={styles.primaryBtn}
          >
            <p>Swap</p>
            <p>(Current Solution)</p>
          </button>
        ) : (
          <div className={styles.primaryBtn} onClick={onCollect}>
            Connect Wallet
          </div>
        )}
      </form>
    </div>
  )
}

export default SwapForm

project outcome

UI effect display:

image-20220918160739000

Connect the wallet:

image-20220918161021422

Switch network:

image-20220918161224538

Pull up the wallet signature effect display:

image-20220918160854568

Summarize

web3-react This library is still under development, the latest beta version supports multiple wallets Connector connection method, core It is also relatively large. After the official version is released, I will update the latest tutorial. So far, 👋


MangoGoing
774 声望1.2k 粉丝

开源项目:详见个人详情