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_FORTMATIC_KEY="pk_live_357F77728B8EB880"
REACT_APP_LOCALES="locales"
import { TEST_ADDRESS_NEVER_USE_SHORTENED } from '../support/ethereum'
describe('Landing Page', () => {
beforeEach(() => cy.visit('/'))
it('loads swap page', () => {
......@@ -15,9 +13,4 @@ describe('Landing Page', () => {
cy.get('#pool-nav-link').click()
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 @@
]
},
"dependencies": {
"@coinbase/wallet-sdk": "^3.2.0",
"@ethersproject/experimental": "^5.4.0",
"@fontsource/ibm-plex-mono": "^4.5.1",
"@fontsource/inter": "^4.5.1",
......@@ -115,13 +116,17 @@
"@uniswap/v3-core": "1.0.0",
"@uniswap/v3-periphery": "^1.1.1",
"@uniswap/v3-sdk": "^3.8.2",
"@web3-react/core": "^8.0.23-beta.0",
"@web3-react/eip1193": "^8.0.18-beta.0",
"@web3-react/empty": "^8.0.12-beta.0",
"@web3-react/metamask": "^8.0.19-beta.0",
"@web3-react/types": "^8.0.12-beta.0",
"@web3-react/url": "^8.0.17-beta.0",
"@web3-react/walletconnect": "^8.0.26-beta.0",
"@walletconnect/ethereum-provider": "1.7.1",
"@web3-react/coinbase-wallet": "^8.0.33-beta.0",
"@web3-react/core": "^8.0.33-beta.0",
"@web3-react/eip1193": "^8.0.25-beta.0",
"@web3-react/empty": "^8.0.19-beta.0",
"@web3-react/gnosis-safe": "^8.0.5-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",
"array.prototype.flat": "^1.2.4",
"array.prototype.flatmap": "^1.2.4",
......@@ -140,6 +145,7 @@
"eslint-plugin-unused-imports": "^2.0.0",
"ethers": "^5.1.4",
"firebase": "^9.1.3",
"fortmatic": "^2.4.0",
"graphql": "^15.5.0",
"graphql-request": "^3.4.0",
"immer": "^9.0.6",
......@@ -187,14 +193,6 @@
"use-resize-observer": "^8.0.0",
"wcag-contrast": "^3.0.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-navigation-preload": "^6.1.0",
"workbox-precaching": "^6.1.0",
......
......@@ -5,11 +5,11 @@ import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useCallback, useContext } from 'react'
import { ExternalLink as LinkIcon } from 'react-feather'
import { useAppDispatch } from 'state/hooks'
import { updateSelectedWallet } from 'state/user/reducer'
import styled, { ThemeContext } from 'styled-components/macro'
import { AbstractConnector } from 'web3-react-abstract-connector'
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 { clearAllTransactions } from '../../state/transactions/reducer'
import { ExternalLink, LinkStyledButton, ThemedText } from '../../theme'
......@@ -177,7 +177,7 @@ const IconWrapper = styled.div<{ size?: number }>`
`};
`
function WrappedStatusIcon({ connector }: { connector: AbstractConnector | Connector }) {
function WrappedStatusIcon({ connector }: { connector: Connector }) {
return (
<IconWrapper size={16}>
<StatusIcon connector={connector} />
......@@ -265,12 +265,16 @@ export default function AccountDetails({
<AccountGroupingRow>
{formatConnectorName()}
<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
style={{ fontSize: '.825rem', fontWeight: 400, marginRight: '8px' }}
onClick={() => {
;(connector as any).close()
connector.deactivate ? connector.deactivate() : connector.resetState()
dispatch(updateSelectedWallet({ wallet: undefined }))
openOptions()
}}
data-cy="wallet-disconnect"
>
<Trans>Disconnect</Trans>
</WalletAction>
......@@ -280,6 +284,7 @@ export default function AccountDetails({
onClick={() => {
openOptions()
}}
data-cy="wallet-change"
>
<Trans>Change</Trans>
</WalletAction>
......
import { Trans } from '@lingui/macro'
import { getWalletForConnector } from 'connectors'
import { CHAIN_INFO } from 'constants/chainInfo'
import { CHAIN_IDS_TO_NAMES, SupportedChainId } from 'constants/chains'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
......@@ -11,12 +12,12 @@ import { ArrowDownCircle, ChevronDown } from 'react-feather'
import { useHistory } from 'react-router-dom'
import { useModalOpen, useToggleModal } from 'state/application/hooks'
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 { ExternalLink, MEDIA_WIDTHS } from 'theme'
import { replaceURLParam } from 'utils/routes'
import { useAppDispatch } from '../../state/hooks'
import { switchToNetwork } from '../../utils/switchToNetwork'
import { isChainAllowed, switchChain } from 'utils/switchChain'
const ActiveRowLinkList = styled.div`
display: flex;
......@@ -184,8 +185,8 @@ function Row({
targetChain: SupportedChainId
onSelectChain: (targetChain: number) => void
}) {
const { library, chainId } = useActiveWeb3React()
if (!library || !chainId) {
const { provider, chainId } = useActiveWeb3React()
if (!provider || !chainId) {
return null
}
const active = chainId === targetChain
......@@ -257,8 +258,16 @@ const getChainNameFromId = (id: string | number) => {
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() {
const { chainId, library } = useActiveWeb3React()
const dispatch = useAppDispatch()
const { chainId, provider, connector } = useActiveWeb3React()
const parsedQs = useParsedQueryString()
const { urlChain, urlChainId } = getParsedChainId(parsedQs)
const prevChainId = usePrevious(chainId)
......@@ -271,21 +280,22 @@ export default function NetworkSelector() {
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(
(targetChain: number, skipToggle?: boolean) => {
if (!library?.provider) return
switchToNetwork({ provider: library.provider, chainId: targetChain })
.then(() => {
try {
dispatch(updateWalletError({ wallet, error: undefined }))
await switchChain(connector, targetChain)
if (!skipToggle) {
toggle()
}
history.replace({
search: replaceURLParam(history.location.search, 'chain', getChainNameFromId(targetChain)),
})
})
.catch((error) => {
} catch (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
......@@ -298,10 +308,11 @@ export default function NetworkSelector() {
toggle()
}
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(() => {
......@@ -312,9 +323,9 @@ export default function NetworkSelector() {
history.replace({ search: replaceURLParam(history.location.search, 'chain', getChainNameFromId(chainId)) })
// otherwise assume network change originates from URL
} 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
useEffect(() => {
......@@ -323,7 +334,7 @@ export default function NetworkSelector() {
}
}, [chainId, history, urlChainId, urlChain])
if (!chainId || !info || !library) {
if (!chainId || !info || !provider) {
return null
}
......@@ -340,10 +351,11 @@ export default function NetworkSelector() {
<FlyoutHeader>
<Trans>Select a network</Trans>
</FlyoutHeader>
<Row onSelectChain={handleChainSwitch} targetChain={SupportedChainId.MAINNET} />
<Row onSelectChain={handleChainSwitch} targetChain={SupportedChainId.POLYGON} />
<Row onSelectChain={handleChainSwitch} targetChain={SupportedChainId.OPTIMISM} />
<Row onSelectChain={handleChainSwitch} targetChain={SupportedChainId.ARBITRUM_ONE} />
{NETWORK_SELECTOR_CHAINS.map((chainId: SupportedChainId) =>
isChainAllowed(connector, chainId) ? (
<Row onSelectChain={onSelectChain} targetChain={chainId} key={chainId} />
) : null
)}
</FlyoutMenuContents>
</FlyoutMenu>
)}
......
......@@ -13,6 +13,7 @@ import { useUserHasSubmittedClaim } from 'state/transactions/hooks'
import { useDarkModeManager } from 'state/user/hooks'
import { useNativeCurrencyBalances } from 'state/wallet/hooks'
import styled from 'styled-components/macro'
import { isChainAllowed } from 'utils/switchChain'
import { ReactComponent as Logo } from '../../assets/svg/logo.svg'
import { ExternalLink, ThemedText } from '../../theme'
......@@ -76,7 +77,7 @@ const HeaderElement = styled.div`
margin-left: 0.5em;
}
/* addresses safari's lack of support for "gap" */
/* addresses safaris lack of support for "gap" */
& > *:not(:first-child) {
margin-left: 8px;
}
......@@ -246,7 +247,9 @@ const StyledExternalLink = styled(ExternalLink).attrs({
`
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 [darkMode] = useDarkModeManager()
......@@ -265,7 +268,7 @@ export default function Header() {
const {
infoLink,
nativeCurrency: { symbol: nativeCurrencySymbol },
} = CHAIN_INFO[chainId ? chainId : SupportedChainId.MAINNET]
} = CHAIN_INFO[!chainId || !chainAllowed ? SupportedChainId.MAINNET : chainId]
return (
<HeaderFrame showBackground={scrollY > 45}>
......
import { Connector } from '@web3-react/types'
import { AbstractConnector } from 'web3-react-abstract-connector'
import CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg'
import FortmaticIcon from '../../assets/images/fortmaticIcon.png'
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'
export default function StatusIcon({ connector }: { connector: AbstractConnector | Connector }) {
export default function StatusIcon({ connector }: { connector: Connector }) {
switch (connector) {
case injected:
return <Identicon />
case walletconnect:
return <img src={WalletConnectIcon} alt={'WalletConnect'} />
case walletlink:
return <img src={CoinbaseWalletIcon} alt={'Coinbase Wallet'} />
case walletConnect:
return <img src={WalletConnectIcon} alt="WalletConnect" />
case coinbaseWallet:
return <img src={CoinbaseWalletIcon} alt="Coinbase Wallet" />
case fortmatic:
return <img src={FortmaticIcon} alt={'Fortmatic'} />
return <img src={FortmaticIcon} alt="Fortmatic" />
default:
return null
}
......
......@@ -4,15 +4,14 @@ import Badge from 'components/Badge'
import { CHAIN_INFO } from 'constants/chainInfo'
import { L2_CHAIN_IDS, SupportedL2ChainId } from 'constants/chains'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import useAddTokenToMetamask from 'hooks/useAddTokenToMetamask'
import { ReactNode, useContext } from 'react'
import useCurrencyLogoURIs from 'lib/hooks/useCurrencyLogoURIs'
import { ReactNode, useCallback, useContext, useState } from 'react'
import { AlertCircle, AlertTriangle, ArrowUpCircle, CheckCircle } from 'react-feather'
import { Text } from 'rebass'
import { useIsTransactionConfirmed, useTransaction } from 'state/transactions/hooks'
import styled, { ThemeContext } from 'styled-components/macro'
import Circle from '../../assets/images/blue-loader.svg'
import MetaMaskLogo from '../../assets/images/metamask.png'
import { ExternalLink } from '../../theme'
import { CloseIcon, CustomLightSpinner } from '../../theme'
import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink'
......@@ -97,9 +96,25 @@ function TransactionSubmittedContent({
}) {
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 (
<Wrapper>
......@@ -124,13 +139,11 @@ function TransactionSubmittedContent({
</Text>
</ExternalLink>
)}
{currencyToAdd && library?.provider?.isMetaMask && (
{currencyToAdd && connector.watchAsset && (
<ButtonLight mt="12px" padding="6px 12px" width="fit-content" onClick={addToken}>
{!success ? (
<RowFixed>
<Trans>
Add {currencyToAdd.symbol} to Metamask <StyledLogo src={MetaMaskLogo} />
</Trans>
<Trans>Add {currencyToAdd.symbol}</Trans>
</RowFixed>
) : (
<RowFixed>
......
import { Trans } from '@lingui/macro'
import { Connector } from '@web3-react/types'
import { ButtonEmpty, ButtonPrimary } from 'components/Button'
import styled from 'styled-components/macro'
import { ThemedText } from 'theme'
import { AbstractConnector } from 'web3-react-abstract-connector'
import Loader from '../Loader'
......@@ -49,15 +49,13 @@ const LoadingWrapper = styled.div`
export default function PendingView({
connector,
error = false,
setPendingError,
tryActivation,
resetAccountView,
openOptions,
}: {
connector?: AbstractConnector
connector: Connector
error?: boolean
setPendingError: (error: boolean) => void
tryActivation: (connector: AbstractConnector) => void
resetAccountView: () => void
tryActivation: (connector: Connector) => void
openOptions: () => void
}) {
return (
<PendingSection>
......@@ -77,14 +75,13 @@ export default function PendingView({
$borderRadius="12px"
padding="12px"
onClick={() => {
setPendingError(false)
connector && tryActivation(connector)
tryActivation(connector)
}}
>
<Trans>Try Again</Trans>
</ButtonPrimary>
<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>
</ThemedText.Link>
</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
import { t, Trans } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core'
import { Connector } from '@web3-react/types'
import { getWalletForConnector } from 'connectors'
import { darken } from 'polished'
import { useMemo } from 'react'
import { Activity } from 'react-feather'
import { useAppSelector } from 'state/hooks'
import styled, { css } from 'styled-components/macro'
import { AbstractConnector } from 'web3-react-abstract-connector'
import { UnsupportedChainIdError, useWeb3React } from 'web3-react-core'
import { isChainAllowed } from 'utils/switchChain'
import { NetworkContextName } from '../../constants/misc'
import useENSName from '../../hooks/useENSName'
import { useHasSocks } from '../../hooks/useSocksBalance'
import { useWalletModalToggle } from '../../state/application/hooks'
import { isTransactionRecent, useAllTransactions } from '../../state/transactions/hooks'
......@@ -131,7 +131,7 @@ function Sock() {
)
}
function WrappedStatusIcon({ connector }: { connector: AbstractConnector | Connector }) {
function WrappedStatusIcon({ connector }: { connector: Connector }) {
return (
<IconWrapper size={16}>
<StatusIcon connector={connector} />
......@@ -140,9 +140,11 @@ function WrappedStatusIcon({ connector }: { connector: AbstractConnector | Conne
}
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()
......@@ -157,7 +159,27 @@ function Web3StatusInner() {
const hasSocks = useHasSocks()
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 (
<Web3StatusConnected id="web3-status-connected" onClick={toggleWalletModal} pending={hasPendingTransactions}>
{hasPendingTransactions ? (
......@@ -176,13 +198,6 @@ function Web3StatusInner() {
{!hasPendingTransactions && connector && <WrappedStatusIcon connector={connector} />}
</Web3StatusConnected>
)
} else if (error) {
return (
<Web3StatusError onClick={toggleWalletModal}>
<NetworkIcon />
<Text>{error instanceof UnsupportedChainIdError ? <Trans>Wrong Network</Trans> : <Trans>Error</Trans>}</Text>
</Web3StatusError>
)
} else {
return (
<Web3StatusConnect id="connect-wallet" onClick={toggleWalletModal} faded={!account}>
......@@ -195,10 +210,7 @@ function Web3StatusInner() {
}
export default function Web3Status() {
const { active, account } = useWeb3React()
const contextNetwork = useWeb3React(NetworkContextName)
const { ENSName } = useENSName(account ?? undefined)
const { ENSName } = useWeb3React()
const allTransactions = useAllTransactions()
......@@ -213,9 +225,7 @@ export default function Web3Status() {
return (
<>
<Web3StatusInner />
{(contextNetwork.active || active) && (
<WalletModal ENSName={ENSName ?? undefined} pendingTransactions={pending} confirmedTransactions={confirmed} />
)}
</>
)
}
......@@ -53,7 +53,7 @@ interface StakingModalProps {
}
export default function StakingModal({ isOpen, onDismiss, stakingInfo, userLiquidityUnstaked }: StakingModalProps) {
const { library } = useActiveWeb3React()
const { provider } = useActiveWeb3React()
// track and parse user input
const [typedValue, setTypedValue] = useState('')
......@@ -144,7 +144,7 @@ export default function StakingModal({ isOpen, onDismiss, stakingInfo, userLiqui
}, [maxAmountInput, onUserInput])
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 (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 { SafeAppConnector } from '@gnosis.pm/safe-apps-web3-react'
import { ALL_SUPPORTED_CHAIN_IDS, SupportedChainId } from 'constants/chains'
import { CoinbaseWallet } from '@web3-react/coinbase-wallet'
import { initializeConnector, Web3ReactHooks } from '@web3-react/core'
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 { InjectedConnector } from 'web3-react-injected-connector'
import { WalletConnectConnector } from 'web3-react-walletconnect-connector'
import { WalletLinkConnector } from 'web3-react-walletlink-connector'
import Fortmatic from 'fortmatic'
import { useMemo } from 'react'
import UNISWAP_LOGO_URL from '../assets/svg/logo.svg'
import getLibrary from '../utils/getLibrary'
import { FortmaticConnector } from './Fortmatic'
import { NetworkConnector } from './NetworkConnector'
const FORMATIC_KEY = process.env.REACT_APP_FORTMATIC_KEY
export enum Wallet {
INJECTED = 'INJECTED',
COINBASE_WALLET = 'COINBASE_WALLET',
WALLET_CONNECT = 'WALLET_CONNECT',
FORTMATIC = 'FORTMATIC',
NETWORK = 'NETWORK',
GNOSIS_SAFE = 'GNOSIS_SAFE',
}
export const BACKFILLABLE_WALLETS = [Wallet.COINBASE_WALLET, Wallet.WALLET_CONNECT, Wallet.INJECTED]
export const SELECTABLE_WALLETS = [...BACKFILLABLE_WALLETS, Wallet.FORTMATIC]
function onError(error: Error) {
console.debug(`web3-react error: ${error}`)
}
export const network = new NetworkConnector({
urls: INFURA_NETWORK_URLS,
defaultChainId: 1,
})
export function getWalletForConnector(connector: Connector) {
switch (connector) {
case injected:
return Wallet.INJECTED
case coinbaseWallet:
return Wallet.COINBASE_WALLET
case walletConnect:
return Wallet.WALLET_CONNECT
case fortmatic:
return Wallet.FORTMATIC
case network:
return Wallet.NETWORK
case gnosisSafe:
return Wallet.GNOSIS_SAFE
default:
throw Error('unsupported connector')
}
}
export function getConnectorForWallet(wallet: Wallet) {
switch (wallet) {
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
}
}
let networkLibrary: Web3Provider | undefined
export function getNetworkLibrary(): Web3Provider {
return (networkLibrary = networkLibrary ?? getLibrary(network.provider))
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 injected = new InjectedConnector({
supportedChainIds: ALL_SUPPORTED_CHAIN_IDS,
})
export const [network, networkHooks] = initializeConnector<Network>(
(actions) => new Network({ actions, urlMap: INFURA_NETWORK_URLS, defaultChainId: 1 })
)
export const gnosisSafe = new SafeAppConnector()
export const [injected, injectedHooks] = initializeConnector<MetaMask>((actions) => new MetaMask({ actions, onError }))
export const walletconnect = new WalletConnectConnector({
supportedChainIds: ALL_SUPPORTED_CHAIN_IDS,
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,
})
)
// mainnet only
export const fortmatic = new FortmaticConnector({
apiKey: FORMATIC_KEY ?? '',
chainId: 1,
})
export const [fortmatic, fortmaticHooks] = initializeConnector<EIP1193>(
(actions) => new EIP1193({ actions, provider: new Fortmatic(process.env.REACT_APP_FORTMATIC_KEY).getProvider() })
)
export const walletlink = new WalletLinkConnector({
export const [coinbaseWallet, coinbaseWalletHooks] = initializeConnector<CoinbaseWallet>(
(actions) =>
new CoinbaseWallet({
actions,
options: {
url: INFURA_NETWORK_URLS[SupportedChainId.MAINNET],
appName: 'Uniswap',
appLogoUrl: UNISWAP_LOGO_URL,
supportedChainIds: ALL_SUPPORTED_CHAIN_IDS,
})
},
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'
const INFURA_KEY = process.env.REACT_APP_INFURA_KEY
......@@ -5,6 +7,8 @@ if (typeof INFURA_KEY === 'undefined') {
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
*/
......
import { AbstractConnector } from 'web3-react-abstract-connector'
import { Connector } from '@web3-react/types'
import INJECTED_ICON_URL from '../assets/images/arrow-right.svg'
import COINBASE_ICON_URL from '../assets/images/coinbaseWalletIcon.svg'
import FORTMATIC_ICON_URL from '../assets/images/fortmaticIcon.png'
import METAMASK_ICON_URL from '../assets/images/metamask.png'
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 {
connector?: AbstractConnector
connector?: Connector
wallet?: Wallet
name: string
iconURL: string
description: string
......@@ -22,6 +23,7 @@ interface WalletInfo {
export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = {
INJECTED: {
connector: injected,
wallet: Wallet.INJECTED,
name: 'Injected',
iconURL: INJECTED_ICON_URL,
description: 'Injected web3 provider.',
......@@ -31,6 +33,7 @@ export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = {
},
METAMASK: {
connector: injected,
wallet: Wallet.INJECTED,
name: 'MetaMask',
iconURL: METAMASK_ICON_URL,
description: 'Easy-to-use browser extension.',
......@@ -38,7 +41,8 @@ export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = {
color: '#E8831D',
},
WALLET_CONNECT: {
connector: walletconnect,
connector: walletConnect,
wallet: Wallet.WALLET_CONNECT,
name: 'WalletConnect',
iconURL: WALLETCONNECT_ICON_URL,
description: 'Connect to Trust Wallet, Rainbow Wallet and more...',
......@@ -46,8 +50,9 @@ export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = {
color: '#4196FC',
mobile: true,
},
WALLET_LINK: {
connector: walletlink,
COINBASE_WALLET: {
connector: coinbaseWallet,
wallet: Wallet.COINBASE_WALLET,
name: 'Coinbase Wallet',
iconURL: COINBASE_ICON_URL,
description: 'Use Coinbase Wallet app on mobile device',
......@@ -65,6 +70,7 @@ export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = {
},
FORTMATIC: {
connector: fortmatic,
wallet: Wallet.FORTMATIC,
name: 'Fortmatic',
iconURL: FORTMATIC_ICON_URL,
description: 'Login using Fortmatic hosted wallet',
......
/* eslint-disable react-hooks/rules-of-hooks */
import { Web3Provider } from '@ethersproject/providers'
import { useWeb3React } from 'web3-react-core'
import { NetworkContextName } from '../constants/misc'
// TODO(vm): Rm this file once #3759 is merged.
import { useWeb3React } from '@web3-react/core'
export default function useActiveWeb3React() {
const interfaceContext = useWeb3React<Web3Provider>()
const interfaceNetworkContext = useWeb3React<Web3Provider>(
process.env.REACT_APP_IS_WIDGET ? undefined : NetworkContextName
)
if (interfaceContext.active) {
return interfaceContext
}
return interfaceNetworkContext
return useWeb3React()
}
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>(
ABI: any,
withSignerIfPossible = true
): T | null {
const { library, account, chainId } = useActiveWeb3React()
const { provider, account, chainId } = useActiveWeb3React()
return useMemo(() => {
if (!addressOrAddressMap || !ABI || !library || !chainId) return null
if (!addressOrAddressMap || !ABI || !provider || !chainId) return null
let address: string | undefined
if (typeof addressOrAddressMap === 'string') address = addressOrAddressMap
else address = addressOrAddressMap[chainId]
if (!address) return null
try {
return getContract(address, ABI, library, withSignerIfPossible && account ? account : undefined)
return getContract(address, ABI, provider, withSignerIfPossible && account ? account : undefined)
} catch (error) {
console.error('Failed to get contract', error)
return null
}
}, [addressOrAddressMap, ABI, library, chainId, withSignerIfPossible, account]) as T
}, [addressOrAddressMap, ABI, provider, chainId, withSignerIfPossible, account]) as T
}
export function useV2MigratorContract() {
......
......@@ -126,7 +126,7 @@ export function useERC20Permit(
state: UseERC20PermitState
gatherPermitSignature: null | (() => Promise<void>)
} {
const { account, chainId, library } = useActiveWeb3React()
const { account, chainId, provider } = useActiveWeb3React()
const tokenAddress = currencyAmount?.currency?.isToken ? currencyAmount.currency.address : undefined
const eip2612Contract = useEIP2612Contract(tokenAddress)
const isArgentWallet = useIsArgentWallet()
......@@ -145,7 +145,7 @@ export function useERC20Permit(
!account ||
!chainId ||
!transactionDeadline ||
!library ||
!provider ||
!tokenNonceState.valid ||
!tokenAddress ||
!spender ||
......@@ -221,7 +221,7 @@ export function useERC20Permit(
message,
})
return library
return provider
.send('eth_signTypedData_v4', [account, data])
.then(splitSignature)
.then((signature) => {
......@@ -248,7 +248,7 @@ export function useERC20Permit(
chainId,
isArgentWallet,
transactionDeadline,
library,
provider,
tokenNonceState.loading,
tokenNonceState.valid,
tokenNonceState.result,
......
import { nanoid } from '@reduxjs/toolkit'
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 resolveENSContentHash from 'lib/utils/resolveENSContentHash'
import { useCallback } from 'react'
import { useAppDispatch } from 'state/hooks'
import { getNetworkLibrary } from '../connectors'
import { fetchTokenList } from '../state/lists/actions'
export function useFetchListCallback(): (listUrl: string, sendDispatch?: boolean) => Promise<TokenList> {
const { chainId, library } = useActiveWeb3React()
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
return useCallback(
async (listUrl: string, sendDispatch = true) => {
const requestId = nanoid()
sendDispatch && dispatch(fetchTokenList.pending({ requestId, url: listUrl }))
return getTokenList(listUrl, ensResolver)
return getTokenList(listUrl, (ensName: string) => resolveENSContentHash(ensName, MAINNET_PROVIDER))
.then((tokenList) => {
sendDispatch && dispatch(fetchTokenList.fulfilled({ url: listUrl, tokenList, requestId }))
return tokenList
......@@ -44,6 +27,6 @@ export function useFetchListCallback(): (listUrl: string, sendDispatch?: boolean
throw error
})
},
[dispatch, ensResolver]
[dispatch]
)
}
......@@ -39,7 +39,7 @@ export function useSwapCallArguments(
deadline: BigNumber | undefined,
feeOptions: FeeOptions | undefined
): SwapCall[] {
const { account, chainId, library } = useActiveWeb3React()
const { account, chainId, provider } = useActiveWeb3React()
const { address: recipientAddress } = useENS(recipientAddressOrName)
const recipient = recipientAddressOrName === null ? account : recipientAddress
......@@ -47,7 +47,7 @@ export function useSwapCallArguments(
const argentWalletContract = useArgentWalletContract()
return useMemo(() => {
if (!trade || !recipient || !library || !account || !chainId || !deadline) return []
if (!trade || !recipient || !provider || !account || !chainId || !deadline) return []
if (trade instanceof V2Trade) {
if (!routerContract) return []
......@@ -175,7 +175,7 @@ export function useSwapCallArguments(
chainId,
deadline,
feeOptions,
library,
provider,
recipient,
routerContract,
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'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import { HashRouter } from 'react-router-dom'
import { createWeb3ReactRoot, Web3ReactProvider } from 'web3-react-core'
import Blocklist from './components/Blocklist'
import { NetworkContextName } from './constants/misc'
import Web3Provider from './components/Web3Provider'
import { LanguageProvider } from './i18n'
import App from './pages/App'
import * as serviceWorkerRegistration from './serviceWorkerRegistration'
......@@ -24,9 +23,6 @@ import TransactionUpdater from './state/transactions/updater'
import UserUpdater from './state/user/updater'
import ThemeProvider, { ThemedGlobalStyle } from './theme'
import RadialGradientByChainUpdater from './theme/RadialGradientByChainUpdater'
import getLibrary from './utils/getLibrary'
const Web3ProviderNetwork = createWeb3ReactRoot(NetworkContextName)
if (!!window.ethereum) {
window.ethereum.autoRefreshOnNetworkChange = false
......@@ -51,8 +47,7 @@ ReactDOM.render(
<Provider store={store}>
<HashRouter>
<LanguageProvider>
<Web3ReactProvider getLibrary={getLibrary}>
<Web3ProviderNetwork getLibrary={getLibrary}>
<Web3Provider>
<Blocklist>
<BlockNumberProvider>
<Updaters />
......@@ -62,8 +57,7 @@ ReactDOM.render(
</ThemeProvider>
</BlockNumberProvider>
</Blocklist>
</Web3ProviderNetwork>
</Web3ReactProvider>
</Web3Provider>
</LanguageProvider>
</HashRouter>
</Provider>
......
......@@ -42,7 +42,7 @@ export function useSwapCallback({
deadline,
feeOptions,
}: UseSwapCallbackArgs): UseSwapCallbackReturns {
const { account, chainId, library } = useActiveWeb3React()
const { account, chainId, provider } = useActiveWeb3React()
const swapCalls = useSwapCallArguments(
trade,
......@@ -52,13 +52,13 @@ export function useSwapCallback({
deadline,
feeOptions
)
const { callback } = useSendSwapTransaction(account, chainId, library, trade, swapCalls)
const { callback } = useSendSwapTransaction(account, chainId, provider, trade, swapCalls)
const { address: recipientAddress } = useENS(recipientAddressOrName)
const recipient = recipientAddressOrName === null ? account : recipientAddress
return useMemo(() => {
if (!trade || !library || !account || !chainId || !callback) {
if (!trade || !provider || !account || !chainId || !callback) {
return { state: SwapCallbackState.INVALID, error: <Trans>Missing dependencies</Trans> }
}
if (!recipient) {
......@@ -73,5 +73,5 @@ export function useSwapCallback({
state: SwapCallbackState.VALID,
callback: async () => callback(),
}
}, [trade, library, account, chainId, callback, recipient, recipientAddressOrName])
}, [trade, provider, account, chainId, callback, recipient, recipientAddressOrName])
}
......@@ -45,18 +45,18 @@ interface UpdaterProps {
}
export default function Updater({ pendingTransactions, onCheck, onReceipt }: UpdaterProps): null {
const { chainId, library } = useActiveWeb3React()
const { chainId, provider } = useActiveWeb3React()
const lastBlockNumber = useBlockNumber()
const fastForwardBlockNumber = useFastForwardBlockNumber()
const getReceipt = useCallback(
(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
return retry(
() =>
library.getTransactionReceipt(hash).then((receipt) => {
provider.getTransactionReceipt(hash).then((receipt) => {
if (receipt === null) {
console.debug(`Retrying tranasaction receipt for ${hash}`)
throw new RetryableError()
......@@ -66,11 +66,11 @@ export default function Updater({ pendingTransactions, onCheck, onReceipt }: Upd
retryOptions
)
},
[chainId, library]
[chainId, provider]
)
useEffect(() => {
if (!chainId || !library || !lastBlockNumber) return
if (!chainId || !provider || !lastBlockNumber) return
const cancels = Object.keys(pendingTransactions)
.filter((hash) => shouldCheck(lastBlockNumber, pendingTransactions[hash]))
......@@ -95,7 +95,7 @@ export default function Updater({ pendingTransactions, onCheck, onReceipt }: Upd
return () => {
cancels.forEach((cancel) => cancel())
}
}, [chainId, library, lastBlockNumber, getReceipt, fastForwardBlockNumber, onReceipt, onCheck, pendingTransactions])
}, [chainId, provider, lastBlockNumber, getReceipt, fastForwardBlockNumber, onReceipt, onCheck, pendingTransactions])
return null
}
......@@ -29,7 +29,7 @@ export function useFastForwardBlockNumber(): (block: number) => void {
}
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 onBlock = useCallback(
......@@ -48,24 +48,24 @@ export function BlockNumberProvider({ children }: { children: ReactNode }) {
const windowVisible = useIsWindowVisible()
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.
setChainBlock((chainBlock) => (chainBlock.chainId === activeChainId ? chainBlock : { chainId: activeChainId }))
library
provider
.getBlockNumber()
.then(onBlock)
.catch((error) => {
console.error(`Failed to get block number for chainId ${activeChainId}`, error)
})
library.on('block', onBlock)
provider.on('block', onBlock)
return () => {
library.removeListener('block', onBlock)
provider.removeListener('block', onBlock)
}
}
return undefined
}, [activeChainId, library, onBlock, setChainBlock, windowVisible])
}, [activeChainId, provider, onBlock, setChainBlock, windowVisible])
const value = useMemo(
() => ({
......
......@@ -6,6 +6,7 @@ import { useBytes32TokenContract, useTokenContract } from 'hooks/useContract'
import { NEVER_RELOAD, useSingleCallResult } from 'lib/hooks/multicall'
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
import { useMemo } from 'react'
import { isChainAllowed } from 'utils/switchChain'
import { TOKEN_SHORTHANDS } from '../../constants/tokens'
import { isAddress } from '../../utils'
......@@ -29,7 +30,8 @@ function parseStringOrBytes32(str: string | undefined, bytes32: string | undefin
* Returns undefined if tokenAddress is invalid or token does not exist.
*/
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)
......@@ -43,7 +45,7 @@ export function useTokenFromNetwork(tokenAddress: string | null | undefined): To
const decimals = useSingleCallResult(tokenContract, 'decimals', undefined, NEVER_RELOAD)
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.result) {
return new Token(
......@@ -58,6 +60,7 @@ export function useTokenFromNetwork(tokenAddress: string | null | undefined): To
}, [
formattedAddress,
chainId,
chainAllowed,
decimals.loading,
decimals.result,
symbol.loading,
......@@ -93,7 +96,7 @@ export function useTokenFromMapOrNetwork(tokens: TokenMap, tokenAddress?: string
*/
export function useCurrencyFromMap(tokens: TokenMap, currencyId?: string | null): Currency | null | undefined {
const nativeCurrency = useNativeCurrency()
const { chainId } = useActiveWeb3React()
const { chainId, connector } = useActiveWeb3React()
const isNative = Boolean(nativeCurrency && currencyId?.toUpperCase() === 'ETH')
const shorthandMatchAddress = useMemo(() => {
const chain = supportedChainId(chainId)
......@@ -102,7 +105,8 @@ export function useCurrencyFromMap(tokens: TokenMap, currencyId?: string | null)
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
const wrappedNative = nativeCurrency?.wrapped
......
......@@ -81,7 +81,7 @@ export default function AddLiquidity({
},
history,
}: RouteComponentProps<{ currencyIdA?: string; currencyIdB?: string; feeAmount?: string; tokenId?: string }>) {
const { account, chainId, library } = useActiveWeb3React()
const { account, chainId, provider } = useActiveWeb3React()
const theme = useContext(ThemeContext)
const toggleWalletModal = useWalletModalToggle() // toggle wallet when disconnected
const expertMode = useIsExpertMode()
......@@ -225,7 +225,7 @@ export default function AddLiquidity({
)
async function onAdd() {
if (!chainId || !library || !account) return
if (!chainId || !provider || !account) return
if (!positionManager || !baseCurrency || !quoteCurrency) {
return
......@@ -281,7 +281,7 @@ export default function AddLiquidity({
setAttemptingTxn(true)
library
provider
.getSigner()
.estimateGas(txn)
.then((estimate) => {
......@@ -290,7 +290,7 @@ export default function AddLiquidity({
gasLimit: calculateGasMargin(estimate),
}
return library
return provider
.getSigner()
.sendTransaction(newTxn)
.then((response: TransactionResponse) => {
......
......@@ -53,7 +53,7 @@ export default function AddLiquidity({
},
history,
}: RouteComponentProps<{ currencyIdA?: string; currencyIdB?: string }>) {
const { account, chainId, library } = useActiveWeb3React()
const { account, chainId, provider } = useActiveWeb3React()
const theme = useContext(ThemeContext)
......@@ -137,7 +137,7 @@ export default function AddLiquidity({
const addTransaction = useTransactionAdder()
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
if (!parsedAmountA || !parsedAmountB || !currencyA || !currencyB || !deadline) {
......
......@@ -11,7 +11,6 @@ import ErrorBoundary from '../components/ErrorBoundary'
import Header from '../components/Header'
import Polling from '../components/Header/Polling'
import Popups from '../components/Popups'
import Web3ReactManager from '../components/Web3ReactManager'
import DarkModeQueryParamReader from '../theme/DarkModeQueryParamReader'
import AddLiquidity from './AddLiquidity'
import { RedirectDuplicateTokenIds } from './AddLiquidity/redirects'
......@@ -81,7 +80,6 @@ export default function App() {
<ErrorBoundary>
<Route component={DarkModeQueryParamReader} />
<Route component={ApeModeQueryParamReader} />
<Web3ReactManager>
<AppWrapper>
<HeaderWrapper>
<Header />
......@@ -109,12 +107,7 @@ export default function App() {
<Route exact strict path="/pool" component={Pool} />
<Route exact strict path="/pool/:tokenId" component={PositionPage} />
<Route
exact
strict
path="/add/v2/:currencyIdA?/:currencyIdB?"
component={RedirectDuplicateTokenIdsV2}
/>
<Route exact strict path="/add/v2/:currencyIdA?/:currencyIdB?" component={RedirectDuplicateTokenIdsV2} />
<Route
exact
strict
......@@ -141,7 +134,6 @@ export default function App() {
<Marginer />
</BodyWrapper>
</AppWrapper>
</Web3ReactManager>
</ErrorBoundary>
)
}
......@@ -318,7 +318,7 @@ export function PositionPage({
params: { tokenId: tokenIdFromUrl },
},
}: RouteComponentProps<{ tokenId?: string }>) {
const { chainId, account, library } = useActiveWeb3React()
const { chainId, account, provider } = useActiveWeb3React()
const theme = useTheme()
const parsedTokenId = tokenIdFromUrl ? BigNumber.from(tokenIdFromUrl) : undefined
......@@ -433,7 +433,7 @@ export function PositionPage({
!positionManager ||
!account ||
!tokenId ||
!library
!provider
)
return
......@@ -454,7 +454,7 @@ export function PositionPage({
value,
}
library
provider
.getSigner()
.estimateGas(txn)
.then((estimate) => {
......@@ -463,7 +463,7 @@ export function PositionPage({
gasLimit: calculateGasMargin(estimate),
}
return library
return provider
.getSigner()
.sendTransaction(newTxn)
.then((response: TransactionResponse) => {
......@@ -497,7 +497,7 @@ export function PositionPage({
account,
tokenId,
addTransaction,
library,
provider,
])
const owner = useSingleCallResult(!!tokenId ? positionManager : null, 'ownerOf', [tokenId]).result?.[0]
......
......@@ -66,7 +66,7 @@ export default function RemoveLiquidityV3({
function Remove({ tokenId }: { tokenId: BigNumber }) {
const { position } = useV3PositionFromTokenId(tokenId)
const theme = useTheme()
const { account, chainId, library } = useActiveWeb3React()
const { account, chainId, provider } = useActiveWeb3React()
// flag for receiving WETH
const [receiveWETH, setReceiveWETH] = useState(false)
......@@ -111,7 +111,7 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
!chainId ||
!positionSDK ||
!liquidityPercentage ||
!library
!provider
) {
return
}
......@@ -136,7 +136,7 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
value,
}
library
provider
.getSigner()
.estimateGas(txn)
.then((estimate) => {
......@@ -145,7 +145,7 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
gasLimit: calculateGasMargin(estimate),
}
return library
return provider
.getSigner()
.sendTransaction(newTxn)
.then((response: TransactionResponse) => {
......@@ -180,7 +180,7 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
feeValue1,
positionSDK,
liquidityPercentage,
library,
provider,
tokenId,
allowedSlippage,
addTransaction,
......
......@@ -52,7 +52,7 @@ export default function RemoveLiquidity({
},
}: RouteComponentProps<{ currencyIdA: string; currencyIdB: string }>) {
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 theme = useContext(ThemeContext)
......@@ -105,7 +105,7 @@ export default function RemoveLiquidity({
const [approval, approveCallback] = useApproveCallback(parsedAmounts[Field.LIQUIDITY], router?.address)
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]
if (!liquidityAmount) throw new Error('missing liquidity amount')
......@@ -148,7 +148,7 @@ export default function RemoveLiquidity({
const addTransaction = useTransactionAdder()
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
if (!currencyAmountA || !currencyAmountB) {
throw new Error('missing currency amounts')
......
......@@ -22,7 +22,7 @@ function useQueryCacheInvalidator() {
}
export default function Updater(): null {
const { chainId, library } = useActiveWeb3React()
const { chainId, provider } = useActiveWeb3React()
const dispatch = useAppDispatch()
const windowVisible = useIsWindowVisible()
......@@ -31,10 +31,10 @@ export default function Updater(): null {
useQueryCacheInvalidator()
useEffect(() => {
if (library && chainId && windowVisible) {
if (provider && chainId && windowVisible) {
setActiveChainId(chainId)
}
}, [dispatch, chainId, library, windowVisible])
}, [dispatch, chainId, provider, windowVisible])
const debouncedChainId = useDebounce(activeChainId, 100)
......
......@@ -155,7 +155,7 @@ export function useClaimCallback(account: string | null | undefined): {
claimCallback: () => Promise<string>
} {
// get claim data for this account
const { library, chainId } = useActiveWeb3React()
const { provider, chainId } = useActiveWeb3React()
const claimData = useUserClaimData(account)
// used for popup summary
......@@ -164,7 +164,7 @@ export function useClaimCallback(account: string | null | undefined): {
const distributorContract = useMerkleDistributorContract()
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]
......
......@@ -384,14 +384,14 @@ export function useUserVotesAsOfBlock(block: number | undefined): CurrencyAmount
}
export function useDelegateCallback(): (delegatee: string | undefined) => undefined | Promise<string> {
const { account, chainId, library } = useActiveWeb3React()
const { account, chainId, provider } = useActiveWeb3React()
const addTransaction = useTransactionAdder()
const uniContract = useUniContract()
return useCallback(
(delegatee: string | undefined) => {
if (!library || !chainId || !account || !delegatee || !isAddress(delegatee ?? '')) return undefined
if (!provider || !chainId || !account || !delegatee || !isAddress(delegatee ?? '')) return undefined
const args = [delegatee]
if (!uniContract) throw new Error('No UNI Contract!')
return uniContract.estimateGas.delegate(...args, {}).then((estimatedGasLimit) => {
......@@ -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'
import swap from './swap/reducer'
import transactions from './transactions/reducer'
import user from './user/reducer'
import wallet from './wallet/reducer'
const PERSISTED_KEYS: string[] = ['user', 'transactions', 'lists']
......@@ -23,6 +24,7 @@ const store = configureStore({
reducer: {
application,
user,
wallet,
transactions,
swap,
mint,
......
......@@ -13,7 +13,7 @@ import { acceptListUpdate, enableList } from './actions'
import { useActiveListUrls } from './hooks'
export default function Updater(): null {
const { chainId, library } = useActiveWeb3React()
const { chainId, provider } = useActiveWeb3React()
const dispatch = useAppDispatch()
const isWindowVisible = useIsWindowVisible()
......@@ -37,8 +37,8 @@ export default function Updater(): null {
dispatch(enableList(ARBITRUM_LIST))
}
}, [chainId, dispatch])
// fetch all lists every 10 minutes, but only after we initialize library
useInterval(fetchAllListsCallback, library ? 1000 * 60 * 10 : null)
// fetch all lists every 10 minutes, but only after we initialize provider
useInterval(fetchAllListsCallback, provider ? 1000 * 60 * 10 : null)
// whenever a list is not loaded and not loading, try again to load it
useEffect(() => {
......@@ -48,7 +48,7 @@ export default function Updater(): null {
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)
useEffect(() => {
......@@ -58,7 +58,7 @@ export default function Updater(): null {
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
useEffect(() => {
......
......@@ -10,7 +10,7 @@ import { isHistoricalLog, keyToFilter } from './utils'
export default function Updater(): null {
const dispatch = useAppDispatch()
const state = useAppSelector((state) => state.logs)
const { chainId, library } = useActiveWeb3React()
const { chainId, provider } = useActiveWeb3React()
const blockNumber = useBlockNumber()
......@@ -34,7 +34,7 @@ export default function Updater(): null {
}, [blockNumber, chainId, state])
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 }))
filtersNeedFetch.forEach((filter) => {
......@@ -43,7 +43,7 @@ export default function Updater(): null {
let toBlock = filter.toBlock ?? blockNumber
if (typeof fromBlock === 'string') fromBlock = Number.parseInt(fromBlock)
if (typeof toBlock === 'string') toBlock = Number.parseInt(toBlock)
library
provider
.getLogs({
...filter,
fromBlock,
......@@ -69,7 +69,7 @@ export default function Updater(): null {
)
})
})
}, [blockNumber, chainId, dispatch, filtersNeedFetch, library])
}, [blockNumber, chainId, dispatch, filtersNeedFetch, provider])
return null
}
import { createSlice } from '@reduxjs/toolkit'
import { Wallet } from 'connectors'
import { SupportedLocale } from 'constants/locales'
import { DEFAULT_DEADLINE_FROM_NOW } from '../../constants/misc'
......@@ -8,6 +9,13 @@ import { SerializedPair, SerializedToken } from './types'
const currentTimestamp = () => new Date().getTime()
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
lastUpdateVersionTimestamp?: number
......@@ -57,6 +65,8 @@ function pairKey(token0Address: string, token1Address: string) {
}
export const initialState: UserState = {
selectedWallet: undefined,
selectedWalletBackfilled: false,
matchesDarkMode: false,
userDarkMode: null,
userExpertMode: false,
......@@ -78,6 +88,10 @@ const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
updateSelectedWallet(state, { payload: { wallet } }) {
state.selectedWallet = wallet
state.selectedWalletBackfilled = true
},
updateUserDarkMode(state, action) {
state.userDarkMode = action.payload.userDarkMode
state.timestamp = currentTimestamp()
......@@ -188,6 +202,7 @@ const userSlice = createSlice({
})
export const {
updateSelectedWallet,
addSerializedPair,
addSerializedToken,
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 { hexStripZeros } from '@ethersproject/bytes'
import { ExternalProvider } from '@ethersproject/providers'
import { Connector } from '@web3-react/types'
import { coinbaseWallet, fortmatic, gnosisSafe, injected, network, walletConnect } from 'connectors'
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'
interface SwitchNetworkArguments {
provider: ExternalProvider
chainId: SupportedChainId
}
function getRpcUrls(chainId: SupportedChainId): [string] {
switch (chainId) {
case SupportedChainId.MAINNET:
......@@ -36,48 +30,35 @@ function getRpcUrls(chainId: SupportedChainId): [string] {
throw new Error('RPC URLs must use public endpoints')
}
// provider.request returns Promise<any>, but wallet_switchEthereumChain must return null or throw
// see https://github.com/rekmarks/EIPs/blob/3326-create/EIPS/eip-3326.md for more info on wallet_switchEthereumChain
export async function switchToNetwork({ provider, chainId }: SwitchNetworkArguments): Promise<null | void> {
if (!provider.request) {
return
export function isChainAllowed(connector: Connector, chainId: number) {
switch (connector) {
case fortmatic:
return chainId === SupportedChainId.MAINNET
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({
method: 'wallet_addEthereumChain',
params: [
{
chainId: formattedChainId,
export const switchChain = async (connector: Connector, chainId: number) => {
if (!isChainAllowed(connector, chainId)) {
throw new Error(`Chain ${chainId} not supported for connector (${typeof connector})`)
} else if (connector === walletConnect || connector === network) {
await connector.activate(chainId)
} else {
const info = CHAIN_INFO[chainId]
const addChainParameter = {
chainId,
chainName: info.label,
rpcUrls: getRpcUrls(chainId),
nativeCurrency: info.nativeCurrency,
blockExplorerUrls: [info.explorer],
},
],
})
// metamask (only known implementer) automatically switches after a network is added
// 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