Commit db0d3cf3 authored by Vignesh Mohankumar's avatar Vignesh Mohankumar Committed by GitHub

feat: upgrade to web3-react v8 (#3759)

* initial

* comment more stuff out for now

* more changes

* more temp

* remove walletconnect bug logic

* switch to provider not connector

* remove fortmatic

* remove some usage of network connector

* fix initialize connector

* more changes

* remove switch to network

* connect eagerly

* active -> isActive

* add initial option cards

* upgrade web3-react

* delete tryActivation

* delete pending view, reset option code

* fix hooks

* library -> provider

* rm getLibrary

* eagerly connect

* comment all this code for now

* add back app

* dont connect eagerly here

* deactivate

* switchToNetwork

* switch to useWeb3React

* rm Web3ReactManager

* add back og wallet modal code

* switch back to old option logic

* add account logic back

* add back more network switch logic

* Revert "switch to useWeb3React"

This reverts commit 08ac6319d40207c31c72bf3b16b9f22223fc1ddc.

* add back skip disconnect logic

* check for network connector

* use promise.then again

* remove unnecessary pending error logic

* reset useAddTokenToMetamask

* upgrade packages

* use watchAsset

* add gnosis

* rm fortmatic

* close on disconnection

* add Wallet enum

* remove fortmatic imports

* add wallet state

* set/clear override wallet

* resolve empty

* remove some wallet modal view logic

* useWeb3ReactListener

* move to use effect

* add setwalletoverride in deactivate for now

* start to fix the wallet modal bug

* back button should open options

* connect eagerly to all

* Revert "add setwalletoverride in deactivate for now"

This reverts commit fbc990a9245c68460b1f29e368174b5327aa586c.

* useSelectedIsActive

* switch the enum to not be a bug

* actually dispatch the wallet override

* remove connection useEffect for now

* Revert "remove connection useEffect for now"

This reverts commit 0b92eee6894586e08079c1e4092e098b579cb768.

* add back the activation useeffect

* handle resetting eagerly connecting

* dont disconnect from coinbase wallet

* disconnect except for coinbase wallet, bc their reload breaks things

* handle eager activation edge case

* backfill wallet override

* rename wrapper components

* update test

* network if override undefined

* npx deduplicate

* comment for why coinbase wallet special cased

* connectorPrevious -> previousConnector

* Array.find instead of forEach

* useState instead of useReducer

* add comments and simplify

* Web3Wrapper component

* add type guard

* check for watchAsset

* revert Option.tsx changes

* set -> updateWalletOverride

* generalize connector type usage

* rm comment

* eagerlyConnect comment

* null -> undefined

* add comment for wallet override

* add back pendingError logic

* merge conflicts

* remove provider dep

* add back connect a wallet

* move active prop out of base props

* add back account details test

* add type of isActiveMap

* add back eslint

* add TODO

* Web3Provider

* return null from Updater

* update comment

* integration tests initial

* try updating test

* check for gnosis safe

* fix gnosis safe check

* pr comments

* pr comments

* don't eagerly connect to any wallets other than gnosis or walletOverride

* remove unused branch

* pendingError from hook

* eslint-disable-line

* try connecting to wallets if not backfilled

* move eager connection logic

* remove connect eagerly set logic

* disconnect on change

* simplify ConnectorState

* better solution for changing wallet priority

* merge fixes

* fix tests

* try fixing test again

* add comment

* add fortmatic back

* set walletOverride for fortmatic

* hide other chains

* handle eager connection

* connect everything eagerly if not backfilled

* fix chain switching

* async

* rm error console

* fortmatic update

* log errors

* don't eagerly connect to fortmatic

* onSelectChain + switchChain

* typo

* don't disconnect from coinbase wallet for now

* upgrade web3-react

* close on disconnection/connection again

* simplify account change check

* comment fix

* comment

* fortmatic icon

* comment for fortmatic in network selector

* consolidate useEffect hooks in walletmodal for connection/disconnection

* switchToChain

* comment

* isEagerlyConnecting instead of eagerlyConnectingWallets

* update web3-react

* close modal fortmatic

* remove error log

* chainIdNotAllowed

* handle useToken

* update SupportedChainId

* move if statements around

* move to wallet reducer

* close as error

* export fix

* add back history change

* add back popular

* fortmatic key

* persist wallet

* remove eagerly connect

* call connect eagerly

* handle modal errors

* handle fortmatic close properly

* connector error changes

* go back to options

* change redux wallets

* simplify reducer

* fix eagerly connect / disconnect

* remove account change hook

* simplify connect eagerly

* remove unused var

* revert chain

* walletOverride reducer

* update web3-react

* fix compile errors for now

* show disconnect button

* clear pending connector

* clear error state

* add back skip toggle check

* MAINNET provider for ENS

* add coinbase wallet sdk

* fix test

* add back style but fix syntax highlighting

* dont create separate json rpc provider

* don't use selected hooks

* dont export

* dispatch first

* useConnectors

* comment

* simplify activeMap

* useIsActiveMap

* prettier

* prop change

* move comment

* useCallback

* coinbase wallet link fix

* rm ModalWallet type

* reportError

* isChainAllowed

* NETWORK_SELECTOR_CHAINS

* mainnet provider

* remove unused wallet views

* add back default case

* selected wallet

* comment change

* !chainAllowed

* rm ensResolver

* rm forEach

* re-define reportError

* move effects arounds

* change error message for switching chain

* simplify Web3Provider

* delete use isActive map

* fix test?

* rm disconnect test for now

* error message updates

* const -> function

* move fn

* undo changes for showing connect wallet state

* clear error before activating

* remove special case for fortmatic error

* backfillable/selectable wallets

* log wallet

* Revert "rm disconnect test for now"

This reverts commit 225bc7dc5622ae918d8a8b70e4425c648d1a1fac.

* check if account exists

* unused dep

* remove reload piece of test

* update connect a wallet default state

* headerRow
parent ace4276b
REACT_APP_INFURA_KEY="4bf032f2d38a4ed6bb975b80d6340847" REACT_APP_INFURA_KEY="4bf032f2d38a4ed6bb975b80d6340847"
REACT_APP_LOCALES="locales" REACT_APP_FORTMATIC_KEY="pk_live_357F77728B8EB880"
\ No newline at end of file REACT_APP_LOCALES="locales"
import { TEST_ADDRESS_NEVER_USE_SHORTENED } from '../support/ethereum'
describe('Landing Page', () => { describe('Landing Page', () => {
beforeEach(() => cy.visit('/')) beforeEach(() => cy.visit('/'))
it('loads swap page', () => { it('loads swap page', () => {
...@@ -15,9 +13,4 @@ describe('Landing Page', () => { ...@@ -15,9 +13,4 @@ describe('Landing Page', () => {
cy.get('#pool-nav-link').click() cy.get('#pool-nav-link').click()
cy.url().should('include', '/pool') cy.url().should('include', '/pool')
}) })
it('is connected', () => {
cy.get('#web3-status-connected').click()
cy.get('#web3-account-identifier-row').contains(TEST_ADDRESS_NEVER_USE_SHORTENED)
})
}) })
import { TEST_ADDRESS_NEVER_USE_SHORTENED } from '../support/ethereum'
describe('Wallet', () => {
before(() => {
cy.visit('/')
})
it('displays account details', () => {
cy.get('#web3-status-connected').contains(TEST_ADDRESS_NEVER_USE_SHORTENED).click()
})
it('displays account view in wallet modal', () => {
cy.get('#web3-account-identifier-row').contains(TEST_ADDRESS_NEVER_USE_SHORTENED)
})
it('changes back to the options grid', () => {
cy.get('[data-cy=wallet-change]').click()
cy.get('[data-cy=option-grid]').should('exist')
})
it('selects injected wallet option', () => {
cy.contains('Injected').click()
cy.get('#web3-account-identifier-row').contains(TEST_ADDRESS_NEVER_USE_SHORTENED)
})
it('shows connect buttons after disconnect', () => {
cy.get('[data-cy=wallet-disconnect]').click()
cy.get('[data-cy=option-grid]').should('exist')
})
})
...@@ -54,6 +54,7 @@ ...@@ -54,6 +54,7 @@
] ]
}, },
"dependencies": { "dependencies": {
"@coinbase/wallet-sdk": "^3.2.0",
"@ethersproject/experimental": "^5.4.0", "@ethersproject/experimental": "^5.4.0",
"@fontsource/ibm-plex-mono": "^4.5.1", "@fontsource/ibm-plex-mono": "^4.5.1",
"@fontsource/inter": "^4.5.1", "@fontsource/inter": "^4.5.1",
...@@ -115,13 +116,17 @@ ...@@ -115,13 +116,17 @@
"@uniswap/v3-core": "1.0.0", "@uniswap/v3-core": "1.0.0",
"@uniswap/v3-periphery": "^1.1.1", "@uniswap/v3-periphery": "^1.1.1",
"@uniswap/v3-sdk": "^3.8.2", "@uniswap/v3-sdk": "^3.8.2",
"@web3-react/core": "^8.0.23-beta.0", "@walletconnect/ethereum-provider": "1.7.1",
"@web3-react/eip1193": "^8.0.18-beta.0", "@web3-react/coinbase-wallet": "^8.0.33-beta.0",
"@web3-react/empty": "^8.0.12-beta.0", "@web3-react/core": "^8.0.33-beta.0",
"@web3-react/metamask": "^8.0.19-beta.0", "@web3-react/eip1193": "^8.0.25-beta.0",
"@web3-react/types": "^8.0.12-beta.0", "@web3-react/empty": "^8.0.19-beta.0",
"@web3-react/url": "^8.0.17-beta.0", "@web3-react/gnosis-safe": "^8.0.5-beta.0",
"@web3-react/walletconnect": "^8.0.26-beta.0", "@web3-react/metamask": "^8.0.26-beta.0",
"@web3-react/network": "^8.0.26-beta.0",
"@web3-react/types": "^8.0.19-beta.0",
"@web3-react/url": "^8.0.24-beta.0",
"@web3-react/walletconnect": "^8.0.34-beta.0",
"ajv": "^6.12.3", "ajv": "^6.12.3",
"array.prototype.flat": "^1.2.4", "array.prototype.flat": "^1.2.4",
"array.prototype.flatmap": "^1.2.4", "array.prototype.flatmap": "^1.2.4",
...@@ -140,6 +145,7 @@ ...@@ -140,6 +145,7 @@
"eslint-plugin-unused-imports": "^2.0.0", "eslint-plugin-unused-imports": "^2.0.0",
"ethers": "^5.1.4", "ethers": "^5.1.4",
"firebase": "^9.1.3", "firebase": "^9.1.3",
"fortmatic": "^2.4.0",
"graphql": "^15.5.0", "graphql": "^15.5.0",
"graphql-request": "^3.4.0", "graphql-request": "^3.4.0",
"immer": "^9.0.6", "immer": "^9.0.6",
...@@ -187,14 +193,6 @@ ...@@ -187,14 +193,6 @@
"use-resize-observer": "^8.0.0", "use-resize-observer": "^8.0.0",
"wcag-contrast": "^3.0.0", "wcag-contrast": "^3.0.0",
"web-vitals": "^2.1.0", "web-vitals": "^2.1.0",
"web3-react-abstract-connector": "npm:@web3-react/abstract-connector@^6.0.7",
"web3-react-core": "npm:@web3-react/core@^6.0.9",
"web3-react-fortmatic-connector": "npm:@web3-react/fortmatic-connector@^6.0.9",
"web3-react-injected-connector": "npm:@web3-react/injected-connector@^6.0.7",
"web3-react-types": "npm:@web3-react/types@^6.0.7",
"web3-react-walletconnect-connector": "npm:@web3-react/walletconnect-connector@^7.0.2-alpha.0",
"web3-react-walletlink-connector": "npm:@web3-react/walletlink-connector@^6.2.13",
"wicg-inert": "^3.1.1",
"workbox-core": "^6.1.0", "workbox-core": "^6.1.0",
"workbox-navigation-preload": "^6.1.0", "workbox-navigation-preload": "^6.1.0",
"workbox-precaching": "^6.1.0", "workbox-precaching": "^6.1.0",
......
...@@ -5,11 +5,11 @@ import useActiveWeb3React from 'hooks/useActiveWeb3React' ...@@ -5,11 +5,11 @@ import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useCallback, useContext } from 'react' import { useCallback, useContext } from 'react'
import { ExternalLink as LinkIcon } from 'react-feather' import { ExternalLink as LinkIcon } from 'react-feather'
import { useAppDispatch } from 'state/hooks' import { useAppDispatch } from 'state/hooks'
import { updateSelectedWallet } from 'state/user/reducer'
import styled, { ThemeContext } from 'styled-components/macro' import styled, { ThemeContext } from 'styled-components/macro'
import { AbstractConnector } from 'web3-react-abstract-connector'
import { ReactComponent as Close } from '../../assets/images/x.svg' import { ReactComponent as Close } from '../../assets/images/x.svg'
import { injected, walletlink } from '../../connectors' import { coinbaseWallet, injected } from '../../connectors'
import { SUPPORTED_WALLETS } from '../../constants/wallet' import { SUPPORTED_WALLETS } from '../../constants/wallet'
import { clearAllTransactions } from '../../state/transactions/reducer' import { clearAllTransactions } from '../../state/transactions/reducer'
import { ExternalLink, LinkStyledButton, ThemedText } from '../../theme' import { ExternalLink, LinkStyledButton, ThemedText } from '../../theme'
...@@ -177,7 +177,7 @@ const IconWrapper = styled.div<{ size?: number }>` ...@@ -177,7 +177,7 @@ const IconWrapper = styled.div<{ size?: number }>`
`}; `};
` `
function WrappedStatusIcon({ connector }: { connector: AbstractConnector | Connector }) { function WrappedStatusIcon({ connector }: { connector: Connector }) {
return ( return (
<IconWrapper size={16}> <IconWrapper size={16}>
<StatusIcon connector={connector} /> <StatusIcon connector={connector} />
...@@ -265,12 +265,16 @@ export default function AccountDetails({ ...@@ -265,12 +265,16 @@ export default function AccountDetails({
<AccountGroupingRow> <AccountGroupingRow>
{formatConnectorName()} {formatConnectorName()}
<div> <div>
{connector !== injected && connector !== walletlink && ( {/* Coinbase Wallet reloads the page right now, which breaks the selectedWallet from being set properly on localStorage */}
{connector !== coinbaseWallet && (
<WalletAction <WalletAction
style={{ fontSize: '.825rem', fontWeight: 400, marginRight: '8px' }} style={{ fontSize: '.825rem', fontWeight: 400, marginRight: '8px' }}
onClick={() => { onClick={() => {
;(connector as any).close() connector.deactivate ? connector.deactivate() : connector.resetState()
dispatch(updateSelectedWallet({ wallet: undefined }))
openOptions()
}} }}
data-cy="wallet-disconnect"
> >
<Trans>Disconnect</Trans> <Trans>Disconnect</Trans>
</WalletAction> </WalletAction>
...@@ -280,6 +284,7 @@ export default function AccountDetails({ ...@@ -280,6 +284,7 @@ export default function AccountDetails({
onClick={() => { onClick={() => {
openOptions() openOptions()
}} }}
data-cy="wallet-change"
> >
<Trans>Change</Trans> <Trans>Change</Trans>
</WalletAction> </WalletAction>
......
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { getWalletForConnector } from 'connectors'
import { CHAIN_INFO } from 'constants/chainInfo' import { CHAIN_INFO } from 'constants/chainInfo'
import { CHAIN_IDS_TO_NAMES, SupportedChainId } from 'constants/chains' import { CHAIN_IDS_TO_NAMES, SupportedChainId } from 'constants/chains'
import useActiveWeb3React from 'hooks/useActiveWeb3React' import useActiveWeb3React from 'hooks/useActiveWeb3React'
...@@ -11,12 +12,12 @@ import { ArrowDownCircle, ChevronDown } from 'react-feather' ...@@ -11,12 +12,12 @@ import { ArrowDownCircle, ChevronDown } from 'react-feather'
import { useHistory } from 'react-router-dom' import { useHistory } from 'react-router-dom'
import { useModalOpen, useToggleModal } from 'state/application/hooks' import { useModalOpen, useToggleModal } from 'state/application/hooks'
import { addPopup, ApplicationModal } from 'state/application/reducer' import { addPopup, ApplicationModal } from 'state/application/reducer'
import { useAppDispatch } from 'state/hooks'
import { updateWalletError } from 'state/wallet/reducer'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import { ExternalLink, MEDIA_WIDTHS } from 'theme' import { ExternalLink, MEDIA_WIDTHS } from 'theme'
import { replaceURLParam } from 'utils/routes' import { replaceURLParam } from 'utils/routes'
import { isChainAllowed, switchChain } from 'utils/switchChain'
import { useAppDispatch } from '../../state/hooks'
import { switchToNetwork } from '../../utils/switchToNetwork'
const ActiveRowLinkList = styled.div` const ActiveRowLinkList = styled.div`
display: flex; display: flex;
...@@ -184,8 +185,8 @@ function Row({ ...@@ -184,8 +185,8 @@ function Row({
targetChain: SupportedChainId targetChain: SupportedChainId
onSelectChain: (targetChain: number) => void onSelectChain: (targetChain: number) => void
}) { }) {
const { library, chainId } = useActiveWeb3React() const { provider, chainId } = useActiveWeb3React()
if (!library || !chainId) { if (!provider || !chainId) {
return null return null
} }
const active = chainId === targetChain const active = chainId === targetChain
...@@ -257,8 +258,16 @@ const getChainNameFromId = (id: string | number) => { ...@@ -257,8 +258,16 @@ const getChainNameFromId = (id: string | number) => {
return CHAIN_IDS_TO_NAMES[id as SupportedChainId] || '' return CHAIN_IDS_TO_NAMES[id as SupportedChainId] || ''
} }
const NETWORK_SELECTOR_CHAINS = [
SupportedChainId.MAINNET,
SupportedChainId.POLYGON,
SupportedChainId.OPTIMISM,
SupportedChainId.ARBITRUM_ONE,
]
export default function NetworkSelector() { export default function NetworkSelector() {
const { chainId, library } = useActiveWeb3React() const dispatch = useAppDispatch()
const { chainId, provider, connector } = useActiveWeb3React()
const parsedQs = useParsedQueryString() const parsedQs = useParsedQueryString()
const { urlChain, urlChainId } = getParsedChainId(parsedQs) const { urlChain, urlChainId } = getParsedChainId(parsedQs)
const prevChainId = usePrevious(chainId) const prevChainId = usePrevious(chainId)
...@@ -271,37 +280,39 @@ export default function NetworkSelector() { ...@@ -271,37 +280,39 @@ export default function NetworkSelector() {
const info = chainId ? CHAIN_INFO[chainId] : undefined const info = chainId ? CHAIN_INFO[chainId] : undefined
const dispatch = useAppDispatch() const onSelectChain = useCallback(
async (targetChain: number, skipToggle?: boolean) => {
if (!connector) return
const wallet = getWalletForConnector(connector)
const handleChainSwitch = useCallback( try {
(targetChain: number, skipToggle?: boolean) => { dispatch(updateWalletError({ wallet, error: undefined }))
if (!library?.provider) return await switchChain(connector, targetChain)
switchToNetwork({ provider: library.provider, chainId: targetChain }) if (!skipToggle) {
.then(() => { toggle()
if (!skipToggle) { }
toggle() history.replace({
} search: replaceURLParam(history.location.search, 'chain', getChainNameFromId(targetChain)),
history.replace({
search: replaceURLParam(history.location.search, 'chain', getChainNameFromId(targetChain)),
})
}) })
.catch((error) => { } catch (error) {
console.error('Failed to switch networks', error) console.error('Failed to switch networks', error)
// we want app network <-> chainId param to be in sync, so if user changes the network by changing the URL // we want app network <-> chainId param to be in sync, so if user changes the network by changing the URL
// but the request fails, revert the URL back to current chainId // but the request fails, revert the URL back to current chainId
if (chainId) { if (chainId) {
history.replace({ search: replaceURLParam(history.location.search, 'chain', getChainNameFromId(chainId)) }) history.replace({ search: replaceURLParam(history.location.search, 'chain', getChainNameFromId(chainId)) })
} }
if (!skipToggle) { if (!skipToggle) {
toggle() toggle()
} }
dispatch(addPopup({ content: { failedSwitchNetwork: targetChain }, key: `failed-network-switch` })) dispatch(updateWalletError({ wallet, error: error.message }))
}) dispatch(addPopup({ content: { failedSwitchNetwork: targetChain }, key: `failed-network-switch` }))
}
}, },
[dispatch, library, toggle, history, chainId] [connector, toggle, dispatch, history, chainId]
) )
useEffect(() => { useEffect(() => {
...@@ -312,9 +323,9 @@ export default function NetworkSelector() { ...@@ -312,9 +323,9 @@ export default function NetworkSelector() {
history.replace({ search: replaceURLParam(history.location.search, 'chain', getChainNameFromId(chainId)) }) history.replace({ search: replaceURLParam(history.location.search, 'chain', getChainNameFromId(chainId)) })
// otherwise assume network change originates from URL // otherwise assume network change originates from URL
} else if (urlChainId && urlChainId !== chainId) { } else if (urlChainId && urlChainId !== chainId) {
handleChainSwitch(urlChainId, true) onSelectChain(urlChainId, true)
} }
}, [chainId, urlChainId, prevChainId, handleChainSwitch, history]) }, [chainId, urlChainId, prevChainId, onSelectChain, history])
// set chain parameter on initial load if not there // set chain parameter on initial load if not there
useEffect(() => { useEffect(() => {
...@@ -323,7 +334,7 @@ export default function NetworkSelector() { ...@@ -323,7 +334,7 @@ export default function NetworkSelector() {
} }
}, [chainId, history, urlChainId, urlChain]) }, [chainId, history, urlChainId, urlChain])
if (!chainId || !info || !library) { if (!chainId || !info || !provider) {
return null return null
} }
...@@ -340,10 +351,11 @@ export default function NetworkSelector() { ...@@ -340,10 +351,11 @@ export default function NetworkSelector() {
<FlyoutHeader> <FlyoutHeader>
<Trans>Select a network</Trans> <Trans>Select a network</Trans>
</FlyoutHeader> </FlyoutHeader>
<Row onSelectChain={handleChainSwitch} targetChain={SupportedChainId.MAINNET} /> {NETWORK_SELECTOR_CHAINS.map((chainId: SupportedChainId) =>
<Row onSelectChain={handleChainSwitch} targetChain={SupportedChainId.POLYGON} /> isChainAllowed(connector, chainId) ? (
<Row onSelectChain={handleChainSwitch} targetChain={SupportedChainId.OPTIMISM} /> <Row onSelectChain={onSelectChain} targetChain={chainId} key={chainId} />
<Row onSelectChain={handleChainSwitch} targetChain={SupportedChainId.ARBITRUM_ONE} /> ) : null
)}
</FlyoutMenuContents> </FlyoutMenuContents>
</FlyoutMenu> </FlyoutMenu>
)} )}
......
...@@ -13,6 +13,7 @@ import { useUserHasSubmittedClaim } from 'state/transactions/hooks' ...@@ -13,6 +13,7 @@ import { useUserHasSubmittedClaim } from 'state/transactions/hooks'
import { useDarkModeManager } from 'state/user/hooks' import { useDarkModeManager } from 'state/user/hooks'
import { useNativeCurrencyBalances } from 'state/wallet/hooks' import { useNativeCurrencyBalances } from 'state/wallet/hooks'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import { isChainAllowed } from 'utils/switchChain'
import { ReactComponent as Logo } from '../../assets/svg/logo.svg' import { ReactComponent as Logo } from '../../assets/svg/logo.svg'
import { ExternalLink, ThemedText } from '../../theme' import { ExternalLink, ThemedText } from '../../theme'
...@@ -76,7 +77,7 @@ const HeaderElement = styled.div` ...@@ -76,7 +77,7 @@ const HeaderElement = styled.div`
margin-left: 0.5em; margin-left: 0.5em;
} }
/* addresses safari's lack of support for "gap" */ /* addresses safaris lack of support for "gap" */
& > *:not(:first-child) { & > *:not(:first-child) {
margin-left: 8px; margin-left: 8px;
} }
...@@ -246,7 +247,9 @@ const StyledExternalLink = styled(ExternalLink).attrs({ ...@@ -246,7 +247,9 @@ const StyledExternalLink = styled(ExternalLink).attrs({
` `
export default function Header() { export default function Header() {
const { account, chainId } = useActiveWeb3React() const { account, chainId, connector } = useActiveWeb3React()
const chainAllowed = chainId && isChainAllowed(connector, chainId)
const userEthBalance = useNativeCurrencyBalances(account ? [account] : [])?.[account ?? ''] const userEthBalance = useNativeCurrencyBalances(account ? [account] : [])?.[account ?? '']
const [darkMode] = useDarkModeManager() const [darkMode] = useDarkModeManager()
...@@ -265,7 +268,7 @@ export default function Header() { ...@@ -265,7 +268,7 @@ export default function Header() {
const { const {
infoLink, infoLink,
nativeCurrency: { symbol: nativeCurrencySymbol }, nativeCurrency: { symbol: nativeCurrencySymbol },
} = CHAIN_INFO[chainId ? chainId : SupportedChainId.MAINNET] } = CHAIN_INFO[!chainId || !chainAllowed ? SupportedChainId.MAINNET : chainId]
return ( return (
<HeaderFrame showBackground={scrollY > 45}> <HeaderFrame showBackground={scrollY > 45}>
......
import { Connector } from '@web3-react/types' import { Connector } from '@web3-react/types'
import { AbstractConnector } from 'web3-react-abstract-connector'
import CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg' import CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg'
import FortmaticIcon from '../../assets/images/fortmaticIcon.png' import FortmaticIcon from '../../assets/images/fortmaticIcon.png'
import WalletConnectIcon from '../../assets/images/walletConnectIcon.svg' import WalletConnectIcon from '../../assets/images/walletConnectIcon.svg'
import { fortmatic, injected, walletconnect, walletlink } from '../../connectors' import { coinbaseWallet, fortmatic, injected, walletConnect } from '../../connectors'
import Identicon from '../Identicon' import Identicon from '../Identicon'
export default function StatusIcon({ connector }: { connector: AbstractConnector | Connector }) { export default function StatusIcon({ connector }: { connector: Connector }) {
switch (connector) { switch (connector) {
case injected: case injected:
return <Identicon /> return <Identicon />
case walletconnect: case walletConnect:
return <img src={WalletConnectIcon} alt={'WalletConnect'} /> return <img src={WalletConnectIcon} alt="WalletConnect" />
case walletlink: case coinbaseWallet:
return <img src={CoinbaseWalletIcon} alt={'Coinbase Wallet'} /> return <img src={CoinbaseWalletIcon} alt="Coinbase Wallet" />
case fortmatic: case fortmatic:
return <img src={FortmaticIcon} alt={'Fortmatic'} /> return <img src={FortmaticIcon} alt="Fortmatic" />
default: default:
return null return null
} }
......
...@@ -4,15 +4,14 @@ import Badge from 'components/Badge' ...@@ -4,15 +4,14 @@ import Badge from 'components/Badge'
import { CHAIN_INFO } from 'constants/chainInfo' import { CHAIN_INFO } from 'constants/chainInfo'
import { L2_CHAIN_IDS, SupportedL2ChainId } from 'constants/chains' import { L2_CHAIN_IDS, SupportedL2ChainId } from 'constants/chains'
import useActiveWeb3React from 'hooks/useActiveWeb3React' import useActiveWeb3React from 'hooks/useActiveWeb3React'
import useAddTokenToMetamask from 'hooks/useAddTokenToMetamask' import useCurrencyLogoURIs from 'lib/hooks/useCurrencyLogoURIs'
import { ReactNode, useContext } from 'react' import { ReactNode, useCallback, useContext, useState } from 'react'
import { AlertCircle, AlertTriangle, ArrowUpCircle, CheckCircle } from 'react-feather' import { AlertCircle, AlertTriangle, ArrowUpCircle, CheckCircle } from 'react-feather'
import { Text } from 'rebass' import { Text } from 'rebass'
import { useIsTransactionConfirmed, useTransaction } from 'state/transactions/hooks' import { useIsTransactionConfirmed, useTransaction } from 'state/transactions/hooks'
import styled, { ThemeContext } from 'styled-components/macro' import styled, { ThemeContext } from 'styled-components/macro'
import Circle from '../../assets/images/blue-loader.svg' import Circle from '../../assets/images/blue-loader.svg'
import MetaMaskLogo from '../../assets/images/metamask.png'
import { ExternalLink } from '../../theme' import { ExternalLink } from '../../theme'
import { CloseIcon, CustomLightSpinner } from '../../theme' import { CloseIcon, CustomLightSpinner } from '../../theme'
import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink' import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink'
...@@ -97,9 +96,25 @@ function TransactionSubmittedContent({ ...@@ -97,9 +96,25 @@ function TransactionSubmittedContent({
}) { }) {
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
const { library } = useActiveWeb3React() const { connector } = useActiveWeb3React()
const { addToken, success } = useAddTokenToMetamask(currencyToAdd) const token = currencyToAdd?.wrapped
const logoURL = useCurrencyLogoURIs(token)[0]
const [success, setSuccess] = useState<boolean | undefined>()
const addToken = useCallback(() => {
if (!token?.symbol || !connector.watchAsset) return
connector
.watchAsset({
address: token.address,
symbol: token.symbol,
decimals: token.decimals,
image: logoURL,
})
.then(() => setSuccess(true))
.catch(() => setSuccess(false))
}, [connector, logoURL, token])
return ( return (
<Wrapper> <Wrapper>
...@@ -124,13 +139,11 @@ function TransactionSubmittedContent({ ...@@ -124,13 +139,11 @@ function TransactionSubmittedContent({
</Text> </Text>
</ExternalLink> </ExternalLink>
)} )}
{currencyToAdd && library?.provider?.isMetaMask && ( {currencyToAdd && connector.watchAsset && (
<ButtonLight mt="12px" padding="6px 12px" width="fit-content" onClick={addToken}> <ButtonLight mt="12px" padding="6px 12px" width="fit-content" onClick={addToken}>
{!success ? ( {!success ? (
<RowFixed> <RowFixed>
<Trans> <Trans>Add {currencyToAdd.symbol}</Trans>
Add {currencyToAdd.symbol} to Metamask <StyledLogo src={MetaMaskLogo} />
</Trans>
</RowFixed> </RowFixed>
) : ( ) : (
<RowFixed> <RowFixed>
......
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { Connector } from '@web3-react/types'
import { ButtonEmpty, ButtonPrimary } from 'components/Button' import { ButtonEmpty, ButtonPrimary } from 'components/Button'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import { ThemedText } from 'theme' import { ThemedText } from 'theme'
import { AbstractConnector } from 'web3-react-abstract-connector'
import Loader from '../Loader' import Loader from '../Loader'
...@@ -49,15 +49,13 @@ const LoadingWrapper = styled.div` ...@@ -49,15 +49,13 @@ const LoadingWrapper = styled.div`
export default function PendingView({ export default function PendingView({
connector, connector,
error = false, error = false,
setPendingError,
tryActivation, tryActivation,
resetAccountView, openOptions,
}: { }: {
connector?: AbstractConnector connector: Connector
error?: boolean error?: boolean
setPendingError: (error: boolean) => void tryActivation: (connector: Connector) => void
tryActivation: (connector: AbstractConnector) => void openOptions: () => void
resetAccountView: () => void
}) { }) {
return ( return (
<PendingSection> <PendingSection>
...@@ -77,14 +75,13 @@ export default function PendingView({ ...@@ -77,14 +75,13 @@ export default function PendingView({
$borderRadius="12px" $borderRadius="12px"
padding="12px" padding="12px"
onClick={() => { onClick={() => {
setPendingError(false) tryActivation(connector)
connector && tryActivation(connector)
}} }}
> >
<Trans>Try Again</Trans> <Trans>Try Again</Trans>
</ButtonPrimary> </ButtonPrimary>
<ButtonEmpty width="fit-content" padding="0" marginTop={20}> <ButtonEmpty width="fit-content" padding="0" marginTop={20}>
<ThemedText.Link fontSize={12} onClick={resetAccountView}> <ThemedText.Link fontSize={12} onClick={openOptions}>
<Trans>Back to wallet selection</Trans> <Trans>Back to wallet selection</Trans>
</ThemedText.Link> </ThemedText.Link>
</ButtonEmpty> </ButtonEmpty>
......
This diff is collapsed.
import { Web3ReactProvider } from '@web3-react/core'
import { Connector } from '@web3-react/types'
import { BACKFILLABLE_WALLETS, getConnectorForWallet, gnosisSafe, network, useConnectors } from 'connectors'
import { ReactNode, useEffect } from 'react'
import { useAppSelector } from 'state/hooks'
const connect = async (connector: Connector) => {
try {
if (connector.connectEagerly) {
await connector.connectEagerly()
} else {
await connector.activate()
}
} catch (error) {
console.debug(`web3-react eager connection error: ${error}`)
}
}
export default function Web3Provider({ children }: { children: ReactNode }) {
const selectedWalletBackfilled = useAppSelector((state) => state.user.selectedWalletBackfilled)
const selectedWallet = useAppSelector((state) => state.user.selectedWallet)
const connectors = useConnectors(selectedWallet)
useEffect(() => {
connect(gnosisSafe)
connect(network)
if (selectedWallet) {
connect(getConnectorForWallet(selectedWallet))
} else if (!selectedWalletBackfilled) {
BACKFILLABLE_WALLETS.map(getConnectorForWallet).forEach(connect)
}
// The dependency list is empty so this is only run once on mount
}, []) // eslint-disable-line react-hooks/exhaustive-deps
return <Web3ReactProvider connectors={connectors}>{children}</Web3ReactProvider>
}
import { Trans } from '@lingui/macro'
import { useEffect } from 'react'
import styled from 'styled-components/macro'
import { useWeb3React } from 'web3-react-core'
import { network } from '../../connectors'
import { NetworkContextName } from '../../constants/misc'
import { useEagerConnect, useInactiveListener } from '../../hooks/web3'
const MessageWrapper = styled.div`
display: flex;
align-items: center;
justify-content: center;
height: 20rem;
`
const Message = styled.h2`
color: ${({ theme }) => theme.secondary1};
`
export default function Web3ReactManager({ children }: { children: JSX.Element }) {
const { active } = useWeb3React()
const { active: networkActive, error: networkError, activate: activateNetwork } = useWeb3React(NetworkContextName)
// try to eagerly connect to an injected provider, if it exists and has granted access already
const triedEager = useEagerConnect()
// after eagerly trying injected, if the network connect ever isn't active or in an error state, activate itd
useEffect(() => {
if (triedEager && !networkActive && !networkError && !active) {
activateNetwork(network)
}
}, [triedEager, networkActive, networkError, activateNetwork, active])
// when there's no account connected, react to logins (broadly speaking) on the injected provider, if it exists
useInactiveListener(!triedEager)
// if the account context isn't active, and there's an error on the network context, it's an irrecoverable error
if (triedEager && !active && networkError) {
return (
<MessageWrapper>
<Message>
<Trans>
Oops! An unknown error occurred. Please refresh the page, or visit from another browser or device.
</Trans>
</Message>
</MessageWrapper>
)
}
return children
}
// eslint-disable-next-line no-restricted-imports // eslint-disable-next-line no-restricted-imports
import { t, Trans } from '@lingui/macro' import { t, Trans } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core'
import { Connector } from '@web3-react/types' import { Connector } from '@web3-react/types'
import { getWalletForConnector } from 'connectors'
import { darken } from 'polished' import { darken } from 'polished'
import { useMemo } from 'react' import { useMemo } from 'react'
import { Activity } from 'react-feather' import { Activity } from 'react-feather'
import { useAppSelector } from 'state/hooks'
import styled, { css } from 'styled-components/macro' import styled, { css } from 'styled-components/macro'
import { AbstractConnector } from 'web3-react-abstract-connector' import { isChainAllowed } from 'utils/switchChain'
import { UnsupportedChainIdError, useWeb3React } from 'web3-react-core'
import { NetworkContextName } from '../../constants/misc'
import useENSName from '../../hooks/useENSName'
import { useHasSocks } from '../../hooks/useSocksBalance' import { useHasSocks } from '../../hooks/useSocksBalance'
import { useWalletModalToggle } from '../../state/application/hooks' import { useWalletModalToggle } from '../../state/application/hooks'
import { isTransactionRecent, useAllTransactions } from '../../state/transactions/hooks' import { isTransactionRecent, useAllTransactions } from '../../state/transactions/hooks'
...@@ -131,7 +131,7 @@ function Sock() { ...@@ -131,7 +131,7 @@ function Sock() {
) )
} }
function WrappedStatusIcon({ connector }: { connector: AbstractConnector | Connector }) { function WrappedStatusIcon({ connector }: { connector: Connector }) {
return ( return (
<IconWrapper size={16}> <IconWrapper size={16}>
<StatusIcon connector={connector} /> <StatusIcon connector={connector} />
...@@ -140,9 +140,11 @@ function WrappedStatusIcon({ connector }: { connector: AbstractConnector | Conne ...@@ -140,9 +140,11 @@ function WrappedStatusIcon({ connector }: { connector: AbstractConnector | Conne
} }
function Web3StatusInner() { function Web3StatusInner() {
const { account, connector, error } = useWeb3React() const { account, connector, chainId, ENSName } = useWeb3React()
const { ENSName } = useENSName(account ?? undefined) const error = useAppSelector((state) => state.wallet.errorByWallet[getWalletForConnector(connector)])
const chainAllowed = chainId && isChainAllowed(connector, chainId)
const allTransactions = useAllTransactions() const allTransactions = useAllTransactions()
...@@ -157,7 +159,27 @@ function Web3StatusInner() { ...@@ -157,7 +159,27 @@ function Web3StatusInner() {
const hasSocks = useHasSocks() const hasSocks = useHasSocks()
const toggleWalletModal = useWalletModalToggle() const toggleWalletModal = useWalletModalToggle()
if (account) { if (!chainId) {
return null
} else if (!chainAllowed) {
return (
<Web3StatusError onClick={toggleWalletModal}>
<NetworkIcon />
<Text>
<Trans>Wrong Network</Trans>
</Text>
</Web3StatusError>
)
} else if (error) {
return (
<Web3StatusError onClick={toggleWalletModal}>
<NetworkIcon />
<Text>
<Trans>Error</Trans>
</Text>
</Web3StatusError>
)
} else if (account) {
return ( return (
<Web3StatusConnected id="web3-status-connected" onClick={toggleWalletModal} pending={hasPendingTransactions}> <Web3StatusConnected id="web3-status-connected" onClick={toggleWalletModal} pending={hasPendingTransactions}>
{hasPendingTransactions ? ( {hasPendingTransactions ? (
...@@ -176,13 +198,6 @@ function Web3StatusInner() { ...@@ -176,13 +198,6 @@ function Web3StatusInner() {
{!hasPendingTransactions && connector && <WrappedStatusIcon connector={connector} />} {!hasPendingTransactions && connector && <WrappedStatusIcon connector={connector} />}
</Web3StatusConnected> </Web3StatusConnected>
) )
} else if (error) {
return (
<Web3StatusError onClick={toggleWalletModal}>
<NetworkIcon />
<Text>{error instanceof UnsupportedChainIdError ? <Trans>Wrong Network</Trans> : <Trans>Error</Trans>}</Text>
</Web3StatusError>
)
} else { } else {
return ( return (
<Web3StatusConnect id="connect-wallet" onClick={toggleWalletModal} faded={!account}> <Web3StatusConnect id="connect-wallet" onClick={toggleWalletModal} faded={!account}>
...@@ -195,10 +210,7 @@ function Web3StatusInner() { ...@@ -195,10 +210,7 @@ function Web3StatusInner() {
} }
export default function Web3Status() { export default function Web3Status() {
const { active, account } = useWeb3React() const { ENSName } = useWeb3React()
const contextNetwork = useWeb3React(NetworkContextName)
const { ENSName } = useENSName(account ?? undefined)
const allTransactions = useAllTransactions() const allTransactions = useAllTransactions()
...@@ -213,9 +225,7 @@ export default function Web3Status() { ...@@ -213,9 +225,7 @@ export default function Web3Status() {
return ( return (
<> <>
<Web3StatusInner /> <Web3StatusInner />
{(contextNetwork.active || active) && ( <WalletModal ENSName={ENSName ?? undefined} pendingTransactions={pending} confirmedTransactions={confirmed} />
<WalletModal ENSName={ENSName ?? undefined} pendingTransactions={pending} confirmedTransactions={confirmed} />
)}
</> </>
) )
} }
...@@ -53,7 +53,7 @@ interface StakingModalProps { ...@@ -53,7 +53,7 @@ interface StakingModalProps {
} }
export default function StakingModal({ isOpen, onDismiss, stakingInfo, userLiquidityUnstaked }: StakingModalProps) { export default function StakingModal({ isOpen, onDismiss, stakingInfo, userLiquidityUnstaked }: StakingModalProps) {
const { library } = useActiveWeb3React() const { provider } = useActiveWeb3React()
// track and parse user input // track and parse user input
const [typedValue, setTypedValue] = useState('') const [typedValue, setTypedValue] = useState('')
...@@ -144,7 +144,7 @@ export default function StakingModal({ isOpen, onDismiss, stakingInfo, userLiqui ...@@ -144,7 +144,7 @@ export default function StakingModal({ isOpen, onDismiss, stakingInfo, userLiqui
}, [maxAmountInput, onUserInput]) }, [maxAmountInput, onUserInput])
async function onAttemptToApprove() { async function onAttemptToApprove() {
if (!pairContract || !library || !deadline) throw new Error('missing dependencies') if (!pairContract || !provider || !deadline) throw new Error('missing dependencies')
if (!parsedAmount) throw new Error('missing liquidity amount') if (!parsedAmount) throw new Error('missing liquidity amount')
if (gatherPermitSignature) { if (gatherPermitSignature) {
......
import { FortmaticConnector as FortmaticConnectorCore } from 'web3-react-fortmatic-connector'
export const OVERLAY_READY = 'OVERLAY_READY'
type FormaticSupportedChains = 1 | 3 | 4 | 42
const CHAIN_ID_NETWORK_ARGUMENT: { readonly [chainId in FormaticSupportedChains]: string | undefined } = {
1: undefined,
3: 'ropsten',
4: 'rinkeby',
42: 'kovan',
}
export class FortmaticConnector extends FortmaticConnectorCore {
async activate() {
if (!this.fortmatic) {
const { default: Fortmatic } = await import('fortmatic')
const { apiKey, chainId } = this as any
if (chainId in CHAIN_ID_NETWORK_ARGUMENT) {
this.fortmatic = new Fortmatic(apiKey, CHAIN_ID_NETWORK_ARGUMENT[chainId as FormaticSupportedChains])
} else {
throw new Error(`Unsupported network ID: ${chainId}`)
}
}
const provider = this.fortmatic.getProvider()
const pollForOverlayReady = new Promise<void>((resolve) => {
const interval = setInterval(() => {
if (provider.overlayReady) {
clearInterval(interval)
this.emit(OVERLAY_READY)
resolve()
}
}, 200)
})
const [account] = await Promise.all([
provider.enable().then((accounts: string[]) => accounts[0]),
pollForOverlayReady,
])
return { provider: this.fortmatic.getProvider(), chainId: (this as any).chainId, account }
}
}
import invariant from 'tiny-invariant'
import { AbstractConnector } from 'web3-react-abstract-connector'
import { ConnectorUpdate } from 'web3-react-types'
interface NetworkConnectorArguments {
urls: { [chainId: number]: string }
defaultChainId?: number
}
// taken from ethers.js, compatible interface with web3 provider
type AsyncSendable = {
isMetaMask?: boolean
host?: string
path?: string
sendAsync?: (request: any, callback: (error: any, response: any) => void) => void
send?: (request: any, callback: (error: any, response: any) => void) => void
}
class RequestError extends Error {
constructor(message: string, public code: number, public data?: unknown) {
super(message)
}
}
interface BatchItem {
request: { jsonrpc: '2.0'; id: number; method: string; params: unknown }
resolve: (result: any) => void
reject: (error: Error) => void
}
class MiniRpcProvider implements AsyncSendable {
public readonly isMetaMask: false = false
public readonly chainId: number
public readonly url: string
public readonly host: string
public readonly path: string
public readonly batchWaitTimeMs: number
private readonly connector: NetworkConnector
private nextId = 1
private batchTimeoutId: ReturnType<typeof setTimeout> | null = null
private batch: BatchItem[] = []
constructor(connector: NetworkConnector, chainId: number, url: string, batchWaitTimeMs?: number) {
this.connector = connector
this.chainId = chainId
this.url = url
const parsed = new URL(url)
this.host = parsed.host
this.path = parsed.pathname
// how long to wait to batch calls
this.batchWaitTimeMs = batchWaitTimeMs ?? 50
}
public readonly clearBatch = async () => {
console.debug('Clearing batch', this.batch)
let batch = this.batch
batch = batch.filter((b) => {
if (b.request.method === 'wallet_switchEthereumChain') {
try {
this.connector.changeChainId(parseInt((b.request.params as [{ chainId: string }])[0].chainId))
b.resolve({ id: b.request.id })
} catch (error) {
b.reject(error)
}
return false
}
return true
})
this.batch = []
this.batchTimeoutId = null
let response: Response
try {
response = await fetch(this.url, {
method: 'POST',
headers: { 'content-type': 'application/json', accept: 'application/json' },
body: JSON.stringify(batch.map((item) => item.request)),
})
} catch (error) {
batch.forEach(({ reject }) => reject(new Error('Failed to send batch call')))
return
}
if (!response.ok) {
batch.forEach(({ reject }) => reject(new RequestError(`${response.status}: ${response.statusText}`, -32000)))
return
}
let json
try {
json = await response.json()
} catch (error) {
batch.forEach(({ reject }) => reject(new Error('Failed to parse JSON response')))
return
}
const byKey = batch.reduce<{ [id: number]: BatchItem }>((memo, current) => {
memo[current.request.id] = current
return memo
}, {})
for (const result of json) {
const {
resolve,
reject,
request: { method },
} = byKey[result.id]
if ('error' in result) {
reject(new RequestError(result?.error?.message, result?.error?.code, result?.error?.data))
} else if ('result' in result && resolve) {
resolve(result.result)
} else {
reject(new RequestError(`Received unexpected JSON-RPC response to ${method} request.`, -32000, result))
}
}
}
public readonly sendAsync = (
request: {
jsonrpc: '2.0'
id: number | string | null
method: string
params?: unknown[] | Record<string, unknown>
},
callback: (error: any, response: any) => void
): void => {
this.request(request.method, request.params)
.then((result) => callback(null, { jsonrpc: '2.0', id: request.id, result }))
.catch((error) => callback(error, null))
}
public readonly request = async (
method: string | { method: string; params: unknown[] },
params?: unknown[] | Record<string, unknown>
): Promise<unknown> => {
if (typeof method !== 'string') {
return this.request(method.method, method.params)
}
if (method === 'eth_chainId') {
return `0x${this.chainId.toString(16)}`
}
const promise = new Promise((resolve, reject) => {
this.batch.push({
request: {
jsonrpc: '2.0',
id: this.nextId++,
method,
params,
},
resolve,
reject,
})
})
this.batchTimeoutId = this.batchTimeoutId ?? setTimeout(this.clearBatch, this.batchWaitTimeMs)
return promise
}
}
export class NetworkConnector extends AbstractConnector {
private readonly providers: { [chainId: number]: MiniRpcProvider }
private currentChainId: number
constructor({ urls, defaultChainId }: NetworkConnectorArguments) {
invariant(defaultChainId || Object.keys(urls).length === 1, 'defaultChainId is a required argument with >1 url')
super({ supportedChainIds: Object.keys(urls).map((k): number => Number(k)) })
this.currentChainId = defaultChainId ?? Number(Object.keys(urls)[0])
this.providers = Object.keys(urls).reduce<{ [chainId: number]: MiniRpcProvider }>((accumulator, chainId) => {
accumulator[Number(chainId)] = new MiniRpcProvider(this, Number(chainId), urls[Number(chainId)])
return accumulator
}, {})
}
public get provider(): MiniRpcProvider {
return this.providers[this.currentChainId]
}
public async activate(): Promise<ConnectorUpdate> {
return { provider: this.providers[this.currentChainId], chainId: this.currentChainId, account: null }
}
public async getProvider(): Promise<MiniRpcProvider> {
return this.providers[this.currentChainId]
}
public async getChainId(): Promise<number> {
return this.currentChainId
}
public async getAccount(): Promise<null> {
return null
}
public deactivate() {
return
}
/**
* Meant to be called only by MiniRpcProvider
* @param chainId the new chain id
*/
public changeChainId(chainId: number) {
if (chainId in this.providers) {
this.currentChainId = chainId
this.emitUpdate({
chainId,
account: null,
provider: this.providers[chainId],
})
} else {
throw new Error(`Unsupported chain ID: ${chainId}`)
}
}
}
import { Web3Provider } from '@ethersproject/providers' import { CoinbaseWallet } from '@web3-react/coinbase-wallet'
import { SafeAppConnector } from '@gnosis.pm/safe-apps-web3-react' import { initializeConnector, Web3ReactHooks } from '@web3-react/core'
import { ALL_SUPPORTED_CHAIN_IDS, SupportedChainId } from 'constants/chains' import { EIP1193 } from '@web3-react/eip1193'
import { GnosisSafe } from '@web3-react/gnosis-safe'
import { MetaMask } from '@web3-react/metamask'
import { Network } from '@web3-react/network'
import { Connector } from '@web3-react/types'
import { WalletConnect } from '@web3-react/walletconnect'
import { SupportedChainId } from 'constants/chains'
import { INFURA_NETWORK_URLS } from 'constants/infura' import { INFURA_NETWORK_URLS } from 'constants/infura'
import { InjectedConnector } from 'web3-react-injected-connector' import Fortmatic from 'fortmatic'
import { WalletConnectConnector } from 'web3-react-walletconnect-connector' import { useMemo } from 'react'
import { WalletLinkConnector } from 'web3-react-walletlink-connector'
import UNISWAP_LOGO_URL from '../assets/svg/logo.svg' import UNISWAP_LOGO_URL from '../assets/svg/logo.svg'
import getLibrary from '../utils/getLibrary'
import { FortmaticConnector } from './Fortmatic' export enum Wallet {
import { NetworkConnector } from './NetworkConnector' INJECTED = 'INJECTED',
COINBASE_WALLET = 'COINBASE_WALLET',
const FORMATIC_KEY = process.env.REACT_APP_FORTMATIC_KEY WALLET_CONNECT = 'WALLET_CONNECT',
FORTMATIC = 'FORTMATIC',
export const network = new NetworkConnector({ NETWORK = 'NETWORK',
urls: INFURA_NETWORK_URLS, GNOSIS_SAFE = 'GNOSIS_SAFE',
defaultChainId: 1, }
})
export const BACKFILLABLE_WALLETS = [Wallet.COINBASE_WALLET, Wallet.WALLET_CONNECT, Wallet.INJECTED]
let networkLibrary: Web3Provider | undefined export const SELECTABLE_WALLETS = [...BACKFILLABLE_WALLETS, Wallet.FORTMATIC]
export function getNetworkLibrary(): Web3Provider {
return (networkLibrary = networkLibrary ?? getLibrary(network.provider)) function onError(error: Error) {
} console.debug(`web3-react error: ${error}`)
}
export const injected = new InjectedConnector({
supportedChainIds: ALL_SUPPORTED_CHAIN_IDS, export function getWalletForConnector(connector: Connector) {
}) switch (connector) {
case injected:
export const gnosisSafe = new SafeAppConnector() return Wallet.INJECTED
case coinbaseWallet:
export const walletconnect = new WalletConnectConnector({ return Wallet.COINBASE_WALLET
supportedChainIds: ALL_SUPPORTED_CHAIN_IDS, case walletConnect:
rpc: INFURA_NETWORK_URLS, return Wallet.WALLET_CONNECT
qrcode: true, case fortmatic:
}) return Wallet.FORTMATIC
case network:
// mainnet only return Wallet.NETWORK
export const fortmatic = new FortmaticConnector({ case gnosisSafe:
apiKey: FORMATIC_KEY ?? '', return Wallet.GNOSIS_SAFE
chainId: 1, default:
}) throw Error('unsupported connector')
}
export const walletlink = new WalletLinkConnector({ }
url: INFURA_NETWORK_URLS[SupportedChainId.MAINNET],
appName: 'Uniswap', export function getConnectorForWallet(wallet: Wallet) {
appLogoUrl: UNISWAP_LOGO_URL, switch (wallet) {
supportedChainIds: ALL_SUPPORTED_CHAIN_IDS, case Wallet.INJECTED:
}) return injected
case Wallet.COINBASE_WALLET:
return coinbaseWallet
case Wallet.WALLET_CONNECT:
return walletConnect
case Wallet.FORTMATIC:
return fortmatic
case Wallet.NETWORK:
return network
case Wallet.GNOSIS_SAFE:
return gnosisSafe
}
}
function getHooksForWallet(wallet: Wallet) {
switch (wallet) {
case Wallet.INJECTED:
return injectedHooks
case Wallet.COINBASE_WALLET:
return coinbaseWalletHooks
case Wallet.WALLET_CONNECT:
return walletConnectHooks
case Wallet.FORTMATIC:
return fortmaticHooks
case Wallet.NETWORK:
return networkHooks
case Wallet.GNOSIS_SAFE:
return gnosisSafeHooks
}
}
export const [network, networkHooks] = initializeConnector<Network>(
(actions) => new Network({ actions, urlMap: INFURA_NETWORK_URLS, defaultChainId: 1 })
)
export const [injected, injectedHooks] = initializeConnector<MetaMask>((actions) => new MetaMask({ actions, onError }))
export const [gnosisSafe, gnosisSafeHooks] = initializeConnector<GnosisSafe>((actions) => new GnosisSafe({ actions }))
export const [walletConnect, walletConnectHooks] = initializeConnector<WalletConnect>(
(actions) =>
new WalletConnect({
actions,
options: {
rpc: INFURA_NETWORK_URLS,
qrcode: true,
},
onError,
})
)
export const [fortmatic, fortmaticHooks] = initializeConnector<EIP1193>(
(actions) => new EIP1193({ actions, provider: new Fortmatic(process.env.REACT_APP_FORTMATIC_KEY).getProvider() })
)
export const [coinbaseWallet, coinbaseWalletHooks] = initializeConnector<CoinbaseWallet>(
(actions) =>
new CoinbaseWallet({
actions,
options: {
url: INFURA_NETWORK_URLS[SupportedChainId.MAINNET],
appName: 'Uniswap',
appLogoUrl: UNISWAP_LOGO_URL,
},
onError,
})
)
interface ConnectorListItem {
connector: Connector
hooks: Web3ReactHooks
}
function getConnectorListItemForWallet(wallet: Wallet) {
return {
connector: getConnectorForWallet(wallet),
hooks: getHooksForWallet(wallet),
}
}
export function useConnectors(selectedWallet: Wallet | undefined) {
return useMemo(() => {
const connectors: ConnectorListItem[] = [{ connector: gnosisSafe, hooks: gnosisSafeHooks }]
if (selectedWallet) {
connectors.push(getConnectorListItemForWallet(selectedWallet))
}
connectors.push(
...SELECTABLE_WALLETS.filter((wallet) => wallet !== selectedWallet).map(getConnectorListItemForWallet)
)
connectors.push({ connector: network, hooks: networkHooks })
const web3ReactConnectors: [Connector, Web3ReactHooks][] = connectors.map(({ connector, hooks }) => [
connector,
hooks,
])
return web3ReactConnectors
}, [selectedWallet])
}
import { JsonRpcProvider } from '@ethersproject/providers'
import { SupportedChainId } from './chains' import { SupportedChainId } from './chains'
const INFURA_KEY = process.env.REACT_APP_INFURA_KEY const INFURA_KEY = process.env.REACT_APP_INFURA_KEY
...@@ -5,6 +7,8 @@ if (typeof INFURA_KEY === 'undefined') { ...@@ -5,6 +7,8 @@ if (typeof INFURA_KEY === 'undefined') {
throw new Error(`REACT_APP_INFURA_KEY must be a defined environment variable`) throw new Error(`REACT_APP_INFURA_KEY must be a defined environment variable`)
} }
export const MAINNET_PROVIDER = new JsonRpcProvider(`https://mainnet.infura.io/v3/${INFURA_KEY}`)
/** /**
* These are the network URLs used by the interface when there is not another available source of chain data * These are the network URLs used by the interface when there is not another available source of chain data
*/ */
......
import { AbstractConnector } from 'web3-react-abstract-connector' import { Connector } from '@web3-react/types'
import INJECTED_ICON_URL from '../assets/images/arrow-right.svg' import INJECTED_ICON_URL from '../assets/images/arrow-right.svg'
import COINBASE_ICON_URL from '../assets/images/coinbaseWalletIcon.svg' import COINBASE_ICON_URL from '../assets/images/coinbaseWalletIcon.svg'
import FORTMATIC_ICON_URL from '../assets/images/fortmaticIcon.png' import FORTMATIC_ICON_URL from '../assets/images/fortmaticIcon.png'
import METAMASK_ICON_URL from '../assets/images/metamask.png' import METAMASK_ICON_URL from '../assets/images/metamask.png'
import WALLETCONNECT_ICON_URL from '../assets/images/walletConnectIcon.svg' import WALLETCONNECT_ICON_URL from '../assets/images/walletConnectIcon.svg'
import { fortmatic, injected, walletconnect, walletlink } from '../connectors' import { coinbaseWallet, fortmatic, injected, Wallet, walletConnect } from '../connectors'
interface WalletInfo { interface WalletInfo {
connector?: AbstractConnector connector?: Connector
wallet?: Wallet
name: string name: string
iconURL: string iconURL: string
description: string description: string
...@@ -22,6 +23,7 @@ interface WalletInfo { ...@@ -22,6 +23,7 @@ interface WalletInfo {
export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = { export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = {
INJECTED: { INJECTED: {
connector: injected, connector: injected,
wallet: Wallet.INJECTED,
name: 'Injected', name: 'Injected',
iconURL: INJECTED_ICON_URL, iconURL: INJECTED_ICON_URL,
description: 'Injected web3 provider.', description: 'Injected web3 provider.',
...@@ -31,6 +33,7 @@ export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = { ...@@ -31,6 +33,7 @@ export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = {
}, },
METAMASK: { METAMASK: {
connector: injected, connector: injected,
wallet: Wallet.INJECTED,
name: 'MetaMask', name: 'MetaMask',
iconURL: METAMASK_ICON_URL, iconURL: METAMASK_ICON_URL,
description: 'Easy-to-use browser extension.', description: 'Easy-to-use browser extension.',
...@@ -38,7 +41,8 @@ export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = { ...@@ -38,7 +41,8 @@ export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = {
color: '#E8831D', color: '#E8831D',
}, },
WALLET_CONNECT: { WALLET_CONNECT: {
connector: walletconnect, connector: walletConnect,
wallet: Wallet.WALLET_CONNECT,
name: 'WalletConnect', name: 'WalletConnect',
iconURL: WALLETCONNECT_ICON_URL, iconURL: WALLETCONNECT_ICON_URL,
description: 'Connect to Trust Wallet, Rainbow Wallet and more...', description: 'Connect to Trust Wallet, Rainbow Wallet and more...',
...@@ -46,8 +50,9 @@ export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = { ...@@ -46,8 +50,9 @@ export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = {
color: '#4196FC', color: '#4196FC',
mobile: true, mobile: true,
}, },
WALLET_LINK: { COINBASE_WALLET: {
connector: walletlink, connector: coinbaseWallet,
wallet: Wallet.COINBASE_WALLET,
name: 'Coinbase Wallet', name: 'Coinbase Wallet',
iconURL: COINBASE_ICON_URL, iconURL: COINBASE_ICON_URL,
description: 'Use Coinbase Wallet app on mobile device', description: 'Use Coinbase Wallet app on mobile device',
...@@ -65,6 +70,7 @@ export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = { ...@@ -65,6 +70,7 @@ export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = {
}, },
FORTMATIC: { FORTMATIC: {
connector: fortmatic, connector: fortmatic,
wallet: Wallet.FORTMATIC,
name: 'Fortmatic', name: 'Fortmatic',
iconURL: FORTMATIC_ICON_URL, iconURL: FORTMATIC_ICON_URL,
description: 'Login using Fortmatic hosted wallet', description: 'Login using Fortmatic hosted wallet',
......
/* eslint-disable react-hooks/rules-of-hooks */ // TODO(vm): Rm this file once #3759 is merged.
import { Web3Provider } from '@ethersproject/providers' import { useWeb3React } from '@web3-react/core'
import { useWeb3React } from 'web3-react-core'
import { NetworkContextName } from '../constants/misc'
export default function useActiveWeb3React() { export default function useActiveWeb3React() {
const interfaceContext = useWeb3React<Web3Provider>() return useWeb3React()
const interfaceNetworkContext = useWeb3React<Web3Provider>(
process.env.REACT_APP_IS_WIDGET ? undefined : NetworkContextName
)
if (interfaceContext.active) {
return interfaceContext
}
return interfaceNetworkContext
} }
import { Currency, Token } from '@uniswap/sdk-core'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import useCurrencyLogoURIs from 'lib/hooks/useCurrencyLogoURIs'
import { useCallback, useState } from 'react'
export default function useAddTokenToMetamask(currencyToAdd: Currency | undefined): {
addToken: () => void
success: boolean | undefined
} {
const { library } = useActiveWeb3React()
const token: Token | undefined = currencyToAdd?.wrapped
const [success, setSuccess] = useState<boolean | undefined>()
const logoURL = useCurrencyLogoURIs(token)[0]
const addToken = useCallback(() => {
if (library && library?.provider?.isMetaMask && library.provider.request && token) {
library.provider
.request({
method: 'wallet_watchAsset',
params: {
//@ts-ignore // need this for incorrect ethers provider type
type: 'ERC20',
options: {
address: token.address,
symbol: token.symbol,
decimals: token.decimals,
image: logoURL,
},
},
})
.then((success) => {
setSuccess(success)
})
.catch(() => setSuccess(false))
} else {
setSuccess(false)
}
}, [library, logoURL, token])
return { addToken, success }
}
...@@ -48,21 +48,21 @@ export function useContract<T extends Contract = Contract>( ...@@ -48,21 +48,21 @@ export function useContract<T extends Contract = Contract>(
ABI: any, ABI: any,
withSignerIfPossible = true withSignerIfPossible = true
): T | null { ): T | null {
const { library, account, chainId } = useActiveWeb3React() const { provider, account, chainId } = useActiveWeb3React()
return useMemo(() => { return useMemo(() => {
if (!addressOrAddressMap || !ABI || !library || !chainId) return null if (!addressOrAddressMap || !ABI || !provider || !chainId) return null
let address: string | undefined let address: string | undefined
if (typeof addressOrAddressMap === 'string') address = addressOrAddressMap if (typeof addressOrAddressMap === 'string') address = addressOrAddressMap
else address = addressOrAddressMap[chainId] else address = addressOrAddressMap[chainId]
if (!address) return null if (!address) return null
try { try {
return getContract(address, ABI, library, withSignerIfPossible && account ? account : undefined) return getContract(address, ABI, provider, withSignerIfPossible && account ? account : undefined)
} catch (error) { } catch (error) {
console.error('Failed to get contract', error) console.error('Failed to get contract', error)
return null return null
} }
}, [addressOrAddressMap, ABI, library, chainId, withSignerIfPossible, account]) as T }, [addressOrAddressMap, ABI, provider, chainId, withSignerIfPossible, account]) as T
} }
export function useV2MigratorContract() { export function useV2MigratorContract() {
......
...@@ -126,7 +126,7 @@ export function useERC20Permit( ...@@ -126,7 +126,7 @@ export function useERC20Permit(
state: UseERC20PermitState state: UseERC20PermitState
gatherPermitSignature: null | (() => Promise<void>) gatherPermitSignature: null | (() => Promise<void>)
} { } {
const { account, chainId, library } = useActiveWeb3React() const { account, chainId, provider } = useActiveWeb3React()
const tokenAddress = currencyAmount?.currency?.isToken ? currencyAmount.currency.address : undefined const tokenAddress = currencyAmount?.currency?.isToken ? currencyAmount.currency.address : undefined
const eip2612Contract = useEIP2612Contract(tokenAddress) const eip2612Contract = useEIP2612Contract(tokenAddress)
const isArgentWallet = useIsArgentWallet() const isArgentWallet = useIsArgentWallet()
...@@ -145,7 +145,7 @@ export function useERC20Permit( ...@@ -145,7 +145,7 @@ export function useERC20Permit(
!account || !account ||
!chainId || !chainId ||
!transactionDeadline || !transactionDeadline ||
!library || !provider ||
!tokenNonceState.valid || !tokenNonceState.valid ||
!tokenAddress || !tokenAddress ||
!spender || !spender ||
...@@ -221,7 +221,7 @@ export function useERC20Permit( ...@@ -221,7 +221,7 @@ export function useERC20Permit(
message, message,
}) })
return library return provider
.send('eth_signTypedData_v4', [account, data]) .send('eth_signTypedData_v4', [account, data])
.then(splitSignature) .then(splitSignature)
.then((signature) => { .then((signature) => {
...@@ -248,7 +248,7 @@ export function useERC20Permit( ...@@ -248,7 +248,7 @@ export function useERC20Permit(
chainId, chainId,
isArgentWallet, isArgentWallet,
transactionDeadline, transactionDeadline,
library, provider,
tokenNonceState.loading, tokenNonceState.loading,
tokenNonceState.valid, tokenNonceState.valid,
tokenNonceState.result, tokenNonceState.result,
......
import { nanoid } from '@reduxjs/toolkit' import { nanoid } from '@reduxjs/toolkit'
import { TokenList } from '@uniswap/token-lists' import { TokenList } from '@uniswap/token-lists'
import useActiveWeb3React from 'hooks/useActiveWeb3React' import { MAINNET_PROVIDER } from 'constants/infura'
import getTokenList from 'lib/hooks/useTokenList/fetchTokenList' import getTokenList from 'lib/hooks/useTokenList/fetchTokenList'
import resolveENSContentHash from 'lib/utils/resolveENSContentHash' import resolveENSContentHash from 'lib/utils/resolveENSContentHash'
import { useCallback } from 'react' import { useCallback } from 'react'
import { useAppDispatch } from 'state/hooks' import { useAppDispatch } from 'state/hooks'
import { getNetworkLibrary } from '../connectors'
import { fetchTokenList } from '../state/lists/actions' import { fetchTokenList } from '../state/lists/actions'
export function useFetchListCallback(): (listUrl: string, sendDispatch?: boolean) => Promise<TokenList> { export function useFetchListCallback(): (listUrl: string, sendDispatch?: boolean) => Promise<TokenList> {
const { chainId, library } = useActiveWeb3React()
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const ensResolver = useCallback(
async (ensName: string) => {
if (!library || chainId !== 1) {
const networkLibrary = getNetworkLibrary()
const network = await networkLibrary.getNetwork()
if (networkLibrary && network.chainId === 1) {
return resolveENSContentHash(ensName, networkLibrary)
}
throw new Error('Could not construct mainnet ENS resolver')
}
return resolveENSContentHash(ensName, library)
},
[chainId, library]
)
// note: prevent dispatch if using for list search or unsupported list // note: prevent dispatch if using for list search or unsupported list
return useCallback( return useCallback(
async (listUrl: string, sendDispatch = true) => { async (listUrl: string, sendDispatch = true) => {
const requestId = nanoid() const requestId = nanoid()
sendDispatch && dispatch(fetchTokenList.pending({ requestId, url: listUrl })) sendDispatch && dispatch(fetchTokenList.pending({ requestId, url: listUrl }))
return getTokenList(listUrl, ensResolver) return getTokenList(listUrl, (ensName: string) => resolveENSContentHash(ensName, MAINNET_PROVIDER))
.then((tokenList) => { .then((tokenList) => {
sendDispatch && dispatch(fetchTokenList.fulfilled({ url: listUrl, tokenList, requestId })) sendDispatch && dispatch(fetchTokenList.fulfilled({ url: listUrl, tokenList, requestId }))
return tokenList return tokenList
...@@ -44,6 +27,6 @@ export function useFetchListCallback(): (listUrl: string, sendDispatch?: boolean ...@@ -44,6 +27,6 @@ export function useFetchListCallback(): (listUrl: string, sendDispatch?: boolean
throw error throw error
}) })
}, },
[dispatch, ensResolver] [dispatch]
) )
} }
...@@ -39,7 +39,7 @@ export function useSwapCallArguments( ...@@ -39,7 +39,7 @@ export function useSwapCallArguments(
deadline: BigNumber | undefined, deadline: BigNumber | undefined,
feeOptions: FeeOptions | undefined feeOptions: FeeOptions | undefined
): SwapCall[] { ): SwapCall[] {
const { account, chainId, library } = useActiveWeb3React() const { account, chainId, provider } = useActiveWeb3React()
const { address: recipientAddress } = useENS(recipientAddressOrName) const { address: recipientAddress } = useENS(recipientAddressOrName)
const recipient = recipientAddressOrName === null ? account : recipientAddress const recipient = recipientAddressOrName === null ? account : recipientAddress
...@@ -47,7 +47,7 @@ export function useSwapCallArguments( ...@@ -47,7 +47,7 @@ export function useSwapCallArguments(
const argentWalletContract = useArgentWalletContract() const argentWalletContract = useArgentWalletContract()
return useMemo(() => { return useMemo(() => {
if (!trade || !recipient || !library || !account || !chainId || !deadline) return [] if (!trade || !recipient || !provider || !account || !chainId || !deadline) return []
if (trade instanceof V2Trade) { if (trade instanceof V2Trade) {
if (!routerContract) return [] if (!routerContract) return []
...@@ -175,7 +175,7 @@ export function useSwapCallArguments( ...@@ -175,7 +175,7 @@ export function useSwapCallArguments(
chainId, chainId,
deadline, deadline,
feeOptions, feeOptions,
library, provider,
recipient, recipient,
routerContract, routerContract,
signatureData, signatureData,
......
import type { EthereumProvider } from 'lib/ethereum'
import { useEffect, useState } from 'react'
import { useWeb3React } from 'web3-react-core'
import { gnosisSafe, injected } from '../connectors'
import { IS_IN_IFRAME } from '../constants/misc'
import { isMobile } from '../utils/userAgent'
export function useEagerConnect() {
const { activate, active } = useWeb3React()
const [tried, setTried] = useState(false)
// gnosisSafe.isSafeApp() races a timeout against postMessage, so it delays pageload if we are not in a safe app;
// if we are not embedded in an iframe, it is not worth checking
const [triedSafe, setTriedSafe] = useState(!IS_IN_IFRAME)
// first, try connecting to a gnosis safe
useEffect(() => {
if (!triedSafe) {
gnosisSafe.isSafeApp().then((loadedInSafe) => {
if (loadedInSafe) {
activate(gnosisSafe, undefined, true).catch(() => {
setTriedSafe(true)
})
} else {
setTriedSafe(true)
}
})
}
}, [activate, setTriedSafe, triedSafe])
// then, if that fails, try connecting to an injected connector
useEffect(() => {
if (!active && triedSafe) {
injected.isAuthorized().then((isAuthorized) => {
if (isAuthorized) {
activate(injected, undefined, true).catch(() => {
setTried(true)
})
} else {
if (isMobile && window.ethereum) {
activate(injected, undefined, true).catch(() => {
setTried(true)
})
} else {
setTried(true)
}
}
})
}
}, [activate, active, triedSafe])
// wait until we get confirmation of a connection to flip the flag
useEffect(() => {
if (active) {
setTried(true)
}
}, [active])
return tried
}
/**
* Use for network and injected - logs user in
* and out after checking what network theyre on
*/
export function useInactiveListener(suppress = false) {
const { active, error, activate } = useWeb3React()
useEffect(() => {
const ethereum = window.ethereum as EthereumProvider | undefined
if (ethereum && ethereum.on && !active && !error && !suppress) {
const handleChainChanged = () => {
// eat errors
activate(injected, undefined, true).catch((error) => {
console.error('Failed to activate after chain changed', error)
})
}
const handleAccountsChanged = (accounts: string[]) => {
if (accounts.length > 0) {
// eat errors
activate(injected, undefined, true).catch((error) => {
console.error('Failed to activate after accounts changed', error)
})
}
}
ethereum.on('chainChanged', handleChainChanged)
ethereum.on('accountsChanged', handleAccountsChanged)
return () => {
if (ethereum.removeListener) {
ethereum.removeListener('chainChanged', handleChainChanged)
ethereum.removeListener('accountsChanged', handleAccountsChanged)
}
}
}
return undefined
}, [active, error, suppress, activate])
}
...@@ -9,10 +9,9 @@ import { StrictMode } from 'react' ...@@ -9,10 +9,9 @@ import { StrictMode } from 'react'
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
import { Provider } from 'react-redux' import { Provider } from 'react-redux'
import { HashRouter } from 'react-router-dom' import { HashRouter } from 'react-router-dom'
import { createWeb3ReactRoot, Web3ReactProvider } from 'web3-react-core'
import Blocklist from './components/Blocklist' import Blocklist from './components/Blocklist'
import { NetworkContextName } from './constants/misc' import Web3Provider from './components/Web3Provider'
import { LanguageProvider } from './i18n' import { LanguageProvider } from './i18n'
import App from './pages/App' import App from './pages/App'
import * as serviceWorkerRegistration from './serviceWorkerRegistration' import * as serviceWorkerRegistration from './serviceWorkerRegistration'
...@@ -24,9 +23,6 @@ import TransactionUpdater from './state/transactions/updater' ...@@ -24,9 +23,6 @@ import TransactionUpdater from './state/transactions/updater'
import UserUpdater from './state/user/updater' import UserUpdater from './state/user/updater'
import ThemeProvider, { ThemedGlobalStyle } from './theme' import ThemeProvider, { ThemedGlobalStyle } from './theme'
import RadialGradientByChainUpdater from './theme/RadialGradientByChainUpdater' import RadialGradientByChainUpdater from './theme/RadialGradientByChainUpdater'
import getLibrary from './utils/getLibrary'
const Web3ProviderNetwork = createWeb3ReactRoot(NetworkContextName)
if (!!window.ethereum) { if (!!window.ethereum) {
window.ethereum.autoRefreshOnNetworkChange = false window.ethereum.autoRefreshOnNetworkChange = false
...@@ -51,19 +47,17 @@ ReactDOM.render( ...@@ -51,19 +47,17 @@ ReactDOM.render(
<Provider store={store}> <Provider store={store}>
<HashRouter> <HashRouter>
<LanguageProvider> <LanguageProvider>
<Web3ReactProvider getLibrary={getLibrary}> <Web3Provider>
<Web3ProviderNetwork getLibrary={getLibrary}> <Blocklist>
<Blocklist> <BlockNumberProvider>
<BlockNumberProvider> <Updaters />
<Updaters /> <ThemeProvider>
<ThemeProvider> <ThemedGlobalStyle />
<ThemedGlobalStyle /> <App />
<App /> </ThemeProvider>
</ThemeProvider> </BlockNumberProvider>
</BlockNumberProvider> </Blocklist>
</Blocklist> </Web3Provider>
</Web3ProviderNetwork>
</Web3ReactProvider>
</LanguageProvider> </LanguageProvider>
</HashRouter> </HashRouter>
</Provider> </Provider>
......
...@@ -42,7 +42,7 @@ export function useSwapCallback({ ...@@ -42,7 +42,7 @@ export function useSwapCallback({
deadline, deadline,
feeOptions, feeOptions,
}: UseSwapCallbackArgs): UseSwapCallbackReturns { }: UseSwapCallbackArgs): UseSwapCallbackReturns {
const { account, chainId, library } = useActiveWeb3React() const { account, chainId, provider } = useActiveWeb3React()
const swapCalls = useSwapCallArguments( const swapCalls = useSwapCallArguments(
trade, trade,
...@@ -52,13 +52,13 @@ export function useSwapCallback({ ...@@ -52,13 +52,13 @@ export function useSwapCallback({
deadline, deadline,
feeOptions feeOptions
) )
const { callback } = useSendSwapTransaction(account, chainId, library, trade, swapCalls) const { callback } = useSendSwapTransaction(account, chainId, provider, trade, swapCalls)
const { address: recipientAddress } = useENS(recipientAddressOrName) const { address: recipientAddress } = useENS(recipientAddressOrName)
const recipient = recipientAddressOrName === null ? account : recipientAddress const recipient = recipientAddressOrName === null ? account : recipientAddress
return useMemo(() => { return useMemo(() => {
if (!trade || !library || !account || !chainId || !callback) { if (!trade || !provider || !account || !chainId || !callback) {
return { state: SwapCallbackState.INVALID, error: <Trans>Missing dependencies</Trans> } return { state: SwapCallbackState.INVALID, error: <Trans>Missing dependencies</Trans> }
} }
if (!recipient) { if (!recipient) {
...@@ -73,5 +73,5 @@ export function useSwapCallback({ ...@@ -73,5 +73,5 @@ export function useSwapCallback({
state: SwapCallbackState.VALID, state: SwapCallbackState.VALID,
callback: async () => callback(), callback: async () => callback(),
} }
}, [trade, library, account, chainId, callback, recipient, recipientAddressOrName]) }, [trade, provider, account, chainId, callback, recipient, recipientAddressOrName])
} }
...@@ -45,18 +45,18 @@ interface UpdaterProps { ...@@ -45,18 +45,18 @@ interface UpdaterProps {
} }
export default function Updater({ pendingTransactions, onCheck, onReceipt }: UpdaterProps): null { export default function Updater({ pendingTransactions, onCheck, onReceipt }: UpdaterProps): null {
const { chainId, library } = useActiveWeb3React() const { chainId, provider } = useActiveWeb3React()
const lastBlockNumber = useBlockNumber() const lastBlockNumber = useBlockNumber()
const fastForwardBlockNumber = useFastForwardBlockNumber() const fastForwardBlockNumber = useFastForwardBlockNumber()
const getReceipt = useCallback( const getReceipt = useCallback(
(hash: string) => { (hash: string) => {
if (!library || !chainId) throw new Error('No library or chainId') if (!provider || !chainId) throw new Error('No provider or chainId')
const retryOptions = RETRY_OPTIONS_BY_CHAIN_ID[chainId] ?? DEFAULT_RETRY_OPTIONS const retryOptions = RETRY_OPTIONS_BY_CHAIN_ID[chainId] ?? DEFAULT_RETRY_OPTIONS
return retry( return retry(
() => () =>
library.getTransactionReceipt(hash).then((receipt) => { provider.getTransactionReceipt(hash).then((receipt) => {
if (receipt === null) { if (receipt === null) {
console.debug(`Retrying tranasaction receipt for ${hash}`) console.debug(`Retrying tranasaction receipt for ${hash}`)
throw new RetryableError() throw new RetryableError()
...@@ -66,11 +66,11 @@ export default function Updater({ pendingTransactions, onCheck, onReceipt }: Upd ...@@ -66,11 +66,11 @@ export default function Updater({ pendingTransactions, onCheck, onReceipt }: Upd
retryOptions retryOptions
) )
}, },
[chainId, library] [chainId, provider]
) )
useEffect(() => { useEffect(() => {
if (!chainId || !library || !lastBlockNumber) return if (!chainId || !provider || !lastBlockNumber) return
const cancels = Object.keys(pendingTransactions) const cancels = Object.keys(pendingTransactions)
.filter((hash) => shouldCheck(lastBlockNumber, pendingTransactions[hash])) .filter((hash) => shouldCheck(lastBlockNumber, pendingTransactions[hash]))
...@@ -95,7 +95,7 @@ export default function Updater({ pendingTransactions, onCheck, onReceipt }: Upd ...@@ -95,7 +95,7 @@ export default function Updater({ pendingTransactions, onCheck, onReceipt }: Upd
return () => { return () => {
cancels.forEach((cancel) => cancel()) cancels.forEach((cancel) => cancel())
} }
}, [chainId, library, lastBlockNumber, getReceipt, fastForwardBlockNumber, onReceipt, onCheck, pendingTransactions]) }, [chainId, provider, lastBlockNumber, getReceipt, fastForwardBlockNumber, onReceipt, onCheck, pendingTransactions])
return null return null
} }
...@@ -29,7 +29,7 @@ export function useFastForwardBlockNumber(): (block: number) => void { ...@@ -29,7 +29,7 @@ export function useFastForwardBlockNumber(): (block: number) => void {
} }
export function BlockNumberProvider({ children }: { children: ReactNode }) { export function BlockNumberProvider({ children }: { children: ReactNode }) {
const { chainId: activeChainId, library } = useActiveWeb3React() const { chainId: activeChainId, provider } = useActiveWeb3React()
const [{ chainId, block }, setChainBlock] = useState<{ chainId?: number; block?: number }>({ chainId: activeChainId }) const [{ chainId, block }, setChainBlock] = useState<{ chainId?: number; block?: number }>({ chainId: activeChainId })
const onBlock = useCallback( const onBlock = useCallback(
...@@ -48,24 +48,24 @@ export function BlockNumberProvider({ children }: { children: ReactNode }) { ...@@ -48,24 +48,24 @@ export function BlockNumberProvider({ children }: { children: ReactNode }) {
const windowVisible = useIsWindowVisible() const windowVisible = useIsWindowVisible()
useEffect(() => { useEffect(() => {
if (library && activeChainId && windowVisible) { if (provider && activeChainId && windowVisible) {
// If chainId hasn't changed, don't clear the block. This prevents re-fetching still valid data. // If chainId hasn't changed, don't clear the block. This prevents re-fetching still valid data.
setChainBlock((chainBlock) => (chainBlock.chainId === activeChainId ? chainBlock : { chainId: activeChainId })) setChainBlock((chainBlock) => (chainBlock.chainId === activeChainId ? chainBlock : { chainId: activeChainId }))
library provider
.getBlockNumber() .getBlockNumber()
.then(onBlock) .then(onBlock)
.catch((error) => { .catch((error) => {
console.error(`Failed to get block number for chainId ${activeChainId}`, error) console.error(`Failed to get block number for chainId ${activeChainId}`, error)
}) })
library.on('block', onBlock) provider.on('block', onBlock)
return () => { return () => {
library.removeListener('block', onBlock) provider.removeListener('block', onBlock)
} }
} }
return undefined return undefined
}, [activeChainId, library, onBlock, setChainBlock, windowVisible]) }, [activeChainId, provider, onBlock, setChainBlock, windowVisible])
const value = useMemo( const value = useMemo(
() => ({ () => ({
......
...@@ -6,6 +6,7 @@ import { useBytes32TokenContract, useTokenContract } from 'hooks/useContract' ...@@ -6,6 +6,7 @@ import { useBytes32TokenContract, useTokenContract } from 'hooks/useContract'
import { NEVER_RELOAD, useSingleCallResult } from 'lib/hooks/multicall' import { NEVER_RELOAD, useSingleCallResult } from 'lib/hooks/multicall'
import useNativeCurrency from 'lib/hooks/useNativeCurrency' import useNativeCurrency from 'lib/hooks/useNativeCurrency'
import { useMemo } from 'react' import { useMemo } from 'react'
import { isChainAllowed } from 'utils/switchChain'
import { TOKEN_SHORTHANDS } from '../../constants/tokens' import { TOKEN_SHORTHANDS } from '../../constants/tokens'
import { isAddress } from '../../utils' import { isAddress } from '../../utils'
...@@ -29,7 +30,8 @@ function parseStringOrBytes32(str: string | undefined, bytes32: string | undefin ...@@ -29,7 +30,8 @@ function parseStringOrBytes32(str: string | undefined, bytes32: string | undefin
* Returns undefined if tokenAddress is invalid or token does not exist. * Returns undefined if tokenAddress is invalid or token does not exist.
*/ */
export function useTokenFromNetwork(tokenAddress: string | null | undefined): Token | null | undefined { export function useTokenFromNetwork(tokenAddress: string | null | undefined): Token | null | undefined {
const { chainId } = useActiveWeb3React() const { chainId, connector } = useActiveWeb3React()
const chainAllowed = chainId && isChainAllowed(connector, chainId)
const formattedAddress = isAddress(tokenAddress) const formattedAddress = isAddress(tokenAddress)
...@@ -43,7 +45,7 @@ export function useTokenFromNetwork(tokenAddress: string | null | undefined): To ...@@ -43,7 +45,7 @@ export function useTokenFromNetwork(tokenAddress: string | null | undefined): To
const decimals = useSingleCallResult(tokenContract, 'decimals', undefined, NEVER_RELOAD) const decimals = useSingleCallResult(tokenContract, 'decimals', undefined, NEVER_RELOAD)
return useMemo(() => { return useMemo(() => {
if (typeof tokenAddress !== 'string' || !chainId || !formattedAddress) return undefined if (typeof tokenAddress !== 'string' || !chainAllowed || !formattedAddress) return undefined
if (decimals.loading || symbol.loading || tokenName.loading) return null if (decimals.loading || symbol.loading || tokenName.loading) return null
if (decimals.result) { if (decimals.result) {
return new Token( return new Token(
...@@ -58,6 +60,7 @@ export function useTokenFromNetwork(tokenAddress: string | null | undefined): To ...@@ -58,6 +60,7 @@ export function useTokenFromNetwork(tokenAddress: string | null | undefined): To
}, [ }, [
formattedAddress, formattedAddress,
chainId, chainId,
chainAllowed,
decimals.loading, decimals.loading,
decimals.result, decimals.result,
symbol.loading, symbol.loading,
...@@ -93,7 +96,7 @@ export function useTokenFromMapOrNetwork(tokens: TokenMap, tokenAddress?: string ...@@ -93,7 +96,7 @@ export function useTokenFromMapOrNetwork(tokens: TokenMap, tokenAddress?: string
*/ */
export function useCurrencyFromMap(tokens: TokenMap, currencyId?: string | null): Currency | null | undefined { export function useCurrencyFromMap(tokens: TokenMap, currencyId?: string | null): Currency | null | undefined {
const nativeCurrency = useNativeCurrency() const nativeCurrency = useNativeCurrency()
const { chainId } = useActiveWeb3React() const { chainId, connector } = useActiveWeb3React()
const isNative = Boolean(nativeCurrency && currencyId?.toUpperCase() === 'ETH') const isNative = Boolean(nativeCurrency && currencyId?.toUpperCase() === 'ETH')
const shorthandMatchAddress = useMemo(() => { const shorthandMatchAddress = useMemo(() => {
const chain = supportedChainId(chainId) const chain = supportedChainId(chainId)
...@@ -102,7 +105,8 @@ export function useCurrencyFromMap(tokens: TokenMap, currencyId?: string | null) ...@@ -102,7 +105,8 @@ export function useCurrencyFromMap(tokens: TokenMap, currencyId?: string | null)
const token = useTokenFromMapOrNetwork(tokens, isNative ? undefined : shorthandMatchAddress ?? currencyId) const token = useTokenFromMapOrNetwork(tokens, isNative ? undefined : shorthandMatchAddress ?? currencyId)
if (currencyId === null || currencyId === undefined) return currencyId const chainAllowed = chainId && isChainAllowed(connector, chainId)
if (currencyId === null || currencyId === undefined || !chainAllowed) return null
// this case so we use our builtin wrapped token instead of wrapped tokens on token lists // this case so we use our builtin wrapped token instead of wrapped tokens on token lists
const wrappedNative = nativeCurrency?.wrapped const wrappedNative = nativeCurrency?.wrapped
......
...@@ -81,7 +81,7 @@ export default function AddLiquidity({ ...@@ -81,7 +81,7 @@ export default function AddLiquidity({
}, },
history, history,
}: RouteComponentProps<{ currencyIdA?: string; currencyIdB?: string; feeAmount?: string; tokenId?: string }>) { }: RouteComponentProps<{ currencyIdA?: string; currencyIdB?: string; feeAmount?: string; tokenId?: string }>) {
const { account, chainId, library } = useActiveWeb3React() const { account, chainId, provider } = useActiveWeb3React()
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
const toggleWalletModal = useWalletModalToggle() // toggle wallet when disconnected const toggleWalletModal = useWalletModalToggle() // toggle wallet when disconnected
const expertMode = useIsExpertMode() const expertMode = useIsExpertMode()
...@@ -225,7 +225,7 @@ export default function AddLiquidity({ ...@@ -225,7 +225,7 @@ export default function AddLiquidity({
) )
async function onAdd() { async function onAdd() {
if (!chainId || !library || !account) return if (!chainId || !provider || !account) return
if (!positionManager || !baseCurrency || !quoteCurrency) { if (!positionManager || !baseCurrency || !quoteCurrency) {
return return
...@@ -281,7 +281,7 @@ export default function AddLiquidity({ ...@@ -281,7 +281,7 @@ export default function AddLiquidity({
setAttemptingTxn(true) setAttemptingTxn(true)
library provider
.getSigner() .getSigner()
.estimateGas(txn) .estimateGas(txn)
.then((estimate) => { .then((estimate) => {
...@@ -290,7 +290,7 @@ export default function AddLiquidity({ ...@@ -290,7 +290,7 @@ export default function AddLiquidity({
gasLimit: calculateGasMargin(estimate), gasLimit: calculateGasMargin(estimate),
} }
return library return provider
.getSigner() .getSigner()
.sendTransaction(newTxn) .sendTransaction(newTxn)
.then((response: TransactionResponse) => { .then((response: TransactionResponse) => {
......
...@@ -53,7 +53,7 @@ export default function AddLiquidity({ ...@@ -53,7 +53,7 @@ export default function AddLiquidity({
}, },
history, history,
}: RouteComponentProps<{ currencyIdA?: string; currencyIdB?: string }>) { }: RouteComponentProps<{ currencyIdA?: string; currencyIdB?: string }>) {
const { account, chainId, library } = useActiveWeb3React() const { account, chainId, provider } = useActiveWeb3React()
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
...@@ -137,7 +137,7 @@ export default function AddLiquidity({ ...@@ -137,7 +137,7 @@ export default function AddLiquidity({
const addTransaction = useTransactionAdder() const addTransaction = useTransactionAdder()
async function onAdd() { async function onAdd() {
if (!chainId || !library || !account || !router) return if (!chainId || !provider || !account || !router) return
const { [Field.CURRENCY_A]: parsedAmountA, [Field.CURRENCY_B]: parsedAmountB } = parsedAmounts const { [Field.CURRENCY_A]: parsedAmountA, [Field.CURRENCY_B]: parsedAmountB } = parsedAmounts
if (!parsedAmountA || !parsedAmountB || !currencyA || !currencyB || !deadline) { if (!parsedAmountA || !parsedAmountB || !currencyA || !currencyB || !deadline) {
......
...@@ -11,7 +11,6 @@ import ErrorBoundary from '../components/ErrorBoundary' ...@@ -11,7 +11,6 @@ import ErrorBoundary from '../components/ErrorBoundary'
import Header from '../components/Header' import Header from '../components/Header'
import Polling from '../components/Header/Polling' import Polling from '../components/Header/Polling'
import Popups from '../components/Popups' import Popups from '../components/Popups'
import Web3ReactManager from '../components/Web3ReactManager'
import DarkModeQueryParamReader from '../theme/DarkModeQueryParamReader' import DarkModeQueryParamReader from '../theme/DarkModeQueryParamReader'
import AddLiquidity from './AddLiquidity' import AddLiquidity from './AddLiquidity'
import { RedirectDuplicateTokenIds } from './AddLiquidity/redirects' import { RedirectDuplicateTokenIds } from './AddLiquidity/redirects'
...@@ -81,67 +80,60 @@ export default function App() { ...@@ -81,67 +80,60 @@ export default function App() {
<ErrorBoundary> <ErrorBoundary>
<Route component={DarkModeQueryParamReader} /> <Route component={DarkModeQueryParamReader} />
<Route component={ApeModeQueryParamReader} /> <Route component={ApeModeQueryParamReader} />
<Web3ReactManager> <AppWrapper>
<AppWrapper> <HeaderWrapper>
<HeaderWrapper> <Header />
<Header /> </HeaderWrapper>
</HeaderWrapper> <BodyWrapper>
<BodyWrapper> <Popups />
<Popups /> <Polling />
<Polling /> <TopLevelModals />
<TopLevelModals /> <Suspense fallback={<Loader />}>
<Suspense fallback={<Loader />}> <Switch>
<Switch> <Route strict path="/vote" component={Vote} />
<Route strict path="/vote" component={Vote} /> <Route exact strict path="/create-proposal">
<Route exact strict path="/create-proposal"> <Redirect to="/vote/create-proposal" />
<Redirect to="/vote/create-proposal" /> </Route>
</Route> <Route exact strict path="/claim" component={OpenClaimAddressModalAndRedirectToSwap} />
<Route exact strict path="/claim" component={OpenClaimAddressModalAndRedirectToSwap} /> <Route exact strict path="/uni" component={Earn} />
<Route exact strict path="/uni" component={Earn} /> <Route exact strict path="/uni/:currencyIdA/:currencyIdB" component={Manage} />
<Route exact strict path="/uni/:currencyIdA/:currencyIdB" component={Manage} />
<Route exact strict path="/send" component={RedirectPathToSwapOnly} />
<Route exact strict path="/send" component={RedirectPathToSwapOnly} /> <Route exact strict path="/swap/:outputCurrency" component={RedirectToSwap} />
<Route exact strict path="/swap/:outputCurrency" component={RedirectToSwap} /> <Route exact strict path="/swap" component={Swap} />
<Route exact strict path="/swap" component={Swap} />
<Route exact strict path="/pool/v2/find" component={PoolFinder} />
<Route exact strict path="/pool/v2/find" component={PoolFinder} /> <Route exact strict path="/pool/v2" component={PoolV2} />
<Route exact strict path="/pool/v2" component={PoolV2} /> <Route exact strict path="/pool" component={Pool} />
<Route exact strict path="/pool" component={Pool} /> <Route exact strict path="/pool/:tokenId" component={PositionPage} />
<Route exact strict path="/pool/:tokenId" component={PositionPage} />
<Route exact strict path="/add/v2/:currencyIdA?/:currencyIdB?" component={RedirectDuplicateTokenIdsV2} />
<Route <Route
exact exact
strict strict
path="/add/v2/:currencyIdA?/:currencyIdB?" path="/add/:currencyIdA?/:currencyIdB?/:feeAmount?"
component={RedirectDuplicateTokenIdsV2} component={RedirectDuplicateTokenIds}
/> />
<Route
exact <Route
strict exact
path="/add/:currencyIdA?/:currencyIdB?/:feeAmount?" strict
component={RedirectDuplicateTokenIds} path="/increase/:currencyIdA?/:currencyIdB?/:feeAmount?/:tokenId?"
/> component={AddLiquidity}
/>
<Route
exact <Route exact strict path="/remove/v2/:currencyIdA/:currencyIdB" component={RemoveLiquidity} />
strict <Route exact strict path="/remove/:tokenId" component={RemoveLiquidityV3} />
path="/increase/:currencyIdA?/:currencyIdB?/:feeAmount?/:tokenId?"
component={AddLiquidity} <Route exact strict path="/migrate/v2" component={MigrateV2} />
/> <Route exact strict path="/migrate/v2/:address" component={MigrateV2Pair} />
<Route exact strict path="/remove/v2/:currencyIdA/:currencyIdB" component={RemoveLiquidity} /> <Route component={RedirectPathToSwapOnly} />
<Route exact strict path="/remove/:tokenId" component={RemoveLiquidityV3} /> </Switch>
</Suspense>
<Route exact strict path="/migrate/v2" component={MigrateV2} /> <Marginer />
<Route exact strict path="/migrate/v2/:address" component={MigrateV2Pair} /> </BodyWrapper>
</AppWrapper>
<Route component={RedirectPathToSwapOnly} />
</Switch>
</Suspense>
<Marginer />
</BodyWrapper>
</AppWrapper>
</Web3ReactManager>
</ErrorBoundary> </ErrorBoundary>
) )
} }
...@@ -318,7 +318,7 @@ export function PositionPage({ ...@@ -318,7 +318,7 @@ export function PositionPage({
params: { tokenId: tokenIdFromUrl }, params: { tokenId: tokenIdFromUrl },
}, },
}: RouteComponentProps<{ tokenId?: string }>) { }: RouteComponentProps<{ tokenId?: string }>) {
const { chainId, account, library } = useActiveWeb3React() const { chainId, account, provider } = useActiveWeb3React()
const theme = useTheme() const theme = useTheme()
const parsedTokenId = tokenIdFromUrl ? BigNumber.from(tokenIdFromUrl) : undefined const parsedTokenId = tokenIdFromUrl ? BigNumber.from(tokenIdFromUrl) : undefined
...@@ -433,7 +433,7 @@ export function PositionPage({ ...@@ -433,7 +433,7 @@ export function PositionPage({
!positionManager || !positionManager ||
!account || !account ||
!tokenId || !tokenId ||
!library !provider
) )
return return
...@@ -454,7 +454,7 @@ export function PositionPage({ ...@@ -454,7 +454,7 @@ export function PositionPage({
value, value,
} }
library provider
.getSigner() .getSigner()
.estimateGas(txn) .estimateGas(txn)
.then((estimate) => { .then((estimate) => {
...@@ -463,7 +463,7 @@ export function PositionPage({ ...@@ -463,7 +463,7 @@ export function PositionPage({
gasLimit: calculateGasMargin(estimate), gasLimit: calculateGasMargin(estimate),
} }
return library return provider
.getSigner() .getSigner()
.sendTransaction(newTxn) .sendTransaction(newTxn)
.then((response: TransactionResponse) => { .then((response: TransactionResponse) => {
...@@ -497,7 +497,7 @@ export function PositionPage({ ...@@ -497,7 +497,7 @@ export function PositionPage({
account, account,
tokenId, tokenId,
addTransaction, addTransaction,
library, provider,
]) ])
const owner = useSingleCallResult(!!tokenId ? positionManager : null, 'ownerOf', [tokenId]).result?.[0] const owner = useSingleCallResult(!!tokenId ? positionManager : null, 'ownerOf', [tokenId]).result?.[0]
......
...@@ -66,7 +66,7 @@ export default function RemoveLiquidityV3({ ...@@ -66,7 +66,7 @@ export default function RemoveLiquidityV3({
function Remove({ tokenId }: { tokenId: BigNumber }) { function Remove({ tokenId }: { tokenId: BigNumber }) {
const { position } = useV3PositionFromTokenId(tokenId) const { position } = useV3PositionFromTokenId(tokenId)
const theme = useTheme() const theme = useTheme()
const { account, chainId, library } = useActiveWeb3React() const { account, chainId, provider } = useActiveWeb3React()
// flag for receiving WETH // flag for receiving WETH
const [receiveWETH, setReceiveWETH] = useState(false) const [receiveWETH, setReceiveWETH] = useState(false)
...@@ -111,7 +111,7 @@ function Remove({ tokenId }: { tokenId: BigNumber }) { ...@@ -111,7 +111,7 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
!chainId || !chainId ||
!positionSDK || !positionSDK ||
!liquidityPercentage || !liquidityPercentage ||
!library !provider
) { ) {
return return
} }
...@@ -136,7 +136,7 @@ function Remove({ tokenId }: { tokenId: BigNumber }) { ...@@ -136,7 +136,7 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
value, value,
} }
library provider
.getSigner() .getSigner()
.estimateGas(txn) .estimateGas(txn)
.then((estimate) => { .then((estimate) => {
...@@ -145,7 +145,7 @@ function Remove({ tokenId }: { tokenId: BigNumber }) { ...@@ -145,7 +145,7 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
gasLimit: calculateGasMargin(estimate), gasLimit: calculateGasMargin(estimate),
} }
return library return provider
.getSigner() .getSigner()
.sendTransaction(newTxn) .sendTransaction(newTxn)
.then((response: TransactionResponse) => { .then((response: TransactionResponse) => {
...@@ -180,7 +180,7 @@ function Remove({ tokenId }: { tokenId: BigNumber }) { ...@@ -180,7 +180,7 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
feeValue1, feeValue1,
positionSDK, positionSDK,
liquidityPercentage, liquidityPercentage,
library, provider,
tokenId, tokenId,
allowedSlippage, allowedSlippage,
addTransaction, addTransaction,
......
...@@ -52,7 +52,7 @@ export default function RemoveLiquidity({ ...@@ -52,7 +52,7 @@ export default function RemoveLiquidity({
}, },
}: RouteComponentProps<{ currencyIdA: string; currencyIdB: string }>) { }: RouteComponentProps<{ currencyIdA: string; currencyIdB: string }>) {
const [currencyA, currencyB] = [useCurrency(currencyIdA) ?? undefined, useCurrency(currencyIdB) ?? undefined] const [currencyA, currencyB] = [useCurrency(currencyIdA) ?? undefined, useCurrency(currencyIdB) ?? undefined]
const { account, chainId, library } = useActiveWeb3React() const { account, chainId, provider } = useActiveWeb3React()
const [tokenA, tokenB] = useMemo(() => [currencyA?.wrapped, currencyB?.wrapped], [currencyA, currencyB]) const [tokenA, tokenB] = useMemo(() => [currencyA?.wrapped, currencyB?.wrapped], [currencyA, currencyB])
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
...@@ -105,7 +105,7 @@ export default function RemoveLiquidity({ ...@@ -105,7 +105,7 @@ export default function RemoveLiquidity({
const [approval, approveCallback] = useApproveCallback(parsedAmounts[Field.LIQUIDITY], router?.address) const [approval, approveCallback] = useApproveCallback(parsedAmounts[Field.LIQUIDITY], router?.address)
async function onAttemptToApprove() { async function onAttemptToApprove() {
if (!pairContract || !pair || !library || !deadline) throw new Error('missing dependencies') if (!pairContract || !pair || !provider || !deadline) throw new Error('missing dependencies')
const liquidityAmount = parsedAmounts[Field.LIQUIDITY] const liquidityAmount = parsedAmounts[Field.LIQUIDITY]
if (!liquidityAmount) throw new Error('missing liquidity amount') if (!liquidityAmount) throw new Error('missing liquidity amount')
...@@ -148,7 +148,7 @@ export default function RemoveLiquidity({ ...@@ -148,7 +148,7 @@ export default function RemoveLiquidity({
const addTransaction = useTransactionAdder() const addTransaction = useTransactionAdder()
async function onRemove() { async function onRemove() {
if (!chainId || !library || !account || !deadline || !router) throw new Error('missing dependencies') if (!chainId || !provider || !account || !deadline || !router) throw new Error('missing dependencies')
const { [Field.CURRENCY_A]: currencyAmountA, [Field.CURRENCY_B]: currencyAmountB } = parsedAmounts const { [Field.CURRENCY_A]: currencyAmountA, [Field.CURRENCY_B]: currencyAmountB } = parsedAmounts
if (!currencyAmountA || !currencyAmountB) { if (!currencyAmountA || !currencyAmountB) {
throw new Error('missing currency amounts') throw new Error('missing currency amounts')
......
...@@ -22,7 +22,7 @@ function useQueryCacheInvalidator() { ...@@ -22,7 +22,7 @@ function useQueryCacheInvalidator() {
} }
export default function Updater(): null { export default function Updater(): null {
const { chainId, library } = useActiveWeb3React() const { chainId, provider } = useActiveWeb3React()
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const windowVisible = useIsWindowVisible() const windowVisible = useIsWindowVisible()
...@@ -31,10 +31,10 @@ export default function Updater(): null { ...@@ -31,10 +31,10 @@ export default function Updater(): null {
useQueryCacheInvalidator() useQueryCacheInvalidator()
useEffect(() => { useEffect(() => {
if (library && chainId && windowVisible) { if (provider && chainId && windowVisible) {
setActiveChainId(chainId) setActiveChainId(chainId)
} }
}, [dispatch, chainId, library, windowVisible]) }, [dispatch, chainId, provider, windowVisible])
const debouncedChainId = useDebounce(activeChainId, 100) const debouncedChainId = useDebounce(activeChainId, 100)
......
...@@ -155,7 +155,7 @@ export function useClaimCallback(account: string | null | undefined): { ...@@ -155,7 +155,7 @@ export function useClaimCallback(account: string | null | undefined): {
claimCallback: () => Promise<string> claimCallback: () => Promise<string>
} { } {
// get claim data for this account // get claim data for this account
const { library, chainId } = useActiveWeb3React() const { provider, chainId } = useActiveWeb3React()
const claimData = useUserClaimData(account) const claimData = useUserClaimData(account)
// used for popup summary // used for popup summary
...@@ -164,7 +164,7 @@ export function useClaimCallback(account: string | null | undefined): { ...@@ -164,7 +164,7 @@ export function useClaimCallback(account: string | null | undefined): {
const distributorContract = useMerkleDistributorContract() const distributorContract = useMerkleDistributorContract()
const claimCallback = async function () { const claimCallback = async function () {
if (!claimData || !account || !library || !chainId || !distributorContract) return if (!claimData || !account || !provider || !chainId || !distributorContract) return
const args = [claimData.index, account, claimData.amount, claimData.proof] const args = [claimData.index, account, claimData.amount, claimData.proof]
......
...@@ -384,14 +384,14 @@ export function useUserVotesAsOfBlock(block: number | undefined): CurrencyAmount ...@@ -384,14 +384,14 @@ export function useUserVotesAsOfBlock(block: number | undefined): CurrencyAmount
} }
export function useDelegateCallback(): (delegatee: string | undefined) => undefined | Promise<string> { export function useDelegateCallback(): (delegatee: string | undefined) => undefined | Promise<string> {
const { account, chainId, library } = useActiveWeb3React() const { account, chainId, provider } = useActiveWeb3React()
const addTransaction = useTransactionAdder() const addTransaction = useTransactionAdder()
const uniContract = useUniContract() const uniContract = useUniContract()
return useCallback( return useCallback(
(delegatee: string | undefined) => { (delegatee: string | undefined) => {
if (!library || !chainId || !account || !delegatee || !isAddress(delegatee ?? '')) return undefined if (!provider || !chainId || !account || !delegatee || !isAddress(delegatee ?? '')) return undefined
const args = [delegatee] const args = [delegatee]
if (!uniContract) throw new Error('No UNI Contract!') if (!uniContract) throw new Error('No UNI Contract!')
return uniContract.estimateGas.delegate(...args, {}).then((estimatedGasLimit) => { return uniContract.estimateGas.delegate(...args, {}).then((estimatedGasLimit) => {
...@@ -406,7 +406,7 @@ export function useDelegateCallback(): (delegatee: string | undefined) => undefi ...@@ -406,7 +406,7 @@ export function useDelegateCallback(): (delegatee: string | undefined) => undefi
}) })
}) })
}, },
[account, addTransaction, chainId, library, uniContract] [account, addTransaction, chainId, provider, uniContract]
) )
} }
......
...@@ -16,6 +16,7 @@ import { routingApi } from './routing/slice' ...@@ -16,6 +16,7 @@ import { routingApi } from './routing/slice'
import swap from './swap/reducer' import swap from './swap/reducer'
import transactions from './transactions/reducer' import transactions from './transactions/reducer'
import user from './user/reducer' import user from './user/reducer'
import wallet from './wallet/reducer'
const PERSISTED_KEYS: string[] = ['user', 'transactions', 'lists'] const PERSISTED_KEYS: string[] = ['user', 'transactions', 'lists']
...@@ -23,6 +24,7 @@ const store = configureStore({ ...@@ -23,6 +24,7 @@ const store = configureStore({
reducer: { reducer: {
application, application,
user, user,
wallet,
transactions, transactions,
swap, swap,
mint, mint,
......
...@@ -13,7 +13,7 @@ import { acceptListUpdate, enableList } from './actions' ...@@ -13,7 +13,7 @@ import { acceptListUpdate, enableList } from './actions'
import { useActiveListUrls } from './hooks' import { useActiveListUrls } from './hooks'
export default function Updater(): null { export default function Updater(): null {
const { chainId, library } = useActiveWeb3React() const { chainId, provider } = useActiveWeb3React()
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const isWindowVisible = useIsWindowVisible() const isWindowVisible = useIsWindowVisible()
...@@ -37,8 +37,8 @@ export default function Updater(): null { ...@@ -37,8 +37,8 @@ export default function Updater(): null {
dispatch(enableList(ARBITRUM_LIST)) dispatch(enableList(ARBITRUM_LIST))
} }
}, [chainId, dispatch]) }, [chainId, dispatch])
// fetch all lists every 10 minutes, but only after we initialize library // fetch all lists every 10 minutes, but only after we initialize provider
useInterval(fetchAllListsCallback, library ? 1000 * 60 * 10 : null) useInterval(fetchAllListsCallback, provider ? 1000 * 60 * 10 : null)
// whenever a list is not loaded and not loading, try again to load it // whenever a list is not loaded and not loading, try again to load it
useEffect(() => { useEffect(() => {
...@@ -48,7 +48,7 @@ export default function Updater(): null { ...@@ -48,7 +48,7 @@ export default function Updater(): null {
fetchList(listUrl).catch((error) => console.debug('list added fetching error', error)) fetchList(listUrl).catch((error) => console.debug('list added fetching error', error))
} }
}) })
}, [dispatch, fetchList, library, lists]) }, [dispatch, fetchList, lists])
// if any lists from unsupported lists are loaded, check them too (in case new updates since last visit) // if any lists from unsupported lists are loaded, check them too (in case new updates since last visit)
useEffect(() => { useEffect(() => {
...@@ -58,7 +58,7 @@ export default function Updater(): null { ...@@ -58,7 +58,7 @@ export default function Updater(): null {
fetchList(listUrl).catch((error) => console.debug('list added fetching error', error)) fetchList(listUrl).catch((error) => console.debug('list added fetching error', error))
} }
}) })
}, [dispatch, fetchList, library, lists]) }, [dispatch, fetchList, lists])
// automatically update lists if versions are minor/patch // automatically update lists if versions are minor/patch
useEffect(() => { useEffect(() => {
......
...@@ -10,7 +10,7 @@ import { isHistoricalLog, keyToFilter } from './utils' ...@@ -10,7 +10,7 @@ import { isHistoricalLog, keyToFilter } from './utils'
export default function Updater(): null { export default function Updater(): null {
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const state = useAppSelector((state) => state.logs) const state = useAppSelector((state) => state.logs)
const { chainId, library } = useActiveWeb3React() const { chainId, provider } = useActiveWeb3React()
const blockNumber = useBlockNumber() const blockNumber = useBlockNumber()
...@@ -34,7 +34,7 @@ export default function Updater(): null { ...@@ -34,7 +34,7 @@ export default function Updater(): null {
}, [blockNumber, chainId, state]) }, [blockNumber, chainId, state])
useEffect(() => { useEffect(() => {
if (!library || !chainId || typeof blockNumber !== 'number' || filtersNeedFetch.length === 0) return if (!provider || !chainId || typeof blockNumber !== 'number' || filtersNeedFetch.length === 0) return
dispatch(fetchingLogs({ chainId, filters: filtersNeedFetch, blockNumber })) dispatch(fetchingLogs({ chainId, filters: filtersNeedFetch, blockNumber }))
filtersNeedFetch.forEach((filter) => { filtersNeedFetch.forEach((filter) => {
...@@ -43,7 +43,7 @@ export default function Updater(): null { ...@@ -43,7 +43,7 @@ export default function Updater(): null {
let toBlock = filter.toBlock ?? blockNumber let toBlock = filter.toBlock ?? blockNumber
if (typeof fromBlock === 'string') fromBlock = Number.parseInt(fromBlock) if (typeof fromBlock === 'string') fromBlock = Number.parseInt(fromBlock)
if (typeof toBlock === 'string') toBlock = Number.parseInt(toBlock) if (typeof toBlock === 'string') toBlock = Number.parseInt(toBlock)
library provider
.getLogs({ .getLogs({
...filter, ...filter,
fromBlock, fromBlock,
...@@ -69,7 +69,7 @@ export default function Updater(): null { ...@@ -69,7 +69,7 @@ export default function Updater(): null {
) )
}) })
}) })
}, [blockNumber, chainId, dispatch, filtersNeedFetch, library]) }, [blockNumber, chainId, dispatch, filtersNeedFetch, provider])
return null return null
} }
import { createSlice } from '@reduxjs/toolkit' import { createSlice } from '@reduxjs/toolkit'
import { Wallet } from 'connectors'
import { SupportedLocale } from 'constants/locales' import { SupportedLocale } from 'constants/locales'
import { DEFAULT_DEADLINE_FROM_NOW } from '../../constants/misc' import { DEFAULT_DEADLINE_FROM_NOW } from '../../constants/misc'
...@@ -8,6 +9,13 @@ import { SerializedPair, SerializedToken } from './types' ...@@ -8,6 +9,13 @@ import { SerializedPair, SerializedToken } from './types'
const currentTimestamp = () => new Date().getTime() const currentTimestamp = () => new Date().getTime()
export interface UserState { export interface UserState {
// We want the user to be able to define which wallet they want to use, even if there are multiple connected wallets via web3-react.
// If a user had previously connected a wallet but didn't have a wallet override set (because they connected prior to this field being added),
// we want to handle that case by backfilling them manually. Once we backfill, we set the backfilled field to `true`.
// After some period of time, our active users will have this property set so we can likely remove the backfilling logic.
selectedWalletBackfilled: boolean
selectedWallet?: Wallet
// the timestamp of the last updateVersion action // the timestamp of the last updateVersion action
lastUpdateVersionTimestamp?: number lastUpdateVersionTimestamp?: number
...@@ -57,6 +65,8 @@ function pairKey(token0Address: string, token1Address: string) { ...@@ -57,6 +65,8 @@ function pairKey(token0Address: string, token1Address: string) {
} }
export const initialState: UserState = { export const initialState: UserState = {
selectedWallet: undefined,
selectedWalletBackfilled: false,
matchesDarkMode: false, matchesDarkMode: false,
userDarkMode: null, userDarkMode: null,
userExpertMode: false, userExpertMode: false,
...@@ -78,6 +88,10 @@ const userSlice = createSlice({ ...@@ -78,6 +88,10 @@ const userSlice = createSlice({
name: 'user', name: 'user',
initialState, initialState,
reducers: { reducers: {
updateSelectedWallet(state, { payload: { wallet } }) {
state.selectedWallet = wallet
state.selectedWalletBackfilled = true
},
updateUserDarkMode(state, action) { updateUserDarkMode(state, action) {
state.userDarkMode = action.payload.userDarkMode state.userDarkMode = action.payload.userDarkMode
state.timestamp = currentTimestamp() state.timestamp = currentTimestamp()
...@@ -188,6 +202,7 @@ const userSlice = createSlice({ ...@@ -188,6 +202,7 @@ const userSlice = createSlice({
}) })
export const { export const {
updateSelectedWallet,
addSerializedPair, addSerializedPair,
addSerializedToken, addSerializedToken,
removeSerializedPair, removeSerializedPair,
......
import { createSlice } from '@reduxjs/toolkit'
import { Wallet } from 'connectors'
export interface WalletState {
errorByWallet: Record<Wallet, string | undefined>
}
export const initialState: WalletState = {
errorByWallet: {
[Wallet.INJECTED]: undefined,
[Wallet.FORTMATIC]: undefined,
[Wallet.WALLET_CONNECT]: undefined,
[Wallet.COINBASE_WALLET]: undefined,
[Wallet.NETWORK]: undefined,
[Wallet.GNOSIS_SAFE]: undefined,
},
}
const walletSlice = createSlice({
name: 'wallet',
initialState,
reducers: {
updateWalletError(
state,
{ payload: { wallet, error } }: { payload: { wallet: Wallet; error: string | undefined } }
) {
state.errorByWallet[wallet] = error
},
},
})
export const { updateWalletError } = walletSlice.actions
export default walletSlice.reducer
import { Web3Provider } from '@ethersproject/providers'
import ms from 'ms.macro'
export default function getLibrary(provider: any): Web3Provider {
const library = new Web3Provider(
provider,
typeof provider.chainId === 'number'
? provider.chainId
: typeof provider.chainId === 'string'
? parseInt(provider.chainId)
: 'any'
)
library.pollingInterval = ms`15s`
return library
}
import { BigNumber } from '@ethersproject/bignumber' import { Connector } from '@web3-react/types'
import { hexStripZeros } from '@ethersproject/bytes' import { coinbaseWallet, fortmatic, gnosisSafe, injected, network, walletConnect } from 'connectors'
import { ExternalProvider } from '@ethersproject/providers'
import { CHAIN_INFO } from 'constants/chainInfo' import { CHAIN_INFO } from 'constants/chainInfo'
import { SupportedChainId } from 'constants/chains' import { ALL_SUPPORTED_CHAIN_IDS, SupportedChainId } from 'constants/chains'
import { INFURA_NETWORK_URLS } from 'constants/infura' import { INFURA_NETWORK_URLS } from 'constants/infura'
interface SwitchNetworkArguments {
provider: ExternalProvider
chainId: SupportedChainId
}
function getRpcUrls(chainId: SupportedChainId): [string] { function getRpcUrls(chainId: SupportedChainId): [string] {
switch (chainId) { switch (chainId) {
case SupportedChainId.MAINNET: case SupportedChainId.MAINNET:
...@@ -36,48 +30,35 @@ function getRpcUrls(chainId: SupportedChainId): [string] { ...@@ -36,48 +30,35 @@ function getRpcUrls(chainId: SupportedChainId): [string] {
throw new Error('RPC URLs must use public endpoints') throw new Error('RPC URLs must use public endpoints')
} }
// provider.request returns Promise<any>, but wallet_switchEthereumChain must return null or throw export function isChainAllowed(connector: Connector, chainId: number) {
// see https://github.com/rekmarks/EIPs/blob/3326-create/EIPS/eip-3326.md for more info on wallet_switchEthereumChain switch (connector) {
export async function switchToNetwork({ provider, chainId }: SwitchNetworkArguments): Promise<null | void> { case fortmatic:
if (!provider.request) { return chainId === SupportedChainId.MAINNET
return case injected:
case coinbaseWallet:
case walletConnect:
case network:
case gnosisSafe:
return ALL_SUPPORTED_CHAIN_IDS.includes(chainId)
default:
return false
} }
const formattedChainId = hexStripZeros(BigNumber.from(chainId).toHexString()) }
try {
await provider.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: formattedChainId }],
})
} catch (error) {
// 4902 is the error code for attempting to switch to an unrecognized chainId
if (error.code === 4902) {
const info = CHAIN_INFO[chainId]
await provider.request({ export const switchChain = async (connector: Connector, chainId: number) => {
method: 'wallet_addEthereumChain', if (!isChainAllowed(connector, chainId)) {
params: [ throw new Error(`Chain ${chainId} not supported for connector (${typeof connector})`)
{ } else if (connector === walletConnect || connector === network) {
chainId: formattedChainId, await connector.activate(chainId)
chainName: info.label, } else {
rpcUrls: getRpcUrls(chainId), const info = CHAIN_INFO[chainId]
nativeCurrency: info.nativeCurrency, const addChainParameter = {
blockExplorerUrls: [info.explorer], chainId,
}, chainName: info.label,
], rpcUrls: getRpcUrls(chainId),
}) nativeCurrency: info.nativeCurrency,
// metamask (only known implementer) automatically switches after a network is added blockExplorerUrls: [info.explorer],
// the second call is done here because that behavior is not a part of the spec and cannot be relied upon in the future
// metamask's behavior when switching to the current network is just to return null (a no-op)
try {
await provider.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: formattedChainId }],
})
} catch (error) {
console.debug('Added network but could not switch chains', error)
}
} else {
throw error
} }
await connector.activate(addChainParameter)
} }
} }
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment