refactor: extract Web3Provider hooks, create internal Connection representation (#4040)

* refactor: separate hooks file for Web3Provider

* move utils

* rename + comments

* rename Wallet enum to ConnectionType

* more wallet -> connectiontype

* more wallet -> connectiontype

* move hooks

* use Connection everywhere

* connector -> connection

* generic getConnection

* rename injected -> injectedConnection

* check connectionType

* rm unused
This commit is contained in:
Vignesh Mohankumar 2022-07-07 09:17:49 -10:00 committed by GitHub
parent 48a962a750
commit 4e462ddbef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 389 additions and 361 deletions

@ -1,7 +1,8 @@
import { Trans } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core'
import { Connector } from '@web3-react/types'
import CopyHelper from 'components/AccountDetails/Copy'
import { coinbaseWalletConnection, injectedConnection } from 'connection'
import { getConnection } from 'connection/utils'
import { useCallback, useContext } from 'react'
import { ExternalLink as LinkIcon } from 'react-feather'
import { useAppDispatch } from 'state/hooks'
@ -10,7 +11,6 @@ import styled, { ThemeContext } from 'styled-components/macro'
import { isMobile } from 'utils/userAgent'
import { ReactComponent as Close } from '../../assets/images/x.svg'
import { coinbaseWallet, injected } from '../../connectors'
import { SUPPORTED_WALLETS } from '../../constants/wallet'
import { clearAllTransactions } from '../../state/transactions/reducer'
import { ExternalLink, LinkStyledButton, ThemedText } from '../../theme'
@ -163,29 +163,6 @@ const WalletName = styled.div`
color: ${({ theme }) => theme.text3};
`
const IconWrapper = styled.div<{ size?: number }>`
${({ theme }) => theme.flexColumnNoWrap};
align-items: center;
justify-content: center;
margin-right: 8px;
& > img,
span {
height: ${({ size }) => (size ? size + 'px' : '32px')};
width: ${({ size }) => (size ? size + 'px' : '32px')};
}
${({ theme }) => theme.mediaWidth.upToMedium`
align-items: flex-end;
`};
`
function WrappedStatusIcon({ connector }: { connector: Connector }) {
return (
<IconWrapper size={16}>
<StatusIcon connector={connector} />
</IconWrapper>
)
}
const TransactionListWrapper = styled.div`
${({ theme }) => theme.flexColumnNoWrap};
`
@ -228,6 +205,8 @@ export default function AccountDetails({
openOptions,
}: AccountDetailsProps) {
const { chainId, account, connector } = useWeb3React()
const connectionType = getConnection(connector).type
const theme = useContext(ThemeContext)
const dispatch = useAppDispatch()
@ -241,7 +220,8 @@ export default function AccountDetails({
const name = Object.keys(SUPPORTED_WALLETS)
.filter(
(k) =>
SUPPORTED_WALLETS[k].connector === connector && (connector !== injected || isMetaMask === (k === 'METAMASK'))
SUPPORTED_WALLETS[k].connector === connector &&
(connector !== injectedConnection.connector || isMetaMask === (k === 'METAMASK'))
)
.map((k) => SUPPORTED_WALLETS[k].name)[0]
return (
@ -281,7 +261,7 @@ export default function AccountDetails({
// Coinbase Wallet SDK does not emit a disconnect event to the provider,
// which is what web3-react uses to reset state. As a workaround we manually
// reset state.
if (connector === coinbaseWallet) {
if (connector === coinbaseWalletConnection.connector) {
connector.resetState()
}
} else {
@ -308,21 +288,10 @@ export default function AccountDetails({
</AccountGroupingRow>
<AccountGroupingRow data-testid="web3-account-identifier-row">
<AccountControl>
{ENSName ? (
<>
<div>
{connector && <WrappedStatusIcon connector={connector} />}
<p> {ENSName}</p>
</div>
</>
) : (
<>
<div>
{connector && <WrappedStatusIcon connector={connector} />}
<p> {account && shortenAddress(account)}</p>
</div>
</>
)}
<div>
<StatusIcon connectionType={connectionType} />
<p>{ENSName ? ENSName : account && shortenAddress(account)}</p>
</div>
</AccountControl>
</AccountGroupingRow>
<AccountGroupingRow>

@ -12,7 +12,7 @@ import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg'
import useTheme from '../../hooks/useTheme'
import { useCurrencyBalance } from '../../state/wallet/hooks'
import { useCurrencyBalance } from '../../state/connection/hooks'
import { ThemedText } from '../../theme'
import { ButtonGray } from '../Button'
import CurrencyLogo from '../CurrencyLogo'

@ -1,6 +1,6 @@
import { Trans } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core'
import { getWalletForConnector } from 'connectors'
import { getConnection } from 'connection/utils'
import { CHAIN_INFO } from 'constants/chainInfo'
import { CHAIN_IDS_TO_NAMES, SupportedChainId } from 'constants/chains'
import { useOnClickOutside } from 'hooks/useOnClickOutside'
@ -12,8 +12,8 @@ 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 { updateConnectionError } from 'state/connection/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'
@ -285,15 +285,15 @@ export default function NetworkSelector() {
async (targetChain: number, skipToggle?: boolean) => {
if (!connector) return
const wallet = getWalletForConnector(connector)
const connectionType = getConnection(connector).type
try {
dispatch(updateWalletError({ wallet, error: undefined }))
dispatch(updateConnectionError({ connectionType, error: undefined }))
await switchChain(connector, targetChain)
} catch (error) {
console.error('Failed to switch networks', error)
dispatch(updateWalletError({ wallet, error: error.message }))
dispatch(updateConnectionError({ connectionType, error: error.message }))
dispatch(addPopup({ content: { failedSwitchNetwork: targetChain }, key: `failed-network-switch` }))
}

@ -9,9 +9,9 @@ import { NavLink } from 'react-router-dom'
import { Text } from 'rebass'
import { useShowClaimPopup, useToggleSelfClaimModal } from 'state/application/hooks'
import { useUserHasAvailableClaim } from 'state/claim/hooks'
import { useNativeCurrencyBalances } from 'state/connection/hooks'
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'

@ -1,22 +1,42 @@
import { Connector } from '@web3-react/types'
import { ConnectionType } from 'connection'
import styled from 'styled-components/macro'
import CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg'
import FortmaticIcon from '../../assets/images/fortmaticIcon.png'
import WalletConnectIcon from '../../assets/images/walletConnectIcon.svg'
import { coinbaseWallet, fortmatic, injected, walletConnect } from '../../connectors'
import Identicon from '../Identicon'
export default function StatusIcon({ connector }: { connector: Connector }) {
switch (connector) {
case injected:
return <Identicon />
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" />
default:
return null
const IconWrapper = styled.div<{ size?: number }>`
${({ theme }) => theme.flexColumnNoWrap};
align-items: center;
justify-content: center;
margin-right: 8px;
& > img,
span {
height: ${({ size }) => (size ? size + 'px' : '32px')};
width: ${({ size }) => (size ? size + 'px' : '32px')};
}
${({ theme }) => theme.mediaWidth.upToMedium`
align-items: flex-end;
`};
`
export default function StatusIcon({ connectionType }: { connectionType: ConnectionType }) {
let image
switch (connectionType) {
case ConnectionType.INJECTED:
image = <Identicon />
break
case ConnectionType.WALLET_CONNECT:
image = <img src={WalletConnectIcon} alt="WalletConnect" />
break
case ConnectionType.COINBASE_WALLET:
image = <img src={CoinbaseWalletIcon} alt="Coinbase Wallet" />
break
case ConnectionType.FORTMATIC:
image = <img src={FortmaticIcon} alt="Fortmatic" />
break
}
return <IconWrapper size={16}>{image}</IconWrapper>
}

@ -13,7 +13,7 @@ import styled from 'styled-components/macro'
import { BIG_INT_ZERO } from '../../constants/misc'
import { useColor } from '../../hooks/useColor'
import { useTotalSupply } from '../../hooks/useTotalSupply'
import { useTokenBalance } from '../../state/wallet/hooks'
import { useTokenBalance } from '../../state/connection/hooks'
import { currencyId } from '../../utils/currencyId'
import { unwrappedToken } from '../../utils/unwrappedToken'
import { ButtonEmpty, ButtonPrimary, ButtonSecondary } from '../Button'

@ -13,7 +13,7 @@ import styled from 'styled-components/macro'
import { BIG_INT_ZERO } from '../../constants/misc'
import { useColor } from '../../hooks/useColor'
import { useTotalSupply } from '../../hooks/useTotalSupply'
import { useTokenBalance } from '../../state/wallet/hooks'
import { useTokenBalance } from '../../state/connection/hooks'
import { ExternalLink, ThemedText } from '../../theme'
import { currencyId } from '../../utils/currencyId'
import { unwrappedToken } from '../../utils/unwrappedToken'

@ -33,7 +33,7 @@ jest.mock('@web3-react/core', () => {
}
})
jest.mock('../../../state/wallet/hooks', () => {
jest.mock('../../../state/connection/hooks', () => {
return {
useCurrencyBalance: (currency: Currency) => {
return mockCurrencyAmt[(currency as mockToken)?.address]

@ -11,9 +11,9 @@ import styled from 'styled-components/macro'
import TokenListLogo from '../../../assets/svg/tokenlist.svg'
import { useIsUserAddedToken } from '../../../hooks/Tokens'
import { useCurrencyBalance } from '../../../state/connection/hooks'
import { useCombinedActiveList } from '../../../state/lists/hooks'
import { WrappedTokenInfo } from '../../../state/lists/wrappedTokenInfo'
import { useCurrencyBalance } from '../../../state/wallet/hooks'
import { ThemedText } from '../../../theme'
import { isTokenOnList } from '../../../utils'
import Column from '../../Column'

@ -15,7 +15,7 @@ import { Edit } from 'react-feather'
import AutoSizer from 'react-virtualized-auto-sizer'
import { FixedSizeList } from 'react-window'
import { Text } from 'rebass'
import { useAllTokenBalances } from 'state/wallet/hooks'
import { useAllTokenBalances } from 'state/connection/hooks'
import styled from 'styled-components/macro'
import { useAllTokens, useIsUserAddedToken, useSearchInactiveTokenLists, useToken } from '../../hooks/Tokens'

@ -4,16 +4,17 @@ import { Connector } from '@web3-react/types'
import { sendEvent } from 'components/analytics'
import { AutoColumn } from 'components/Column'
import { AutoRow } from 'components/Row'
import { ConnectionType, injectedConnection } from 'connection'
import { getConnection } from 'connection/utils'
import { useCallback, useEffect, useState } from 'react'
import { ArrowLeft } from 'react-feather'
import { updateConnectionError } from 'state/connection/reducer'
import { useAppDispatch, useAppSelector } from 'state/hooks'
import { updateSelectedWallet } from 'state/user/reducer'
import { updateWalletError } from 'state/wallet/reducer'
import styled from 'styled-components/macro'
import MetamaskIcon from '../../assets/images/metamask.png'
import { ReactComponent as Close } from '../../assets/images/x.svg'
import { fortmatic, getWalletForConnector, injected } from '../../connectors'
import { SUPPORTED_WALLETS } from '../../constants/wallet'
import { useModalOpen, useWalletModalToggle } from '../../state/application/hooks'
import { ApplicationModal } from '../../state/application/reducer'
@ -125,7 +126,7 @@ export default function WalletModal({
const [pendingConnector, setPendingConnector] = useState<Connector | undefined>()
const pendingError = useAppSelector((state) =>
pendingConnector ? state.wallet.errorByWallet[getWalletForConnector(pendingConnector)] : undefined
pendingConnector ? state.connection.errorByConnectionType[getConnection(pendingConnector).type] : undefined
)
const walletModalOpen = useModalOpen(ApplicationModal.WALLET)
@ -143,39 +144,39 @@ export default function WalletModal({
useEffect(() => {
if (pendingConnector && walletView !== WALLET_VIEWS.PENDING) {
updateWalletError({ wallet: getWalletForConnector(pendingConnector), error: undefined })
updateConnectionError({ connectionType: getConnection(pendingConnector).type, error: undefined })
setPendingConnector(undefined)
}
}, [pendingConnector, walletView])
const tryActivation = useCallback(
async (connector: Connector) => {
const wallet = getWalletForConnector(connector)
const connectionType = getConnection(connector).type
// log selected wallet
sendEvent({
category: 'Wallet',
action: 'Change Wallet',
label: wallet,
label: connectionType,
})
try {
// Fortmatic opens it's own modal on activation to log in. This modal has a tabIndex
// collision into the WalletModal, so we special case by closing the modal.
if (connector === fortmatic) {
if (connectionType === ConnectionType.FORTMATIC) {
toggleWalletModal()
}
setPendingConnector(connector)
setWalletView(WALLET_VIEWS.PENDING)
dispatch(updateWalletError({ wallet, error: undefined }))
dispatch(updateConnectionError({ connectionType, error: undefined }))
await connector.activate()
dispatch(updateSelectedWallet({ wallet }))
dispatch(updateSelectedWallet({ wallet: connectionType }))
} catch (error) {
console.debug(`web3-react connection error: ${error}`)
dispatch(updateWalletError({ wallet, error: error.message }))
dispatch(updateConnectionError({ connectionType, error: error.message }))
}
},
[dispatch, toggleWalletModal]
@ -221,7 +222,7 @@ export default function WalletModal({
}
// overwrite injected when needed
if (option.connector === injected) {
if (option.connector === injectedConnection.connector) {
// don't show injected if there's no injected provider
if (!(window.web3 || window.ethereum)) {
if (option.name === 'MetaMask') {

@ -1,44 +1,11 @@
import { Web3ReactProvider } from '@web3-react/core'
import { Connector } from '@web3-react/types'
import { BACKFILLABLE_WALLETS, getConnectorForWallet, gnosisSafe, injected, network, useConnectors } from 'connectors'
import { ReactNode, useEffect } from 'react'
import { useAppSelector } from 'state/hooks'
import { isMobile } from '../../utils/userAgent'
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}`)
}
}
import useConnectors from 'hooks/useConnectors'
import useEagerlyConnect from 'hooks/useEagerlyConnect'
import { ReactNode } from 'react'
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)
const isMetaMask = !!window.ethereum?.isMetaMask
useEffect(() => {
connect(gnosisSafe)
connect(network)
if (isMobile && isMetaMask) {
injected.activate()
} else 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
useEagerlyConnect()
const connectors = useConnectors()
return <Web3ReactProvider connectors={connectors}>{children}</Web3ReactProvider>
}

@ -1,8 +1,7 @@
// 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 { getConnection } from 'connection/utils'
import { darken } from 'polished'
import { useMemo } from 'react'
import { Activity } from 'react-feather'
@ -21,16 +20,6 @@ import Loader from '../Loader'
import { RowBetween } from '../Row'
import WalletModal from '../WalletModal'
const IconWrapper = styled.div<{ size?: number }>`
${({ theme }) => theme.flexColumnNoWrap};
align-items: center;
justify-content: center;
& > * {
height: ${({ size }) => (size ? size + 'px' : '32px')};
width: ${({ size }) => (size ? size + 'px' : '32px')};
}
`
const Web3StatusGeneric = styled(ButtonSecondary)`
${({ theme }) => theme.flexRowNoWrap}
width: 100%;
@ -131,18 +120,11 @@ function Sock() {
)
}
function WrappedStatusIcon({ connector }: { connector: Connector }) {
return (
<IconWrapper size={16}>
<StatusIcon connector={connector} />
</IconWrapper>
)
}
function Web3StatusInner() {
const { account, connector, chainId, ENSName } = useWeb3React()
const connectionType = getConnection(connector).type
const error = useAppSelector((state) => state.wallet.errorByWallet[getWalletForConnector(connector)])
const error = useAppSelector((state) => state.connection.errorByConnectionType[getConnection(connector).type])
const chainAllowed = chainId && isChainAllowed(connector, chainId)
@ -199,7 +181,7 @@ function Web3StatusInner() {
<Text>{ENSName || shortenAddress(account)}</Text>
</>
)}
{!hasPendingTransactions && connector && <WrappedStatusIcon connector={connector} />}
{!hasPendingTransactions && <StatusIcon connectionType={connectionType} />}
</Web3StatusConnected>
)
} else {

@ -8,8 +8,8 @@ import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
import { UNI } from '../../constants/tokens'
import useENS from '../../hooks/useENS'
import { useTokenBalance } from '../../state/connection/hooks'
import { useDelegateCallback } from '../../state/governance/hooks'
import { useTokenBalance } from '../../state/wallet/hooks'
import { ThemedText } from '../../theme'
import AddressInputPanel from '../AddressInputPanel'
import { ButtonPrimary } from '../Button'

100
src/connection/index.ts Normal file

@ -0,0 +1,100 @@
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 Fortmatic from 'fortmatic'
import UNISWAP_LOGO_URL from '../assets/svg/logo.svg'
export enum ConnectionType {
INJECTED = 'INJECTED',
COINBASE_WALLET = 'COINBASE_WALLET',
WALLET_CONNECT = 'WALLET_CONNECT',
FORTMATIC = 'FORTMATIC',
NETWORK = 'NETWORK',
GNOSIS_SAFE = 'GNOSIS_SAFE',
}
export interface Connection {
connector: Connector
hooks: Web3ReactHooks
type: ConnectionType
}
function onError(error: Error) {
console.debug(`web3-react error: ${error}`)
}
const [web3Network, web3NetworkHooks] = initializeConnector<Network>(
(actions) => new Network({ actions, urlMap: INFURA_NETWORK_URLS, defaultChainId: 1 })
)
export const networkConnection: Connection = {
connector: web3Network,
hooks: web3NetworkHooks,
type: ConnectionType.NETWORK,
}
const [web3Injected, web3InjectedHooks] = initializeConnector<MetaMask>((actions) => new MetaMask({ actions, onError }))
export const injectedConnection: Connection = {
connector: web3Injected,
hooks: web3InjectedHooks,
type: ConnectionType.INJECTED,
}
const [web3GnosisSafe, web3GnosisSafeHooks] = initializeConnector<GnosisSafe>((actions) => new GnosisSafe({ actions }))
export const gnosisSafeConnection: Connection = {
connector: web3GnosisSafe,
hooks: web3GnosisSafeHooks,
type: ConnectionType.GNOSIS_SAFE,
}
const [web3WalletConnect, web3WalletConnectHooks] = initializeConnector<WalletConnect>(
(actions) =>
new WalletConnect({
actions,
options: {
rpc: INFURA_NETWORK_URLS,
qrcode: true,
},
onError,
})
)
export const walletConnectConnection: Connection = {
connector: web3WalletConnect,
hooks: web3WalletConnectHooks,
type: ConnectionType.WALLET_CONNECT,
}
const [web3Fortmatic, web3FortmaticHooks] = initializeConnector<EIP1193>(
(actions) => new EIP1193({ actions, provider: new Fortmatic(process.env.REACT_APP_FORTMATIC_KEY).getProvider() })
)
export const fortmaticConnection: Connection = {
connector: web3Fortmatic,
hooks: web3FortmaticHooks,
type: ConnectionType.FORTMATIC,
}
const [web3CoinbaseWallet, web3CoinbaseWalletHooks] = initializeConnector<CoinbaseWallet>(
(actions) =>
new CoinbaseWallet({
actions,
options: {
url: INFURA_NETWORK_URLS[SupportedChainId.MAINNET],
appName: 'Uniswap',
appLogoUrl: UNISWAP_LOGO_URL,
reloadOnDisconnect: false,
},
onError,
})
)
export const coinbaseWalletConnection: Connection = {
connector: web3CoinbaseWallet,
hooks: web3CoinbaseWalletHooks,
type: ConnectionType.COINBASE_WALLET,
}

44
src/connection/utils.ts Normal file

@ -0,0 +1,44 @@
import { Connector } from '@web3-react/types'
import {
coinbaseWalletConnection,
ConnectionType,
fortmaticConnection,
gnosisSafeConnection,
injectedConnection,
networkConnection,
walletConnectConnection,
} from 'connection'
const CONNECTIONS = [
coinbaseWalletConnection,
fortmaticConnection,
injectedConnection,
networkConnection,
walletConnectConnection,
gnosisSafeConnection,
]
export function getConnection(c: Connector | ConnectionType) {
if (c instanceof Connector) {
const connection = CONNECTIONS.find((connection) => connection.connector === c)
if (!connection) {
throw Error('unsupported connector')
}
return connection
} else {
switch (c) {
case ConnectionType.INJECTED:
return injectedConnection
case ConnectionType.COINBASE_WALLET:
return coinbaseWalletConnection
case ConnectionType.WALLET_CONNECT:
return walletConnectConnection
case ConnectionType.FORTMATIC:
return fortmaticConnection
case ConnectionType.NETWORK:
return networkConnection
case ConnectionType.GNOSIS_SAFE:
return gnosisSafeConnection
}
}
}

@ -1,151 +0,0 @@
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 Fortmatic from 'fortmatic'
import { useMemo } from 'react'
import UNISWAP_LOGO_URL from '../assets/svg/logo.svg'
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 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
}
}
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,
reloadOnDisconnect: false,
},
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])
}

@ -1,15 +1,21 @@
import { Connector } from '@web3-react/types'
import {
coinbaseWalletConnection,
ConnectionType,
fortmaticConnection,
injectedConnection,
walletConnectConnection,
} from 'connection'
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 { coinbaseWallet, fortmatic, injected, Wallet, walletConnect } from '../connectors'
interface WalletInfo {
connector?: Connector
wallet?: Wallet
connectionType?: ConnectionType
name: string
iconURL: string
description: string
@ -22,8 +28,8 @@ interface WalletInfo {
export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = {
INJECTED: {
connector: injected,
wallet: Wallet.INJECTED,
connector: injectedConnection.connector,
connectionType: ConnectionType.INJECTED,
name: 'Injected',
iconURL: INJECTED_ICON_URL,
description: 'Injected web3 provider.',
@ -32,8 +38,8 @@ export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = {
primary: true,
},
METAMASK: {
connector: injected,
wallet: Wallet.INJECTED,
connector: injectedConnection.connector,
connectionType: ConnectionType.INJECTED,
name: 'MetaMask',
iconURL: METAMASK_ICON_URL,
description: 'Easy-to-use browser extension.',
@ -41,8 +47,8 @@ export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = {
color: '#E8831D',
},
WALLET_CONNECT: {
connector: walletConnect,
wallet: Wallet.WALLET_CONNECT,
connector: walletConnectConnection.connector,
connectionType: ConnectionType.WALLET_CONNECT,
name: 'WalletConnect',
iconURL: WALLETCONNECT_ICON_URL,
description: 'Connect to Trust Wallet, Rainbow Wallet and more...',
@ -51,8 +57,8 @@ export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = {
mobile: true,
},
COINBASE_WALLET: {
connector: coinbaseWallet,
wallet: Wallet.COINBASE_WALLET,
connector: coinbaseWalletConnection.connector,
connectionType: ConnectionType.COINBASE_WALLET,
name: 'Coinbase Wallet',
iconURL: COINBASE_ICON_URL,
description: 'Use Coinbase Wallet app on mobile device',
@ -69,8 +75,8 @@ export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = {
mobileOnly: true,
},
FORTMATIC: {
connector: fortmatic,
wallet: Wallet.FORTMATIC,
connector: fortmaticConnection.connector,
connectionType: ConnectionType.FORTMATIC,
name: 'Fortmatic',
iconURL: FORTMATIC_ICON_URL,
description: 'Login using Fortmatic hosted wallet',

@ -0,0 +1,34 @@
import { Web3ReactHooks } from '@web3-react/core'
import { Connector } from '@web3-react/types'
import { ConnectionType } from 'connection'
import { getConnection } from 'connection/utils'
import { useMemo } from 'react'
import { BACKFILLABLE_WALLETS } from 'state/connection/constants'
import { useAppSelector } from 'state/hooks'
const SELECTABLE_WALLETS = [...BACKFILLABLE_WALLETS, ConnectionType.FORTMATIC]
export default function useConnectors() {
const selectedWallet = useAppSelector((state) => state.user.selectedWallet)
return useMemo(() => {
const orderedConnectionTypes: ConnectionType[] = []
// Always attempt to use to Gnosis Safe first, as we can't know if we're in a SafeContext.
orderedConnectionTypes.push(ConnectionType.GNOSIS_SAFE)
// Add the `selectedWallet` to the top so it's prioritized, then add the other selectable wallets.
if (selectedWallet) {
orderedConnectionTypes.push(selectedWallet)
}
orderedConnectionTypes.push(...SELECTABLE_WALLETS.filter((wallet) => wallet !== selectedWallet))
// Add network connection last as it should be the fallback.
orderedConnectionTypes.push(ConnectionType.NETWORK)
// Convert to web3-react's representation of connectors.
const web3Connectors: [Connector, Web3ReactHooks][] = orderedConnectionTypes
.map(getConnection)
.map(({ connector, hooks }) => [connector, hooks])
return web3Connectors
}, [selectedWallet])
}

@ -0,0 +1,42 @@
import { Connector } from '@web3-react/types'
import { gnosisSafeConnection, injectedConnection, networkConnection } from 'connection'
import { getConnection } from 'connection/utils'
import { useEffect } from 'react'
import { BACKFILLABLE_WALLETS } from 'state/connection/constants'
import { useAppSelector } from 'state/hooks'
import { isMobile } from 'utils/userAgent'
async function connect(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 useEagerlyConnect() {
const selectedWalletBackfilled = useAppSelector((state) => state.user.selectedWalletBackfilled)
const selectedWallet = useAppSelector((state) => state.user.selectedWallet)
const isMetaMask = !!window.ethereum?.isMetaMask
useEffect(() => {
connect(gnosisSafeConnection.connector)
connect(networkConnection.connector)
if (isMobile && isMetaMask) {
injectedConnection.connector.activate()
} else if (selectedWallet) {
connect(getConnection(selectedWallet).connector)
} else if (!selectedWalletBackfilled) {
BACKFILLABLE_WALLETS.map(getConnection)
.map((connection) => connection.connector)
.forEach(connect)
}
// The dependency list is empty so this is only run once on mount
}, []) // eslint-disable-line react-hooks/exhaustive-deps
}

@ -3,7 +3,7 @@ import { useWeb3React } from '@web3-react/core'
import { SOCKS_CONTROLLER_ADDRESSES } from 'constants/addresses'
import { SupportedChainId } from 'constants/chains'
import { useMemo } from 'react'
import { useTokenBalance } from 'state/wallet/hooks'
import { useTokenBalance } from 'state/connection/hooks'
// technically a 721, not an ERC20, but suffices for our purposes
const SOCKS = new Token(SupportedChainId.MAINNET, SOCKS_CONTROLLER_ADDRESSES[SupportedChainId.MAINNET], 0)

@ -6,9 +6,9 @@ import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
import { useMemo } from 'react'
import { WRAPPED_NATIVE_CURRENCY } from '../constants/tokens'
import { useCurrencyBalance } from '../state/connection/hooks'
import { useTransactionAdder } from '../state/transactions/hooks'
import { TransactionType } from '../state/transactions/types'
import { useCurrencyBalance } from '../state/wallet/hooks'
import { useWETHContract } from './useContract'
export enum WrapType {

@ -24,8 +24,8 @@ import { useTotalSupply } from '../../hooks/useTotalSupply'
import useUSDCPrice from '../../hooks/useUSDCPrice'
import { useV2Pair } from '../../hooks/useV2Pairs'
import { useWalletModalToggle } from '../../state/application/hooks'
import { useTokenBalance } from '../../state/connection/hooks'
import { useStakingInfo } from '../../state/stake/hooks'
import { useTokenBalance } from '../../state/wallet/hooks'
import { ThemedText } from '../../theme'
import { currencyId } from '../../utils/currencyId'

@ -44,8 +44,8 @@ import { useToken } from '../../hooks/Tokens'
import { usePairContract, useV2MigratorContract } from '../../hooks/useContract'
import useIsArgentWallet from '../../hooks/useIsArgentWallet'
import { useTotalSupply } from '../../hooks/useTotalSupply'
import { useTokenBalance } from '../../state/connection/hooks'
import { TransactionType } from '../../state/transactions/types'
import { useTokenBalance } from '../../state/wallet/hooks'
import { BackArrow, ExternalLink, ThemedText } from '../../theme'
import { isAddress } from '../../utils'
import { calculateGasMargin } from '../../utils/calculateGasMargin'

@ -18,8 +18,8 @@ import QuestionHelper from '../../components/QuestionHelper'
import { AutoRow } from '../../components/Row'
import { Dots } from '../../components/swap/styleds'
import { V2_FACTORY_ADDRESSES } from '../../constants/addresses'
import { useTokenBalancesWithLoadingIndicator } from '../../state/connection/hooks'
import { toV2LiquidityToken, useTrackedTokenPairs } from '../../state/user/hooks'
import { useTokenBalancesWithLoadingIndicator } from '../../state/wallet/hooks'
import { BackArrow, StyledInternalLink, ThemedText } from '../../theme'
import { BodyWrapper } from '../AppBody'

@ -20,9 +20,9 @@ import { Dots } from '../../components/swap/styleds'
import { SwitchLocaleLink } from '../../components/SwitchLocaleLink'
import { BIG_INT_ZERO } from '../../constants/misc'
import { useV2Pairs } from '../../hooks/useV2Pairs'
import { useTokenBalancesWithLoadingIndicator } from '../../state/connection/hooks'
import { useStakingInfo } from '../../state/stake/hooks'
import { toV2LiquidityToken, useTrackedTokenPairs } from '../../state/user/hooks'
import { useTokenBalancesWithLoadingIndicator } from '../../state/wallet/hooks'
import { ExternalLink, HideSmall, ThemedText } from '../../theme'
const PageWrapper = styled(AutoColumn)`

@ -19,8 +19,8 @@ import CurrencySearchModal from '../../components/SearchModal/CurrencySearchModa
import { SwitchLocaleLink } from '../../components/SwitchLocaleLink'
import { nativeOnChain } from '../../constants/tokens'
import { PairState, useV2Pair } from '../../hooks/useV2Pairs'
import { useTokenBalance } from '../../state/connection/hooks'
import { usePairAdder } from '../../state/user/hooks'
import { useTokenBalance } from '../../state/wallet/hooks'
import { StyledInternalLink } from '../../theme'
import { ThemedText } from '../../theme'
import { currencyId } from '../../utils/currencyId'

@ -18,9 +18,9 @@ import { Link } from 'react-router-dom'
import { Button } from 'rebass/styled-components'
import { useModalOpen, useToggleDelegateModal } from 'state/application/hooks'
import { ApplicationModal } from 'state/application/reducer'
import { useTokenBalance } from 'state/connection/hooks'
import { ProposalData, ProposalState } from 'state/governance/hooks'
import { useAllProposalData, useUserDelegatee, useUserVotes } from 'state/governance/hooks'
import { useTokenBalance } from 'state/wallet/hooks'
import styled from 'styled-components/macro'
import { ExternalLink, ThemedText } from 'theme'
import { shortenAddress } from 'utils'

@ -38,6 +38,7 @@ import {
useToggleVoteModal,
} from '../../state/application/hooks'
import { ApplicationModal } from '../../state/application/reducer'
import { useTokenBalance } from '../../state/connection/hooks'
import {
ProposalData,
ProposalState,
@ -47,7 +48,6 @@ import {
useUserVotesAsOfBlock,
} from '../../state/governance/hooks'
import { VoteOption } from '../../state/governance/types'
import { useTokenBalance } from '../../state/wallet/hooks'
import { ExternalLink, StyledInternalLink, ThemedText } from '../../theme'
import { isAddress } from '../../utils'
import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink'

@ -9,8 +9,8 @@ import { useAppDispatch, useAppSelector } from 'state/hooks'
import { useTotalSupply } from '../../hooks/useTotalSupply'
import { useV2Pair } from '../../hooks/useV2Pairs'
import { useTokenBalances } from '../connection/hooks'
import { AppState } from '../index'
import { useTokenBalances } from '../wallet/hooks'
import { Field, typeInput } from './actions'
export function useBurnState(): AppState['burn'] {

@ -0,0 +1,7 @@
import { ConnectionType } from 'connection'
export const BACKFILLABLE_WALLETS = [
ConnectionType.COINBASE_WALLET,
ConnectionType.WALLET_CONNECT,
ConnectionType.INJECTED,
]

@ -0,0 +1,33 @@
import { createSlice } from '@reduxjs/toolkit'
import { ConnectionType } from 'connection'
export interface ConnectionState {
errorByConnectionType: Record<ConnectionType, string | undefined>
}
export const initialState: ConnectionState = {
errorByConnectionType: {
[ConnectionType.INJECTED]: undefined,
[ConnectionType.FORTMATIC]: undefined,
[ConnectionType.WALLET_CONNECT]: undefined,
[ConnectionType.COINBASE_WALLET]: undefined,
[ConnectionType.NETWORK]: undefined,
[ConnectionType.GNOSIS_SAFE]: undefined,
},
}
const connectionSlice = createSlice({
name: 'connection',
initialState,
reducers: {
updateConnectionError(
state,
{ payload: { connectionType, error } }: { payload: { connectionType: ConnectionType; error: string | undefined } }
) {
state.errorByConnectionType[connectionType] = error
},
},
})
export const { updateConnectionError } = connectionSlice.actions
export default connectionSlice.reducer

@ -6,6 +6,7 @@ import { load, save } from 'redux-localstorage-simple'
import application from './application/reducer'
import burn from './burn/reducer'
import burnV3 from './burn/v3/reducer'
import connection from './connection/reducer'
import { api as dataApi } from './data/slice'
import { updateVersion } from './global/actions'
import lists from './lists/reducer'
@ -16,7 +17,6 @@ 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']
@ -24,7 +24,7 @@ const store = configureStore({
reducer: {
application,
user,
wallet,
connection,
transactions,
swap,
mint,

@ -9,8 +9,8 @@ import { useAppDispatch, useAppSelector } from 'state/hooks'
import { useTotalSupply } from '../../hooks/useTotalSupply'
import { PairState, useV2Pair } from '../../hooks/useV2Pairs'
import { useCurrencyBalances } from '../connection/hooks'
import { AppState } from '../index'
import { useCurrencyBalances } from '../wallet/hooks'
import { Field, typeInput } from './actions'
const ZERO = JSBI.BigInt(0)

@ -23,8 +23,8 @@ import { replaceURLParam } from 'utils/routes'
import { BIG_INT_ZERO } from '../../../constants/misc'
import { PoolState } from '../../../hooks/usePools'
import { useCurrencyBalances } from '../../connection/hooks'
import { AppState } from '../../index'
import { useCurrencyBalances } from '../../wallet/hooks'
import {
Bound,
Field,

@ -15,8 +15,8 @@ import { useCurrency } from '../../hooks/Tokens'
import useENS from '../../hooks/useENS'
import useParsedQueryString from '../../hooks/useParsedQueryString'
import { isAddress } from '../../utils'
import { useCurrencyBalances } from '../connection/hooks'
import { AppState } from '../index'
import { useCurrencyBalances } from '../wallet/hooks'
import { Field, replaceSwapState, selectCurrency, setRecipient, switchCurrencies, typeInput } from './actions'
import { SwapState } from './reducer'

@ -1,5 +1,5 @@
import { createSlice } from '@reduxjs/toolkit'
import { Wallet } from 'connectors'
import { ConnectionType } from 'connection'
import { SupportedLocale } from 'constants/locales'
import { DEFAULT_DEADLINE_FROM_NOW } from '../../constants/misc'
@ -14,7 +14,7 @@ export interface UserState {
// 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
selectedWallet?: ConnectionType
// the timestamp of the last updateVersion action
lastUpdateVersionTimestamp?: number

@ -1,33 +0,0 @@
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

@ -1,5 +1,12 @@
import { Connector } from '@web3-react/types'
import { coinbaseWallet, fortmatic, gnosisSafe, injected, network, walletConnect } from 'connectors'
import {
coinbaseWalletConnection,
fortmaticConnection,
gnosisSafeConnection,
injectedConnection,
networkConnection,
walletConnectConnection,
} from 'connection'
import { CHAIN_INFO } from 'constants/chainInfo'
import { ALL_SUPPORTED_CHAIN_IDS, SupportedChainId } from 'constants/chains'
import { INFURA_NETWORK_URLS } from 'constants/infura'
@ -32,13 +39,13 @@ function getRpcUrls(chainId: SupportedChainId): [string] {
export function isChainAllowed(connector: Connector, chainId: number) {
switch (connector) {
case fortmatic:
case fortmaticConnection.connector:
return chainId === SupportedChainId.MAINNET
case injected:
case coinbaseWallet:
case walletConnect:
case network:
case gnosisSafe:
case injectedConnection.connector:
case coinbaseWalletConnection.connector:
case walletConnectConnection.connector:
case networkConnection.connector:
case gnosisSafeConnection.connector:
return ALL_SUPPORTED_CHAIN_IDS.includes(chainId)
default:
return false
@ -48,7 +55,7 @@ export function isChainAllowed(connector: Connector, chainId: number) {
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) {
} else if (connector === walletConnectConnection.connector || connector === networkConnection.connector) {
await connector.activate(chainId)
} else {
const info = CHAIN_INFO[chainId]