下载

arrow_down

构建

arrow_down

更多

arrow_down
activityactivityactivityactivity
  • themelight
  • languageIcon

  • menu
Skip to Content

Web3-React

Web3-React 是一个简单、高度可扩展且依赖最少的框架,用于构建现代以太坊 dApp 的连接组件。

快速开始

1.配置连接器:在 Web3-React 包中添加 BitgetWallet

import detectEthereumProvider from "@akkafinance/bitkeep-detect-provider"; import type { Actions, AddEthereumChainParameter, Provider, ProviderConnectInfo, ProviderRpcError, WatchAssetParameters, } from "@web3-react/types"; import { Connector } from "@web3-react/types"; type BitGetWalletProvider = Provider & { isBitGetWallet?: boolean; isConnected?: () => boolean; providers?: BitGetWalletProvider[]; }; export class NoBitGetWalletError extends Error { public constructor() { super("BitGetWallet not installed"); this.name = NoBitGetWalletError.name; Object.setPrototypeOf(this, NoBitGetWalletError.prototype); } } function parseChainId(chainId: string) { return Number.parseInt(chainId, 16); } /** * @param options - 传递给 `./detect-provider` 的选项 * @param onError - 处理从 eventListeners 抛出的错误的处理器。 */ export interface BitGetWalletpConstructorArgs { actions: Actions; options?: Parameters<typeof detectEthereumProvider>[0]; onError?: (error: Error) => void; } export class BitGetWallet extends Connector { /** {@inheritdoc Connector.provider} */ public provider?: BitGetWalletProvider; private readonly options?: Parameters<typeof detectEthereumProvider>[0]; private eagerConnection?: Promise<void>; constructor({ actions, options, onError }: BitGetWalletConstructorArgs) { super(actions, onError); this.options = options; } private async isomorphicInitialize(): Promise<void> { if (this.eagerConnection) return; return (this.eagerConnection = import( "@akkafinance/bitkeep-detect-provider" ).then(async (m) => { const provider = await m.default(this.options); if (provider) { this.provider = provider as unknown as BitGetWalletProvider; if (this.provider.providers?.length) { this.provider = this.provider.providers.find((p) => p.isBitGetWallet) ?? this.provider.providers[0]; } this.provider.on( "connect", ({ chainId }: ProviderConnectInfo): void => { this.actions.update({ chainId: parseChainId(chainId) }); } ); this.provider.on("disconnect", (error: ProviderRpcError): void => { this.actions.resetState(); this.onError?.(error); }); this.provider.on("chainChanged", (chainId: string): void => { this.actions.update({ chainId: parseChainId(chainId) }); }); this.provider.on("accountsChanged", (accounts: string[]): void => { if (accounts.length === 0) { // 通过断开连接来处理这种边缘情况 this.actions.resetState(); } else { this.actions.update({ accounts }); } }); } })); } /** {@inheritdoc Connector.connectEagerly} */ public async connectEagerly(): Promise<void> { const cancelActivation = this.actions.startActivation(); await this.isomorphicInitialize(); if (!this.provider) return cancelActivation(); return Promise.all([ this.provider.request({ method: "eth_chainId" }) as Promise<string>, this.provider.request({ method: "eth_accounts" }) as Promise<string[]>, ]) .then(([chainId, accounts]) => { if (accounts.length) { this.actions.update({ chainId: parseChainId(chainId), accounts }); } else { throw new Error("No accounts returned"); } }) .catch((error) => { console.debug("Could not connect eagerly", error); // 我们应该能够在这里使用 `cancelActivation`,但在移动设备上,metamask 发出 'connect' // 事件,意味着 chainId 被更新,而 cancelActivation 不起作用,因为发生了中间 // 更新,所以我们重置状态 this.actions.resetState(); }); } /** * 启动连接。 * * @param desiredChainIdOrChainParameters - 如果定义,表示要连接的目标链。如果用户已经 * 连接到此链,则不会采取其他步骤。否则,如果满足以下两个条件之一,将提示用户切换 * 到该链:要么他们已经在扩展中添加了该链,要么参数是 AddEthereumChainParameter 类型, * 在这种情况下,将首先提示用户使用指定参数添加链,然后再提示切换。 */ public async activate( desiredChainIdOrChainParameters?: number | AddEthereumChainParameter ): Promise<void> { let cancelActivation: () => void; if (!this.provider?.isConnected?.()) cancelActivation = this.actions.startActivation(); return this.isomorphicInitialize() .then(async () => { if (!this.provider) throw new NoBitGetWalletError(); return Promise.all([ this.provider.request({ method: "eth_chainId" }) as Promise<string>, this.provider.request({ method: "eth_requestAccounts" }) as Promise< string[] >, ]).then(([chainId, accounts]) => { const receivedChainId = parseChainId(chainId); const desiredChainId = typeof desiredChainIdOrChainParameters === "number" ? desiredChainIdOrChainParameters : desiredChainIdOrChainParameters?.chainId; // 如果没有目标链,或者它等于接收到的链,则更新 if (!desiredChainId || receivedChainId === desiredChainId) return this.actions.update({ chainId: receivedChainId, accounts }); const desiredChainIdHex = `0x${desiredChainId.toString(16)}`; // 如果我们在这里,我们可以尝试切换网络 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return this.provider!.request({ method: "wallet_switchEthereumChain", params: [{ chainId: desiredChainIdHex }], }) .catch((error: ProviderRpcError) => { if ( error.code === 4902 && typeof desiredChainIdOrChainParameters !== "number" ) { // 如果我们在这里,我们可以尝试添加新网络 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return this.provider!.request({ method: "wallet_addEthereumChain", params: [ { ...desiredChainIdOrChainParameters, chainId: desiredChainIdHex, }, ], }); } throw error; }) .then(() => this.activate(desiredChainId)); }); }) .catch((error) => { cancelActivation?.(); throw error; }); } /** {@inheritdoc Connector.deactivate} */ public deactivate(): void { this.actions.resetState(); } public async watchAsset({ address, symbol, decimals, image, }: WatchAssetParameters): Promise<true> { if (!this.provider) throw new Error("No provider"); return this.provider .request({ method: "wallet_watchAsset", params: { type: "ERC20", // 最初只支持 ERC20,但最终会支持更多标准 options: { address, // 代币合约的地址 symbol, // 代币符号或简称,最多 11 个字符 decimals, // 代币小数位数 image, // 显示在钱包中的代币图像的字符串 url }, }, }) .then((success) => { if (!success) throw new Error("Method not supported."); return true; }); } }

2.Use this connector in your application:

import { Web3ReactHooks, Web3ReactProvider } from '@web3-react/core' import { BitGetWallet } from './bitgetWallet' import { hooks as metaMaskHooks, metaMask } from './metaMask' const bitget = new BitGetWallet({ actions: bitgetHooks.useSelectedActions(connector), onError: (error: Error) => { console.debug(`web3-react error: ${error}`) }, }) const connectors: [BitGetWallet, Web3ReactHooks][] = [ [bitget, bitgetHooks], [metaMask, metaMaskHooks], ] export default function App() { return ( <Web3ReactProvider connectors={connectors}> <div className='App'> <header className='App-header'> <h1>BitGet Wallet + Web3 React</h1> </header> <main> <Profile /> </main> </div> </Web3ReactProvider> ) }

3.Connect Wallet:

const { connector, hooks, store } = useSelectedConnector(connectors) const { useSelectedAccount, useSelectedChainId, useSelectedIsActive } = hooks const isActive = useSelectedIsActive(connector) const account = useSelectedAccount(connector) const chainId = useSelectedChainId(connector) const handleToggleConnect = useCallback(() => { if (isActive) { if (connector?.deactivate) { void connector.deactivate() } else { void connector.resetState() } } else if (!pendingConnection) { setPendingConnection(true) connector .activate(1) .then(() => setPendingConnection(false)) .catch(() => setPendingConnection(false)) } }, [connector, isActive, pendingConnection])

连接钱包

使用 Hook

import { useWeb3React } from '@web3-react/core' import { injected, walletconnect } from './connectors' function WalletConnection() { const { activate, deactivate, active, account, library, connector, error } = useWeb3React() const connectInjected = async () => { try { await activate(injected) } catch (ex) { console.log(ex) } } const connectWalletConnect = async () => { try { await activate(walletconnect) } catch (ex) { console.log(ex) } } const disconnect = () => { try { deactivate() } catch (ex) { console.log(ex) } } if (active) { return ( <div> <p>已连接账户: {account}</p> <p>连接器: {connector?.constructor.name}</p> <button onClick={disconnect}>断开连接</button> </div> ) } return ( <div> <button onClick={connectInjected}>连接注入钱包</button> <button onClick={connectWalletConnect}>连接 WalletConnect</button> {error && <p>错误: {error.message}</p>} </div> ) }

获取账户信息

import { useWeb3React } from '@web3-react/core' import { useEffect, useState } from 'react' function AccountInfo() { const { account, library } = useWeb3React() const [balance, setBalance] = useState<string>('') useEffect(() => { if (account && library) { library.getBalance(account).then((balance) => { setBalance(library.utils.formatEther(balance)) }) } }, [account, library]) if (!account) { return <div>请先连接钱包</div> } return ( <div> <p>地址: {account}</p> <p>余额: {balance} ETH</p> </div> ) }

发送交易

import { useWeb3React } from '@web3-react/core' import { parseEther } from '@ethersproject/units' function SendTransaction() { const { account, library } = useWeb3React() const sendETH = async () => { if (!account || !library) return try { const signer = library.getSigner(account) const transaction = await signer.sendTransaction({ to: '0x...', value: parseEther('0.01'), }) console.log('交易哈希:', transaction.hash) await transaction.wait() console.log('交易确认') } catch (error) { console.error('交易失败:', error) } } return ( <button onClick={sendETH} disabled={!account}> 发送 0.01 ETH </button> ) }

合约交互

import { useWeb3React } from '@web3-react/core' import { Contract } from '@ethersproject/contracts' import { useEffect, useState } from 'react' const ERC20_ABI = [ 'function balanceOf(address owner) view returns (uint256)', 'function transfer(address to, uint256 amount) returns (bool)', 'function symbol() view returns (string)', 'function decimals() view returns (uint8)', ] function TokenContract() { const { account, library } = useWeb3React() const [contract, setContract] = useState<Contract | null>(null) const [balance, setBalance] = useState<string>('') const [symbol, setSymbol] = useState<string>('') useEffect(() => { if (library && account) { const tokenContract = new Contract( '0x...', // 代币合约地址 ERC20_ABI, library.getSigner(account) ) setContract(tokenContract) } }, [library, account]) useEffect(() => { const fetchTokenInfo = async () => { if (contract && account) { try { const [balance, symbol, decimals] = await Promise.all([ contract.balanceOf(account), contract.symbol(), contract.decimals(), ]) setBalance((balance / 10 ** decimals).toString()) setSymbol(symbol) } catch (error) { console.error('获取代币信息失败:', error) } } } fetchTokenInfo() }, [contract, account]) const transferToken = async () => { if (!contract) return try { const tx = await contract.transfer('0x...', parseEther('1')) await tx.wait() console.log('转账成功') } catch (error) { console.error('转账失败:', error) } } return ( <div> <p> 代币余额: {balance} {symbol} </p> <button onClick={transferToken}>转账 1 {symbol}</button> </div> ) }

签名消息

import { useWeb3React } from '@web3-react/core' function SignMessage() { const { account, library } = useWeb3React() const signMessage = async () => { if (!account || !library) return try { const signer = library.getSigner(account) const message = 'Hello from Bitget Wallet!' const signature = await signer.signMessage(message) console.log('消息:', message) console.log('签名:', signature) } catch (error) { console.error('签名失败:', error) } } return ( <button onClick={signMessage} disabled={!account}> 签名消息 </button> ) }

网络切换

import { useWeb3React } from '@web3-react/core' function NetworkSwitcher() { const { library, account } = useWeb3React() const switchToPolygon = async () => { if (!library || !account) return try { await library.provider.request({ method: 'wallet_switchEthereumChain', params: [{ chainId: '0x89' }], // Polygon }) } catch (switchError: any) { // 如果网络不存在,尝试添加 if (switchError.code === 4902) { try { await library.provider.request({ method: 'wallet_addEthereumChain', params: [ { chainId: '0x89', chainName: 'Polygon', nativeCurrency: { name: 'MATIC', symbol: 'MATIC', decimals: 18, }, rpcUrls: ['https://polygon-rpc.com/'], blockExplorerUrls: ['https://polygonscan.com/'], }, ], }) } catch (addError) { console.error('添加网络失败:', addError) } } } } return <button onClick={switchToPolygon}>切换到 Polygon</button> }

监听事件

import { useWeb3React } from '@web3-react/core' import { useEffect } from 'react' function EventListeners() { const { library, account, activate } = useWeb3React() useEffect(() => { const { ethereum } = window as any if (ethereum) { const handleAccountsChanged = (accounts: string[]) => { console.log('账户变化:', accounts) if (accounts.length === 0) { // 用户断开连接 } else { // 用户切换账户 activate(injected) } } const handleChainChanged = (chainId: string) => { console.log('网络变化:', chainId) activate(injected) } ethereum.on('accountsChanged', handleAccountsChanged) ethereum.on('chainChanged', handleChainChanged) return () => { ethereum.removeListener('accountsChanged', handleAccountsChanged) ethereum.removeListener('chainChanged', handleChainChanged) } } }, [activate]) return <div>事件监听器已激活</div> }

完整示例

import React from 'react' import { Web3ReactProvider, useWeb3React } from '@web3-react/core' import { Web3Provider } from '@ethersproject/providers' import { InjectedConnector } from '@web3-react/injected-connector' const injected = new InjectedConnector({ supportedChainIds: [1, 3, 4, 5, 42, 56, 137], }) function getLibrary(provider: any): Web3Provider { const library = new Web3Provider(provider) library.pollingInterval = 12000 return library } function DApp() { const { activate, deactivate, active, account, library, error } = useWeb3React() const connect = async () => { try { await activate(injected) } catch (ex) { console.log(ex) } } const disconnect = () => { try { deactivate() } catch (ex) { console.log(ex) } } if (error) { return <div>错误: {error.message}</div> } if (!active) { return ( <div> <button onClick={connect}>连接钱包</button> </div> ) } return ( <div> <p>已连接: {account}</p> <button onClick={disconnect}>断开连接</button> </div> ) } function App() { return ( <Web3ReactProvider getLibrary={getLibrary}> <DApp /> </Web3ReactProvider> ) } export default App

注意事项

  1. 连接器配置: 根据您的需求配置正确的连接器
  2. 错误处理: 始终处理连接和交易错误
  3. 事件监听: 监听账户和网络变化事件
  4. 性能优化: 合理设置轮询间隔和缓存策略

相关资源

Last updated on