React 连接 MetaMask钱包

4,238 阅读3分钟

安装依赖

在我们开始之前安装必要的包:

$ yarn add @web3-react/core @web3-react/injected-connector 
$ yarn add @ethersproject/providers
  • @web3-react 为React应用程序提供了非常好的 web3 实用程序,以与 web3 兼容的区块链进行通信。
  • @ethersproject/providers 它包含常见的 Provider 类、用于处理提供者的实用函数,并重新导出实现自定义 Provider 所需的许多类和类型。

连接到钱包

第一步是将我们的 React项目 连接到浏览器钱包。首先我们需要创建几个文件。

我们创建了一个 InjectedConnector 并指定了支持的链 id 以包含 Clover 链 id 【1, 3, 4, 5, 42】。

// connector.js

import { InjectedConnector } from '@web3-react/injected-connector'
export const injected = new InjectedConnector({ supportedChainIds: [1, 3, 4, 5, 42] })

hooks.js 提供了几个钩子来帮助连接钱包。

// hooks.js

import { useState, useEffect } from 'react'
import { useWeb3React } from '@web3-react/core'
import { injected } from './connectors'

export function useEagerConnect() {
  const { activate, active } = useWeb3React()

  const [tried, setTried] = useState(false)

  useEffect(() => {
    injected.isAuthorized().then((isAuthorized) => {
      if (isAuthorized) {
        activate(injected, undefined, true).catch(() => {
          setTried(true)
        })
      } else {
        setTried(true)
      }
    })
  }, []) // intentionally only running on mount (make sure it's only mounted once :))

  // if the connection worked, wait until we get confirmation of that to flip the flag
  useEffect(() => {
    if (!tried && active) {
      setTried(true)
    }
  }, [tried, active])

  return tried
}

export function useInactiveListener(suppress = false) {
  const { active, error, activate } = useWeb3React()

  useEffect(() => {
    const { ethereum } = window
    if (ethereum && ethereum.on && !active && !error && !suppress) {
      const handleConnect = () => {
        console.log("Handling 'connect' event")
        activate(injected)
      }
      const handleChainChanged = (chainId) => {
        console.log("Handling 'chainChanged' event with payload", chainId)
        activate(injected)
      }
      const handleAccountsChanged = (accounts) => {
        console.log("Handling 'accountsChanged' event with payload", accounts)
        if (accounts.length > 0) {
          activate(injected)
        }
      }
      const handleNetworkChanged = (networkId) => {
        console.log("Handling 'networkChanged' event with payload", networkId)
        activate(injected)
      }

      ethereum.on('connect', handleConnect)
      ethereum.on('chainChanged', handleChainChanged)
      ethereum.on('accountsChanged', handleAccountsChanged)
      ethereum.on('networkChanged', handleNetworkChanged)

      return () => {
        if (ethereum.removeListener) {
          ethereum.removeListener('connect', handleConnect)
          ethereum.removeListener('chainChanged', handleChainChanged)
          ethereum.removeListener('accountsChanged', handleAccountsChanged)
          ethereum.removeListener('networkChanged', handleNetworkChanged)
        }
      }
    }
  }, [active, error, suppress, activate])
}

Spinner.js 实现了一个简单的 Spinner 组件,可以用作加载状态。

// Spinner.js

import React from 'react'

export function Spinner(props) {
  const { color, ...rest } = props
  return (
    <svg width="38" height="38" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg" stroke={color} {...rest}>
      <g fill="none" fillRule="evenodd">
        <g transform="translate(1 1)" strokeWidth="2">
          <circle strokeOpacity=".5" cx="18" cy="18" r="18" />
          <path d="M36 18c0-9.94-8.06-18-18-18">
            <animateTransform
              attributeName="transform"
              type="rotate"
              from="0 18 18"
              to="360 18 18"
              dur="1s"
              repeatCount="indefinite"
            />
          </path>
        </g>
      </g>
    </svg>
  )
}

更新 App.js 以将其内容设置为:

我们将 Web3ReactProvider 添加到应用程序的根目录,并在 App 组件中包含 useEagerConnect 钩子。我们还包括 ChainId 组件,它将显示连接的链 ID,如果未检测到连接,则显示未连接。

// App.js

import { Web3ReactProvider, useWeb3React, } from '@web3-react/core'
import { Web3Provider } from '@ethersproject/providers'
import { useEagerConnect, useInactiveListener } from './hooks'

import './App.css';

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

function ChainId() {
  const { chainId, library } = useWeb3React()

  return (
    <div className="ChainIdWrapper">
      <span>Chain Id</span>
      <span role="img" aria-label="chain"></span>
      <span className="ChainIdText">{chainId ?? 'Not Connected'}</span>
    </div>
  )
}

function App() {
  const triedEager = useEagerConnect()

  return (
      <div className="App">
        <header className="App-header">
          <h1>Counter Example </h1>
          <ChainId/>
          <p>
            Current value: n/a
        </p>
          <button className="CounterButton">Inc Counter</button>
          <button className="CounterButton">Dec Counter</button>
        </header>
      </div>
  );
}


export default function() {
  return (
    <Web3ReactProvider getLibrary={getLibrary}>
      <App />
    </Web3ReactProvider>
  )
}

启动应用程序你会在ChainId组件中看到not connected。这是 find 因为我们还没有实现连接逻辑。 但是你可以通过从 MetaMask 手动连接到 web 应用程序来测试它,尝试弄清楚如何自己做。

添加连接按钮

现在让我们添加一个按钮来触发钱包连接对话框。

//App.js

import React from 'react'
import { Spinner } from './Spiner'
import { injected } from './connectors'

function ConnectChain(props) {
  const context = useWeb3React()
  const { connector, library, chainId, account, activate, deactivate, active, error } = context

  const [activatingConnector, setActivatingConnector] = React.useState()
  React.useEffect(() => {
    if (activatingConnector && activatingConnector === connector) {
      setActivatingConnector(undefined)
    }
  }, [activatingConnector, connector])

  const activating = injected === activatingConnector
  const connected = injected === connector
  const disabled = !props.triedEager || !!activatingConnector || !!error

  useInactiveListener(!props.triedEager || !!activatingConnector)

  let isDisconnect = !error && chainId
  const buttonText = isDisconnect ? 'Disconnect' : (activating ? 'Connectting' : 'Connect' )

  return (
    <button
      style={{
        borderColor: activating ? 'orange' : connected ? 'green' : 'unset',
        cursor: disabled ? 'unset' : 'pointer',
        position: 'relative',
      }}
      className="ConnectButton"
      disabled={disabled}
      onClick={() => {
        if (!isDisconnect) {
          setActivatingConnector(injected)
          activate(injected)
        } else {
          deactivate()
        }
      }}
    >
      <div
        style={{
          position: 'absolute',
          top: '0',
          left: '0',
          height: '100%',
          display: 'flex',
          alignItems: 'center',
          color: 'black',
          margin: '0 0 0 1rem'
        }}
      >
        {activating && <Spinner color={'red'} style={{ height: '50%', marginLeft: '-1rem' }} />}
      </div>
      { buttonText }
    </button>
  )
}

然后添加按钮的 css 类文件

.ConnectButton {
  background-color: #4CAF50;
  border: none;
  color: white;
  margin-top: 15px;
  margin-bottom: 15px;
  padding: 15px 32px;
  text-align: center;
  text-decoration: none;
  display: inline-block;
  font-size: 16px;
  width: 200px;
}

.ConnectButton:disabled {
  background-color: grey;
  color: black;
}

ConnectChain 组件简单地呈现一个按钮,如果它没有连接,点击它会触发 web3 连接对话框。

<ConnectChain triedEager={triedEager} />

重新加载页面,连接按钮将出现,您可以单击它打开连接对话框。 连接到钱包后,单击按钮将断开它。