1.构建项目
这里踩坑时间花得比较多,因为网上并没有能很快给出解决的方案
主要使用 Next.js 来进行开发,目的是节约搭建架构花费的时间
- 创建项目建议使用 :
Tailwindcss、Eslint、TypeScript、路由、路径别名
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: false, // 开发过程不使用严格模式
eslint: {
ignoreDuringBuilds: true // 忽略构建时候的错误,防止错误打断构建
},
typescript: {
ignoreBuildErrors: true // 忽略构建时候的错误,防止错误打断构建
}
}
module.exports = nextConfig
1.1生产环境依赖
yarn add bignumber.js react-copy-to-clipboard ethers@5.7.0 wagmi@0.6.4 styled-components
bignumber.js处理大数/小数精度问题 bignumber.js GitHubreact-copy-to-clipboard复制文本组件 NPM 教程ethers用于于以太坊区块链合约交互类库 官网wagmi一个封装了 钱包操作,合约交互 的 React Hook 新版本官网,可以看迁移指南styled-components一个 css-in-js 的 React 样式库
1.2开发环境依赖
- 主要是一些类型提示以及工具
yarn add @types/react-dom autoprefixer @types/react @types/node eslint-config-next eslint postcss sass @typechain/ethers-v5 @types/styled-components babel-plugin-styled-components typechain -D
typechain用于编译AIB类型
"typechain": "typechain --out-dir src/contract/abi/types --target=ethers-v5 "src/contract/abi/*.json""
尽量不要使用pnpm安装包,可能出现意外
2.Wgmi和EtherJS的使用
- 初始化默认链
RPC节点可以到 Chainlist 中找到合适的节点
import { chain } from 'wagmi'
import { SupportedChains } from './types/chain'
export const supportedChains: SupportedChains = {
mainnet: {
...chain.mainnet,
rpcUrls: { // RPC节点,用于和对应的链进行交互,这里是以太坊主网节点的地址
public: 'https://eth-mainnet.nodereal.io/v1/1659dfb40aa24bbb8153a677b98064d7',
default: 'https://eth-mainnet.nodereal.io/v1/1659dfb40aa24bbb8153a677b98064d7'
}
},
goerli: {
...chain.goerli,
rpcUrls: {
public: 'https://ethereum-goerli.publicnode.com',
default: 'https://ethereum-goerli.publicnode.com'
}
}
}
export const defaultChain = process.env.NODE_ENV === 'production' ? supportedChains.mainnet : supportedChains.mainnet
- 初始化
wagmi,打开于区块链交互的大门
import { supportedChains } from '@/configs/chains'
import { configureChains, createClient } from 'wagmi'
import { InjectedConnector } from 'wagmi/connectors/injected'
import { mainnet } from '@wagmi/core/chains'
import { publicProvider } from 'wagmi/providers/public'
// 第一个参数传递支持的链,第二参数是
export const { chains, provider } = configureChains([mainnet], [publicProvider()])
export const client = createClient({
autoConnect: true, // 自动连接
connectors: [new InjectedConnector({ chains })], // 初始化连接器
provider, // 提供一些和合约交互的方法
})
- 提供上下文,让其他后代组件可以使用
wagmi的方法
import { AppProps } from 'next/app'
import { useEffect, useState } from 'react'
import Head from 'next/head'
import { WagmiConfig } from 'wagmi'
import { client } from '@/utils/wagmi'
const App = ({ Component, pageProps }: AppProps) => {
const [mounted, setMounted] = useState(false)
useEffect(() => setMounted(true), [])
if (!mounted) return null // 只能在客户端中使用,所以服务端不渲染
return (
<>
// 提供配置上下文给下级组件
<WagmiConfig client={client}>
<Component {...pageProps} />
</WagmiConfig>
</>
)
}
export default App
- 基于EthersJS初始化合约连接,连接合约并且获取一些签名权限,这样进行敏感操作的时候就会弹出小狐狸窗口了
import { Contract } from 'ethers'
import { defaultChain } from '@/configs/chain'
import { provider } from '@/utils/wagmi'
import { Ido } from './abi/types'
import { useSigner } from 'wagmi'
import idoAbi from './abi/ido.json'
const contractAddresses =
process.env.NODE_ENV === 'production'
? 'You contract address'
: 'You contract address'
export const idoContract = () => {
const { data: signer } = useSigner()
// 获取签名或者客户端的公用注入者,如果注入者都没有那么连读合约都不行
const signerOrProvider = signer ?? provider({ chainId: defaultChain.id })
return new Contract(contractAddresses, idoAbi, signerOrProvider) as Ido
}
- 合约读写交互、钱包状态、前端的链信息
import { defaultChain } from '@/configs/chain'
import { useState } from 'react'
import { idoContract } from '@/contract'
import { useInterval, useMount } from 'ahooks'
import { waitForTransaction, getNetwork, switchNetwork } from '@wagmi/core'
import { useAccount } from 'wagmi'
import { formatEther, parseEther } from 'ethers/lib/utils'
import { Message } from '@arco-design/web-react'
import { minus } from '@/utils/bignumber'
import { checkWhitelist } from '@/api/chekcWhitelist'
export const useIDO = () => {
// 钱包状态
const { address, isConnected } = useAccount()
const { chain } = getNetwork()
// 加载状态
const [loading, setLoading] = useState(false)
const [pageLoading, setPageLoading] = useState(true)
const [value, setValue] = useState('') // 文本框
const [paidCount, setPaidCount] = useState(0) // 用户购买量
const [isClaim, setIsClaim] = useState(false) // 用户是否购买
const [userIsWhite, setUserIsWhite] = useState(false) // 用户是否白名单
const [info, setInfo] = useState() // IDO 信息
const { getInfo, paidEth, buy, isClaim: isClaimFn, claim: onClaimFn } = idoContract()
const getMax = () => Number(formatEther(minus(info?.totalGatherEth, info?.totalPaidEth).toString()))
const checkoutUserStatus = () => {
if (!isConnected) {
return false
}
if (chain?.id !== defaultChain.id) { // 检查链是否正确
switchNetwork({ chainId: defaultChain.id })
return false
}
return true
}
// 点击IDO参与
const onBuy = async () => {
if (!checkoutUserStatus()) return
const pric = Number(value || 0)
const max = Number(formatEther(info?.maxEth || 0))
const min = Number(formatEther(info?.minEth || 0))
const diff = getMax()
// 最小参与量只能是差额
if (diff < min) {
if (pric !== diff) {
return Message.error(`Can only participate in ${+diff.toFixed(4)} ETH`)
}
} else {
if (pric > max) {
return Message.error(`Maximum participation ${max} ETH`)
} else if (pric < min) {
return Message.error(`Minimum participation ${min} ETH`)
}
}
try {
setLoading(true)
const { hash } = await buy({
from: address,
value: parseEther(value) // 需要转换成大数
})
await waitForTransaction({ hash, confirmations: 1 }) // 等待交易完成再更新状态
} catch (e) {
console.log(e)
} finally {
setLoading(false)
}
}
const onClaim = async () => {
if (!checkoutUserStatus()) return
try {
setLoading(true)
const { hash } = await onClaimFn({ from: address })
await waitForTransaction({ hash, confirmations: 1 }) // 等待交易完成再更新状态
} catch (e) {
console.log(e)
} finally {
setLoading(false)
}
}
// 初始化获取数据
const init = async () => {
try {
const [info, paidEthCount, isClaim, userIsWhite] = await Promise.all([
getInfo(),
address && paidEth(address),
address && isClaimFn(address),
address && checkWhitelist(address)
])
setInfo(info)
setIsClaim(!!isClaim)
setUserIsWhite(!!userIsWhite)
setPaidCount(Number(formatEther(paidEthCount || 0)))
setPageLoading(false)
} catch (e) {
console.log(e)
}
}
useMount(() => init())
useInterval(() => init(), 3000)
return {
info,
paidCount,
value,
isClaim,
loading,
pageLoading,
userIsWhite,
setValue,
getMax,
onBuy,
onClaim
}
}
3.钱包连接
主要分为两种情况:
- 手机DAPP上的浏览器
- PC端DAPP浏览器
两者的区别在于,手机端大部分浏览器都是可以直接使用 wagm 的 InjectedConnector类连接钱包,因为手机浏览器会在window对象上提供ethereum对象,这样InjectedConnector可以直接连接钱包,PC端上的话可以使用一些React的第三方UII来实现更好看的交互效果,但本质上都差不多,只是为了兼容更多的钱包(但是PC市场上的用户用小狐狸的居多)
- 可以通过一些第三方的媒体查询库来判断使用PC端钱包库还是移动端直连钱包
- 这些钱包库一般都是搭配着
wagmi一起来使用的
3.1针对PC端的React钱包库
Rainbowkit(推荐使用)
入手比较简单,但需要注意版本,如果你的wagmi是^1.0.0版本的则安装最新的rainbowkit,否则你得安装老版本
yarn add @rainbow-me/rainbowkit
这个是版本迁移指南:www.rainbowkit.com/docs/migrat…
ThirdWeb
拥有一套完善的前端以及合约层面的工具生态,GitHub 209颗星
官网:portal.thirdweb.com/react/react…
npm i @thirdweb-dev/react @thirdweb-dev/sdk ethers@^5
Web3React
yarn add web3-react@unstable
NPM地址:www.npmjs.com/package/web…
如何加入比特鹰
目前我们在招聘的岗位有:投研分析师,Python 后端研发工程师,前端研发工程师,AI研发工程师。
可以将简历投递到邮箱 join@bitying.cn