前置常识
- 对以太坊的基本概念有初步意识
- 把握
react
相干常识 - 理解
typescript
语法 - 理解
ethers.js
库 - 理解
web3js
库 - 理解
web3-react
库
为什么是 React
不是 Vue
?
区块链畛域在 react
外面有一个很优良的库叫 web3-react,还有一个很酷的连贯钱包的 react 连贯 UI 的库叫 web3modal,连贯的过程不须要咱们操作。这个两个库都曾经在最大的交易网站下面应用了,除了这些优良的库,因为 react
的生态在国外自身就是就倒退得比其余框架更加蓬勃,所以导致所有出名的区块链行业代码都是应用 react
web3js ehthers web3-react 区别与分割
基本概念
web3js
Web3
是一个用于与以太坊区块链以及运行以太坊虚拟机的其余区块链进行通信的库,包含 Avalanche
,Binance Smart chain
和Solana
。
web3.js
有一个主类,称为 web3
。在该类中能够找到该库的大多数性能。组成 web3js
的另外 5 个模块别离是:
-
web3-eth
: 使web3.js
的用户能够与以太坊区块链进行交互,比方:web3.eth.getBalance
的作用是取得指定区块的某个地址的以太坊余额web3.eth.signTransaction
的作用是对交易签名web3.eth.sendSignedTransaction
的作用是将签名的交易发送到以太坊区块链。
web3-shh
: 使你能够与 Whisper 协定进行交互。Whisper 是一个音讯传输协定,其目标是轻松播送音讯以及进行低层异步通信。web3-bzz
: 使你能够与 Swarm 交互。Swarm 是一个去中心化存储平台和内容散发服务,它能够用来为去中心化利用存储图片或视频等文件。-
web3-net
: 使你能够与以太坊节点的网络属性进行交互。web3.*.net.getID
返回网络ID
web3.*.net.getPeerCount
返回连贯到节点的对等点数
-
web3-utils
:提供实用程序函数,这些函数可在以太坊去中心化利用以及其余 web3.js 模块中应用。实用程序函数能够重复使用,使代码编写更轻松,在 JavaScript 和其余编程语言中很常见。Web3-utils 蕴含实用程序函数,这些函数用于转换数字、验证值是否满足特定条件以及搜寻数据集。web3.utils.toWei
将以太转换为Wei
web3.utils.hexToNumberString
将十六进制值转换为字符串web3.utils.isAddress
校验特定字符串是否为无效的以太坊地址
web3-react
web3-react
是风行的库 Web3
的一个很好的 React
实现
ehthers.js
Ethers.js 是一个 JavaScript 库,其作用是使开发者能够与以太坊区块链进行交互。该库蕴含 JavaScript
和 TypeScript
中的实用程序函数,以及以太坊钱包的所有性能。Ethers.js 是通过 Ethers 创立的,是采纳 MIT 许可证的凋谢源。
与 web3.js
类似,ethers.js
有四个模块,形成应用程序编程界面 (API
)。
-
Ethers.provider
: 封装与以太坊区块链的连贯。它能够用于签发查问和发送已签名的交易,这将扭转区块链的状态。ethers.providers.InfuraProvider
的作用是使你能够与Infura
托管的以太坊节点网络建设连贯ethers.provider.getBalance
将为你获取区块链中某个地址或区块的以太坊余额ethers.provider.resolve
将解析传递到以太坊地址的以太坊名称服务 (ENS
) 名称
注:web3.js 也有服务于此目标的提供商,位于
web3
根底模块中。Ethers.js 和web3.js
的组织形式截然不同,因而只管两个库的性能十分类似,但模块间并非总是能清晰对应。 Ethers.contract
: 部署智能合约并与它交互,该模块中的函数用于侦听从智能合约发射的事件、调用智能合约提供的函数、获取无关智能合约的信息,以及部署智能合约。-
Ethers.utils
:提供用于格式化数据和解决用户输出的实用程序函数。Ethers.utils 的作用形式与 web3-utils 类似,可能简化去中心化利用的构建流程。ethers.utils.getContractAddress
从用于部署智能合约的交易中提取智能合约地址ethers.utils.computeAddress
通过传递与地址相干的公钥或私钥的函数来计算地址ethers.utils.formatEther
将所传递的Wei
金额转换为Ether
十进制字符串格局
-
Ethers.wallets
:Ethers.wallet
提供的性能与咱们目前探讨过的其余模块截然不同。Ethers.wallet
的作用是使你能够与现有钱包(以太坊地址)建设连贯、创立新钱包以及对交易签名。ethers.wallet.createRandom
将创立随机新账户ethers.wallet.sign
将对交易签名并将已签名的交易返回为十六进制字符串的模式ethers.wallet.getBalance
将为咱们提供钱包地址的以太坊余额
为什么要引入这些库?
钱包连贯其实也能够间接用 web3
和 meta mask
提供的办法写,然而这样有一个问题是须要思考很多场景和多钱包,这样导致代码量很大并且问题可能很多。
区别
ehthers.js
与 web3.js
不同的是,ethers.js
在应用时不须要过多的回调函数,而且能够搭配 Hardhat
工具使的语法失去进一步的优化。
分割
三者 都是 JavaScript
库,其作用是使开发者能够与以太坊区块链交互。这两个库都很实用,都能满足大多数以太坊开发者的需要。web3-react
是 Web3js
的一个很好的 React
实现。
根底能力封装
WrappedWeb3ReactProvider
全局注入 web3
实例,在组件里通过 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
下例中仅反对 BSC
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)
}
}
常见 API
文档链接
wallet_addEthereumChain
增加网络,切换网络时,谬误返回 code
为 4902 时示意该网络未增加,上面以增加 bsc
到钱包网络中为例:
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
监听网络变动
library?.provider.on('networkChanged', (e: any) => {
// 切换网络后,尝试连贯
console.log('networkChanged', e)
})
wallet_switchEthereumChain
切换网络,代码参考根底能力封装的 Switch network
局部代码
eth_sendTransaction
拉起钱包签名交易
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)
})
我的项目实战
我的项目介绍
模仿 PancakeSwap 实现一个连贯 MetaMask
钱包并能实现在 BSC
链上交易的性能。
新建 react 我的项目
$ create-react-app web3-dapp-demo --template typescript
$ cd web3-dapp-demo
$ yarn start
配置环境
引入 web3-react
和 web3js
、ehthers.js
等库后会报相似于 Uncaught ReferenceError: process is not defined
的谬误,视状况配置,如果没报错则疏忽此配置
// 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
}
配置我的项目入口文件
如果我的项目用到了 antv
, 应用 react18
提供的 ReactDOM.createRoot
形式挂在 App
会让 antv
反复渲染两个截然不同的图表。
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>
// )
编写 HomePage 视图页面
因为咱们在入口文件曾经把 web3
实例注入到 Provider
了,所以在我的项目任一组件里都能拿到它,如需获取 window.ethereum
全局 API
能够应用 从 liabrary.provider
代替,window.ethereum
是 MetaMask
插件注入到全局的一个对象,用于申请连贯账户、获取用户连贯的链的数据(如交易后的返回值和事件)、以及显示用户对交易的签名状态。Provider
的存在能够显示以太坊用户的对象。
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 表单
BNB 兑换 USDT 为 1 : 5000
USDT 是咱们本人部署的一个合约,只是名字跟他一样,此用于开发测试用
big.js
用于大数据的比拟、计算应用
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
我的项目成绩
ui 成果展现:
连贯钱包:
切换网络:
拉起钱包签名成果展现:
总结
web3-react
这个库目前还在开发中,最新的 beta
版本反对了多种钱包的 Connector
连贯形式,core
包改变也比拟大,等正式版本公布了再更新一期最新的应用教程,至此,👋