Uniswap / web3-react

A simple, maximally extensible, dependency minimized framework for building modern Ethereum dApps
https://web3-react-mu.vercel.app/
GNU General Public License v3.0
5.55k stars 1.52k forks source link

metaMask.connectEagerly() is not resolving properly #544

Open cryptod00c opened 2 years ago

cryptod00c commented 2 years ago

https://github.com/NoahZinsmeister/web3-react/blob/main/packages/example-next/components/connectorCards/MetaMaskCard.tsx

  // attempt to connect eagerly on mount
  useEffect(() => {
    // void metaMask.connectEagerly()
  }, [])

it looks to be only an issue with MetaMask - where if the user has never connected with MetaMask previously, it attempts to eager connect and fails to resolve off of the "connecting" status - which subsequently (properly, I might add) - doesn't allow the user to manually connect, leaving them stuck. So right now I'm just manually handling whether they have connected previously before I trigger the connectEagerly()

Awesome library and fantastic examples - thanks for open sourcing this. I have a pretty straightforward multi chain wallet connection nav bar if you're at all interested in adding that to the example section...it's open-sourced as well.

vr-devil commented 2 years ago

i have same issue. how do you check the wallet is connected or not? @cryptod00c

cryptod00c commented 2 years ago

@kai-zhong I'm managing that state locally...basically I set a property when the wallet is connected the first time and then check it on load...looks something like this...

  useEffect(() => {
    if (
      !isDisconnected?.includes(WalletEnum.metamask) &&
      previousConnections.includes(WalletEnum.metamask)
    ) {
      void metaMask.connectEagerly();
    }
  }, []);
vr-devil commented 2 years ago

the state is persisted in localStorage?

cryptod00c commented 2 years ago

I have my state persisted in zustand @kai-zhong

import { persist, PersistOptions } from 'zustand/middleware';
vr-devil commented 2 years ago

@cryptod00c ok, thanks.

i take a look at implementation of MetaMask connector.

  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()
      })
  }

and cancelActivation() implementation

  /**
   * Sets activating to true, indicating that an update is in progress.
   *
   * @returns cancelActivation - A function that cancels the activation by setting activating to false,
   * as long as there haven't been any intervening updates.
   */
  function startActivation(): () => void {
    const nullifierCached = ++nullifier

    store.setState({ ...DEFAULT_STATE, activating: true })

    // return a function that cancels the activation iff nothing else has happened
    return () => {
      if (nullifier === nullifierCached) store.setState({ activating: false })
    }
  }

stuck at if (nullifier === nullifierCached) store.setState({ activating: false }). activating state is not set to false properly, and no error is reported.

cryptod00c commented 2 years ago

that should be super helpful to @NoahZinsmeister hopefully - nice research

rtman commented 2 years ago

@cryptod00c can you share your implementation you mentioned earlier?

Awesome library and fantastic examples - thanks for open sourcing this. I have a pretty straightforward multi chain wallet connection nav bar if you're at all interested in adding that to the example section...it's open-sourced as well.

bluenex commented 2 years ago

I have the same issue. Thank you for reporting this.

But I have a question. Why doesn't this happen in the demo site (https://web3-react-mu.vercel.app//)? I try reproducing the same way as what happened locally but the connecting status seems to be correct and the user can connect manually after.

vr-devil commented 2 years ago

Hi, everyone. i have a workaround, i set a timeout in activating stage. In dev mode, activating stage is not stuck, the problem just present in production mode, and i don't know why.

however, example below can resolve my problem, hope to help you.


enum WalletState {
  None,
  Activating,
  Active,
  Inactive,
  HasError,
  ActivatingTimeout
}

function ContainerNode {

  let router = useRouter()
  let web3Hook = useWeb3React()

  let [activatingTimeout, setActivatingTimeout] = useState<any>()
  let [walletState, setWalletState] = useState(WalletState.None)
  let [auth, setAuth] = useState(false)

  let clearTimer = () => {
    console.log('clear timers.')
    activatingTimeout && clearTimeout(activatingTimeout)
  }

  let {asPath} = router
  let {error, isActivating, isActive} = web3Hook

  useEffect(() => {

    console.log(asPath, WalletState[walletState], isActivating, isActive, error)

    if (error) {
      console.log('wallet error.', error)
      setWalletState(WalletState.HasError)
      return;
    }

    switch (walletState) {
      case WalletState.None:
        if (!isActivating) {
          console.log('wallet connect eagerly.')
          web3Hook.connector.connectEagerly?.()
          setActivatingTimeout(setTimeout(() => {
            setWalletState( s => {
              console.log('wallet check activating timeout.', WalletState[s], s === WalletState.Activating)
              if(s == WalletState.Activating) {
                return WalletState.ActivatingTimeout
              }
              return s
            })
          }, 1000 * 5))

          setWalletState(WalletState.Activating)
        }
        break
      case WalletState.Activating:

        if (isActivating) {
          return
        }

        if (isActive) {
          setWalletState(WalletState.Active)
        } else {
          setWalletState(WalletState.Inactive)
        }

        break
      case WalletState.Active:

        if (!isActive) {
          setWalletState(WalletState.Inactive)
          return
        }

        clearTimer()
        // authenticate()
        break
      case WalletState.Inactive:
      case WalletState.ActivatingTimeout:
      default:
        clearTimer()
        router.replace({pathname: '/login', query: {from: router.asPath}}).finally()
    }

    return () => {
      clearTimer()
    }
  }, [asPath, walletState, error, isActivating, isActive])

  return <></>
}
cryptod00c commented 2 years ago

@rtman ☕

https://github.com/cryptod00c/mccCafe https://mcc.cafe