Commit 015c9f85 authored by Ian Lapham's avatar Ian Lapham Committed by Noah Zinsmeister

Hosted wallets (#528)

* formnatic setup

* stable with new hosted

* stable version, fortmatic and portis

* fix safari bug

* cleanup

* add white version of arrow

* update options to tabbable buttons

* update injected version

* style tweaks

* style tweaks, remove portis connector
parent a11dc4cd
......@@ -7,9 +7,11 @@ import Transaction from './Transaction'
import { SUPPORTED_WALLETS } from '../../constants'
import { ReactComponent as Close } from '../../assets/images/x.svg'
import { getEtherscanLink } from '../../utils'
import { injected, walletconnect, walletlink } from '../../connectors'
import { injected, walletconnect, walletlink, fortmatic, portis } from '../../connectors'
import CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg'
import WalletConnectIcon from '../../assets/images/walletConnectIcon.svg'
import FortmaticIcon from '../../assets/images/fortmaticIcon.png'
import PortisIcon from '../../assets/images/portisIcon.png'
import Identicon from '../Identicon'
import { Link } from '../../theme'
......@@ -115,14 +117,16 @@ const GreenCircle = styled.div`
width: 8px;
margin-left: 12px;
margin-right: 2px;
margin-top: -6px;
background-color: ${({ theme }) => theme.connectedGreen};
border-radius: 50%;
}
`
const GreenText = styled.div`
const CircleWrapper = styled.div`
color: ${({ theme }) => theme.connectedGreen};
display: flex;
justify-content: center;
align-items: center;
`
const LowerSection = styled.div`
......@@ -208,6 +212,20 @@ const TransactionListWrapper = styled.div`
${({ theme }) => theme.flexColumnNoWrap};
`
const WalletAction = styled.div`
color: ${({ theme }) => theme.chaliceGray};
margin-left: 16px;
font-weight: 400;
:hover {
cursor: pointer;
text-decoration: underline;
}
`
const MainWalletAction = styled(WalletAction)`
color: ${({ theme }) => theme.royalBlue};
`
function renderTransactions(transactions, pending) {
return (
<TransactionListWrapper>
......@@ -228,14 +246,13 @@ export default function AccountDetails({
const { chainId, account, connector } = useWeb3React()
function formatConnectorName() {
const isMetaMask = window.ethereum && window.ethereum.isMetaMask
const isMetaMask = window.ethereum && window.ethereum.isMetaMask ? true : false
const name = Object.keys(SUPPORTED_WALLETS)
.filter(
k =>
SUPPORTED_WALLETS[k].connector === connector && (connector !== injected || isMetaMask === (k === 'METAMASK'))
)
.map(k => SUPPORTED_WALLETS[k].name)[0]
return <WalletName>{name}</WalletName>
}
......@@ -258,6 +275,27 @@ export default function AccountDetails({
<img src={CoinbaseWalletIcon} alt={''} /> {formatConnectorName()}
</IconWrapper>
)
} else if (connector === fortmatic) {
return (
<IconWrapper size={16}>
<img src={FortmaticIcon} alt={''} /> {formatConnectorName()}
</IconWrapper>
)
} else if (connector === portis) {
return (
<>
<IconWrapper size={16}>
<img src={PortisIcon} alt={''} /> {formatConnectorName()}
<MainWalletAction
onClick={() => {
portis.portis.showPortis()
}}
>
Show Portis
</MainWalletAction>
</IconWrapper>
</>
)
}
}
......@@ -273,11 +311,22 @@ export default function AccountDetails({
<InfoCard>
<AccountGroupingRow>
{getStatusIcon()}
<GreenText>
<GreenCircle>
<div />
</GreenCircle>
</GreenText>
<div>
{connector !== injected && connector !== walletlink && (
<WalletAction
onClick={() => {
connector.close()
}}
>
Disconnect
</WalletAction>
)}
<CircleWrapper>
<GreenCircle>
<div />
</GreenCircle>
</CircleWrapper>
</div>
</AccountGroupingRow>
<AccountGroupingRow>
{ENSName ? (
......@@ -298,7 +347,8 @@ export default function AccountDetails({
</AccountGroupingRow>
</InfoCard>
</YourAccount>
{!isMobile && (
{!(isMobile && (window.web3 || window.ethereum)) && (
<ConnectButtonRow>
<OptionButton
onClick={() => {
......
......@@ -3,42 +3,67 @@ import styled from 'styled-components'
import { transparentize } from 'polished'
import { Link } from '../../theme'
const InfoCard = styled.div`
background-color: ${({ theme }) => theme.backgroundColor};
const InfoCard = styled.button`
background-color: ${({ theme, active }) => (active ? theme.activeGray : theme.backgroundColor)};
padding: 1rem;
border: 1px solid ${({ theme }) => theme.placeholderGray};
border-radius: 20px;
box-shadow: 0 4px 8px 0 ${({ theme }) => transparentize(0.95, theme.shadowColor)};
outline: none;
border: 1px solid;
border-radius: 12px;
width: 100% !important;
: 0 4px 8px 0 ${({ theme, clickable }) => (clickable ? transparentize(0.95, theme.shadowColor) : 'none')};
&:focus {
box-shadow: 0 0 0 1px ${({ theme }) => theme.royalBlue};
}
border-color: ${({ theme, active }) => (active ? 'transparent' : theme.placeholderGray)};
`
const OptionCard = styled(InfoCard)`
display: grid;
grid-template-columns: 1fr 48px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
margin-top: 2rem;
padding: 1rem;
${({ theme }) => theme.mediaWidth.upToMedium`
height: 40px;
grid-template-columns: 1fr 40px;
padding: 0.6rem 1rem;
`};
`
const OptionCardLeft = styled.div`
${({ theme }) => theme.flexColumnNoWrap};
height: 100%;
justify-content: center;
height: 100%;
`
const OptionCardClickable = styled(OptionCard)`
margin-top: 0;
margin-bottom: 1rem;
&:hover {
cursor: pointer;
border: 1px solid ${({ theme }) => theme.malibuBlue};
cursor: ${({ clickable }) => (clickable ? 'pointer' : '')};
border: ${({ clickable, theme }) => (clickable ? `1px solid ${theme.malibuBlue}` : ``)};
}
opacity: ${({ disabled }) => (disabled ? '0.5' : '1')};
`
const GreenCircle = styled.div`
${({ theme }) => theme.flexRowNoWrap}
justify-content: center;
align-items: center;
&:first-child {
height: 8px;
width: 8px;
margin-right: 8px;
background-color: ${({ theme }) => theme.connectedGreen};
border-radius: 50%;
}
`
const CircleWrapper = styled.div`
color: ${({ theme }) => theme.connectedGreen};
display: flex;
justify-content: center;
align-items: center;
`
const HeaderText = styled.div`
${({ theme }) => theme.flexRowNoWrap};
color: ${props => (props.color === 'blue' ? ({ theme }) => theme.royalBlue : props.color)};
font-size: 1rem;
font-weight: 500;
......@@ -62,22 +87,44 @@ const IconWrapper = styled.div`
justify-content: center;
& > img,
span {
height: ${({ size }) => (size ? size + 'px' : '32px')};
width: ${({ size }) => (size ? size + 'px' : '32px')};
height: ${({ size }) => (size ? size + 'px' : '24px')};
width: ${({ size }) => (size ? size + 'px' : '24px')};
}
${({ theme }) => theme.mediaWidth.upToMedium`
align-items: flex-end;
`};
`
export default function Option({ link = null, size = null, onClick = null, color, header, subheader = null, icon }) {
export default function Option({
link = null,
clickable = true,
size = null,
onClick = null,
color,
header,
subheader = null,
icon,
active = false
}) {
const content = (
<OptionCardClickable onClick={onClick}>
<OptionCardClickable onClick={onClick} clickable={clickable && !active} active={active}>
<OptionCardLeft>
<HeaderText color={color}>{header}</HeaderText>
<HeaderText color={color}>
{' '}
{active ? (
<CircleWrapper>
<GreenCircle>
<div />
</GreenCircle>
</CircleWrapper>
) : (
''
)}
{header}
</HeaderText>
{subheader && <SubHeader>{subheader}</SubHeader>}
</OptionCardLeft>
<IconWrapper size={size}>
<IconWrapper size={size} active={active}>
<img src={icon} alt={'Icon'} />
</IconWrapper>
</OptionCardClickable>
......
import React from 'react'
import styled from 'styled-components'
import Option from './Option'
import { SUPPORTED_WALLETS } from '../../constants'
import WalletConnectData from './WalletConnectData'
import { walletconnect, injected } from '../../connectors'
import { Spinner } from '../../theme'
import Circle from '../../assets/images/circle.svg'
import { darken } from 'polished'
const PendingSection = styled.div`
${({ theme }) => theme.flexColumnNoWrap};
align-items: center;
justify-content: center;
width: 100%;
& > * {
width: 100%;
}
`
const SpinnerWrapper = styled(Spinner)`
font-size: 4rem;
margin-right: 1rem;
svg {
path {
color: ${({ theme }) => theme.placeholderGray};
}
}
`
const LoadingMessage = styled.div`
${({ theme }) => theme.flexRowNoWrap};
align-items: center;
justify-content: flex-start;
border-radius: 12px;
margin-bottom: 20px;
color: ${({ theme, error }) => (error ? theme.salmonRed : 'inherit')};
border: 1px solid ${({ theme, error }) => (error ? theme.salmonRed : theme.placeholderGray)};
& > * {
padding: 1rem;
}
`
const ErrorGroup = styled.div`
${({ theme }) => theme.flexRowNoWrap};
align-items: center;
justify-content: flex-start;
`
const ErrorButton = styled.div`
border-radius: 8px;
font-size: 12px;
color: ${({ theme }) => theme.textColor};
background-color: ${({ theme }) => theme.placeholderGray};
margin-left: 1rem;
padding: 0.5rem;
font-weight: 600;
user-select: none;
&:hover {
cursor: pointer;
background-color: ${({ theme }) => darken(0.1, theme.placeholderGray)};
}
`
export default function PendingView({ uri = '', size, connector, error = false, tryActivation }) {
const isMetamask = window.ethereum && window.ethereum.isMetaMask
return (
<PendingSection>
<LoadingMessage error={error}>
<div>
{!error && <SpinnerWrapper src={Circle} />}
{error ? (
<ErrorGroup>
<div>Error connecting.</div>
<ErrorButton
onClick={() => {
tryActivation(connector)
}}
>
Try Again
</ErrorButton>
</ErrorGroup>
) : connector === walletconnect ? (
'Scan QR code with a compatible wallet...'
) : (
'Waiting for connection...'
)}
</div>
</LoadingMessage>
{!error && connector === walletconnect && <WalletConnectData size={size} uri={uri} />}
{Object.keys(SUPPORTED_WALLETS).map(key => {
const option = SUPPORTED_WALLETS[key]
if (option.connector === connector) {
if (option.connector === injected && isMetamask && option.name !== 'MetaMask') {
return null
} else {
return (
<Option
key={key}
clickable={false}
color={option.color}
header={option.name}
subheader={option.description}
icon={require('../../assets/images/' + option.iconName)}
/>
)
}
}
return true
})}
</PendingSection>
)
}
import React from 'react'
import styled from 'styled-components'
import QRCode from 'qrcode.react'
import Option from './Option'
import { useDarkModeManager } from '../../contexts/LocalStorage'
const QRSection = styled.div`
${({ theme }) => theme.flexColumnNoWrap};
align-items: center;
justify-content: center;
h5 {
padding-bottom: 1rem;
}
`
const QRCodeWrapper = styled.div`
${({ theme }) => theme.flexColumnNoWrap};
align-items: center;
justify-content: center;
width: 280px;
height: 280px;
border-radius: 20px;
border-radius: 12px;
margin-bottom: 20px;
border: 1px solid ${({ theme }) => theme.placeholderGray};
`
export default function QrCode({ uri, size, header, subheader, icon }) {
export default function WalletConnectData({ uri = '', size }) {
const [isDark] = useDarkModeManager()
return (
<QRSection>
<h5>Scan QR code with a compatible wallet</h5>
<QRCodeWrapper>
{uri && (
<QRCode size={size} value={uri} bgColor={isDark ? '#333639' : 'white'} fgColor={isDark ? 'white' : 'black'} />
)}
</QRCodeWrapper>
<Option color={'#4196FC'} header={header} subheader={subheader} icon={icon} />
</QRSection>
<QRCodeWrapper>
{uri && (
<QRCode size={size} value={uri} bgColor={isDark ? '#333639' : 'white'} fgColor={isDark ? 'white' : 'black'} />
)}
</QRCodeWrapper>
)
}
......@@ -6,16 +6,16 @@ import { URI_AVAILABLE } from '@web3-react/walletconnect-connector'
import Modal from '../Modal'
import AccountDetails from '../AccountDetails'
import QrCode from './QrCode'
import PendingView from './PendingView'
import Option from './Option'
import { SUPPORTED_WALLETS, MOBILE_DEEP_LINKS } from '../../constants'
import { SUPPORTED_WALLETS } from '../../constants'
import { usePrevious } from '../../hooks'
import { Link } from '../../theme'
import WalletConnectIcon from '../../assets/images/walletConnectIcon.svg'
import MetamaskIcon from '../../assets/images/metamask.png'
import { ReactComponent as Close } from '../../assets/images/x.svg'
import { injected, walletconnect } from '../../connectors'
import { injected, walletconnect, fortmatic } from '../../connectors'
import { useWalletModalToggle, useWalletModalOpen } from '../../contexts/Application'
import { OVERLAY_READY } from '../../connectors/Fortmatic'
const CloseIcon = styled.div`
position: absolute;
......@@ -107,8 +107,9 @@ const HoverText = styled.div`
const WALLET_VIEWS = {
OPTIONS: 'options',
OPTIONS_SECONDARY: 'options_secondary',
ACCOUNT: 'account',
WALLET_CONNECT: 'walletConnect'
PENDING: 'pending'
}
export default function WalletModal({ pendingTransactions, confirmedTransactions, ENSName }) {
......@@ -116,12 +117,17 @@ export default function WalletModal({ pendingTransactions, confirmedTransactions
const [walletView, setWalletView] = useState(WALLET_VIEWS.ACCOUNT)
const [pendingWallet, setPendingWallet] = useState()
const [pendingError, setPendingError] = useState()
const walletModalOpen = useWalletModalOpen()
const toggleWalletModal = useWalletModalToggle()
// always reset to account view
useEffect(() => {
if (walletModalOpen) {
setPendingError(false)
setWalletView(WALLET_VIEWS.ACCOUNT)
}
}, [walletModalOpen])
......@@ -131,7 +137,7 @@ export default function WalletModal({ pendingTransactions, confirmedTransactions
useEffect(() => {
const activateWC = uri => {
setUri(uri)
setWalletView(WALLET_VIEWS.WALLET_CONNECT)
// setWalletView(WALLET_VIEWS.PENDING)
}
walletconnect.on(URI_AVAILABLE, activateWC)
return () => {
......@@ -148,75 +154,95 @@ export default function WalletModal({ pendingTransactions, confirmedTransactions
}
}, [setWalletView, active, error, connector, walletModalOpen, activePrevious, connectorPrevious])
const tryActivation = async connector => {
setPendingWallet(connector) // set wallet for pending view
setWalletView(WALLET_VIEWS.PENDING)
activate(connector, undefined, true).catch(e => {
setPendingError(true)
})
}
// close wallet modal if fortmatic modal is active
useEffect(() => {
fortmatic.on(OVERLAY_READY, () => {
toggleWalletModal()
})
}, [toggleWalletModal])
// get wallets user can switch too, depending on device/browser
function getOptions() {
const isMetamask = window.ethereum && window.ethereum.isMetaMask
if (isMobile && !window.web3 && !window.ethereum) {
return Object.keys(MOBILE_DEEP_LINKS).map(key => {
const option = MOBILE_DEEP_LINKS[key]
return (
<Option
key={key}
color={option.color}
header={option.name}
link={option.href}
subheader={option.description}
icon={require('../../assets/images/' + option.iconName)}
/>
)
})
} else {
return Object.keys(SUPPORTED_WALLETS).map(key => {
const option = SUPPORTED_WALLETS[key]
// don't show the option we're currently connected to
if (option.connector === connector) {
return null
return Object.keys(SUPPORTED_WALLETS).map(key => {
const option = SUPPORTED_WALLETS[key]
// check for mobile options
if (isMobile) {
if (!window.web3 && !window.ethereum && option.mobile) {
return (
<Option
onClick={() => {
option.connector !== connector && !option.href && tryActivation(option.connector)
}}
key={key}
active={option.connector && option.connector === connector}
color={option.color}
link={option.href}
header={option.name}
subheader={null}
icon={require('../../assets/images/' + option.iconName)}
/>
)
}
return null
}
if (option.connector === injected) {
// don't show injected if there's no injected provider
if (!(window.web3 || window.ethereum)) {
if (option.name === 'MetaMask') {
return (
<Option
key={key}
color={'#E8831D'}
header={'Install Metamask'}
subheader={'Easy to use browser extension.'}
link={'https://metamask.io/'}
icon={MetamaskIcon}
/>
)
} else {
return null //dont want to return install twice
}
}
// don't return metamask if injected provider isn't metamask
else if (option.name === 'MetaMask' && !isMetamask) {
return null
}
// likewise for generic
else if (option.name === 'Injected' && isMetamask) {
return null
// overwrite injected when needed
if (option.connector === injected) {
// don't show injected if there's no injected provider
if (!(window.web3 || window.ethereum)) {
if (option.name === 'MetaMask') {
return (
<Option
key={key}
color={'#E8831D'}
header={'Install Metamask'}
subheader={null}
link={'https://metamask.io/'}
icon={MetamaskIcon}
/>
)
} else {
return null //dont want to return install twice
}
}
// don't return metamask if injected provider isn't metamask
else if (option.name === 'MetaMask' && !isMetamask) {
return null
}
// likewise for generic
else if (option.name === 'Injected' && isMetamask) {
return null
}
}
return (
// return rest of options
return (
!isMobile &&
!option.mobileOnly && (
<Option
onClick={() => {
activate(option.connector)
option.connector !== connector && !option.href && tryActivation(option.connector)
}}
key={key}
active={option.connector === connector}
color={option.color}
link={option.href}
header={option.name}
subheader={walletView === WALLET_VIEWS.OPTIONS ? null : option.description}
subheader={null} //use option.descriptio to bring back multi-line
icon={require('../../assets/images/' + option.iconName)}
/>
)
})
}
)
})
}
function getModalContent() {
......@@ -254,13 +280,15 @@ export default function WalletModal({ pendingTransactions, confirmedTransactions
<CloseColor alt={'close icon'} />
</CloseIcon>
{walletView !== WALLET_VIEWS.ACCOUNT ? (
<HeaderRow
color="blue"
onClick={() => {
setWalletView(WALLET_VIEWS.ACCOUNT)
}}
>
<HoverText>Back</HoverText>
<HeaderRow color="blue">
<HoverText
onClick={() => {
setPendingError(false)
setWalletView(WALLET_VIEWS.ACCOUNT)
}}
>
Back
</HoverText>
</HeaderRow>
) : (
<HeaderRow>
......@@ -268,25 +296,25 @@ export default function WalletModal({ pendingTransactions, confirmedTransactions
</HeaderRow>
)}
<ContentWrapper>
{walletView === WALLET_VIEWS.WALLET_CONNECT ? (
<QrCode
{walletView === WALLET_VIEWS.PENDING ? (
<PendingView
uri={uri}
header={'Connect with Wallet Connect'}
subheader={'Open protocol supported by major mobile wallets'}
size={220}
icon={WalletConnectIcon}
connector={pendingWallet}
error={pendingError}
tryActivation={tryActivation}
/>
) : !account ? (
getOptions()
) : (
<OptionGrid>{getOptions()}</OptionGrid>
)}
<Blurb>
<span>New to Ethereum? &nbsp;</span>{' '}
<Link href="https://ethereum.org/use/#3-what-is-a-wallet-and-which-one-should-i-use">
Learn more about wallets
</Link>
</Blurb>
{walletView !== WALLET_VIEWS.PENDING && (
<Blurb>
<span>New to Ethereum? &nbsp;</span>{' '}
<Link href="https://ethereum.org/use/#3-what-is-a-wallet-and-which-one-should-i-use">
Learn more about wallets
</Link>
</Blurb>
)}
</ContentWrapper>
</UpperSection>
)
......
......@@ -12,9 +12,11 @@ import { useAllTransactions } from '../../contexts/Transactions'
import { useWalletModalToggle } from '../../contexts/Application'
import { Spinner } from '../../theme'
import Circle from '../../assets/images/circle.svg'
import { injected, walletconnect, walletlink } from '../../connectors'
import { injected, walletconnect, walletlink, fortmatic, portis } from '../../connectors'
import WalletConnectIcon from '../../assets/images/walletConnectIcon.svg'
import CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg'
import FortmaticIcon from '../../assets/images/fortmaticIcon.png'
import PortisIcon from '../../assets/images/portisIcon.png'
import { NetworkContextName } from '../../constants'
import Identicon from '../Identicon'
......@@ -147,6 +149,18 @@ export default function Web3Status() {
<img src={CoinbaseWalletIcon} alt={''} />
</IconWrapper>
)
} else if (connector === fortmatic) {
return (
<IconWrapper size={16}>
<img src={FortmaticIcon} alt={''} />
</IconWrapper>
)
} else if (connector === portis) {
return (
<IconWrapper size={16}>
<img src={PortisIcon} alt={''} />
</IconWrapper>
)
}
}
......
import { FortmaticConnector as FortmaticConnectorCore } from '@web3-react/fortmatic-connector'
export const OVERLAY_READY = 'OVERLAY_READY'
const chainIdToNetwork = {
1: 'mainnet',
3: 'ropsten',
4: 'rinkeby',
42: 'kovan'
}
export class FortmaticConnector extends FortmaticConnectorCore {
async activate() {
if (!this.fortmatic) {
const { default: Fortmatic } = await import('fortmatic')
this.fortmatic = new Fortmatic(
this.apiKey,
this.chainId === 1 || this.chainId === 4 ? undefined : chainIdToNetwork[this.chainId]
)
}
const provider = this.fortmatic.getProvider()
const pollForOverlayReady = new Promise(resolve => {
const interval = setInterval(() => {
if (provider.overlayReady) {
clearInterval(interval)
this.emit(OVERLAY_READY)
resolve()
}
}, 200)
})
const [account] = await Promise.all([provider.enable().then(accounts => accounts[0]), pollForOverlayReady])
return { provider: this.fortmatic.getProvider(), chainId: this.chainId, account }
}
}
import {
InjectedConnector as InjectedConnectorCore,
NoEthereumProviderError,
UserRejectedRequestError
} from '@web3-react/injected-connector'
export class InjectedConnector extends InjectedConnectorCore {
async activate() {
if (!window.ethereum) {
throw new NoEthereumProviderError()
}
if (window.ethereum.on) {
window.ethereum.on('connect', this.handleConnect)
window.ethereum.on('chainChanged', this.handleChainChanged)
window.ethereum.on('networkChanged', this.handleNetworkChanged)
window.ethereum.on('accountsChanged', this.handleAccountsChanged)
window.ethereum.on('close', this.handleClose)
}
// provides support for most dapp browsers
let account = undefined
if (window.ethereum.isMetaMask) {
window.ethereum.autoRefreshOnNetworkChange = false
account = await window.ethereum
.send('eth_requestAccounts')
.then(({ result: accounts }) => accounts[0])
.catch(error => {
if (error && error.code === 4001) {
throw new UserRejectedRequestError()
} else {
throw error
}
})
} else {
account = await window.ethereum
.enable()
.then(accounts => accounts[0])
.catch(error => {
if (error && error.code === 4001) {
throw new UserRejectedRequestError()
} else {
throw error
}
})
}
return { provider: window.ethereum, account }
}
async getChainId() {
if (!window.ethereum) {
throw new NoEthereumProviderError()
}
if (window.ethereum.isMetaMask) {
return window.ethereum.send('eth_chainId').then(({ result: chainId }) => chainId)
} else {
return window.ethereum.networkVersion ? window.ethereum.networkVersion : 1
}
}
async getAccount() {
if (!window.ethereum) {
throw new NoEthereumProviderError()
}
if (window.ethereum.isMetaMask) {
return window.ethereum.send('eth_accounts').then(({ result: accounts }) => accounts[0])
} else {
return window.ethereum.enable().then(accounts => accounts[0])
}
}
deactivate() {
if (window.ethereum && window.ethereum.removeListener) {
window.ethereum.removeListener('connect', this.handleConnect)
window.ethereum.removeListener('chainChanged', this.handleChainChanged)
window.ethereum.removeListener('networkChanged', this.handleNetworkChanged)
window.ethereum.removeListener('accountsChanged', this.handleAccountsChanged)
window.ethereum.removeListener('close', this.handleClose)
}
}
async isAuthorized() {
if (window.ethereum) {
if (window.ethereum.isMetaMask) {
return window.ethereum
.send('eth_accounts')
.then(({ result: accounts }) => {
if (accounts.length > 0) {
return true
}
return false
})
.catch(() => {
return false
})
} else {
return window.ethereum
.enable()
.then(accounts => {
if (accounts.length > 0) {
return true
}
return false
})
.catch(() => {
return false
})
}
}
return false
}
}
import { InjectedConnector } from '@web3-react/injected-connector'
import { WalletConnectConnector } from '@web3-react/walletconnect-connector'
import { WalletLinkConnector } from '@web3-react/walletlink-connector'
import { PortisConnector } from '@web3-react/portis-connector'
import { InjectedConnector } from './Injected'
import { NetworkConnector } from './Network'
import { FortmaticConnector } from './Fortmatic'
const POLLING_INTERVAL = 10000
export const injected = new InjectedConnector({
supportedChainIds: [1]
})
export const network = new NetworkConnector({
urls: { 1: process.env.REACT_APP_NETWORK_URL },
pollingInterval: POLLING_INTERVAL
})
export const injected = new InjectedConnector({
supportedChainIds: [1]
})
export const walletconnect = new WalletConnectConnector({
rpc: { 1: process.env.REACT_APP_NETWORK_URL },
bridge: 'https://bridge.walletconnect.org',
......@@ -22,6 +24,16 @@ export const walletconnect = new WalletConnectConnector({
pollingInterval: POLLING_INTERVAL
})
export const fortmatic = new FortmaticConnector({
apiKey: process.env.REACT_APP_FORTMATIC_KEY,
chainId: 1
})
export const portis = new PortisConnector({
dAppId: process.env.REACT_APP_PORTIS_ID,
networks: [1]
})
export const walletlink = new WalletLinkConnector({
url: process.env.REACT_APP_NETWORK_URL,
appName: 'Uniswap',
......
import { injected, walletconnect, walletlink } from '../connectors'
import { injected, walletconnect, walletlink, fortmatic, portis } from '../connectors'
export const FACTORY_ADDRESSES = {
1: '0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95',
......@@ -15,52 +15,72 @@ export const SUPPORTED_THEMES = {
export const SUPPORTED_WALLETS = {
INJECTED: {
connector: injected,
id: 'Injected',
name: 'Injected',
iconName: 'arrow-right.svg',
description: 'Injected web3 provider.',
color: '#010101'
href: null,
color: '#010101',
primary: true
},
METAMASK: {
connector: injected,
id: 'MetaMask',
name: 'MetaMask',
iconName: 'metamask.png',
description: 'Easy-to-use browser extension.',
href: null,
color: '#E8831D'
},
WALLET_CONNECT: {
connector: walletconnect,
id: 'WalletConnect',
name: 'Wallet Connect',
iconName: 'walletConnectIcon.svg',
description: 'Connect to Trust Wallet, Rainbow Wallet and more...',
href: null,
color: '#4196FC'
},
WALLET_LINK: {
connector: walletlink,
id: 'WalletLink',
name: 'Coinbase Wallet',
iconName: 'coinbaseWalletIcon.svg',
description: 'Use Coinbase Wallet app on mobile device',
href: null,
color: '#315CF5'
}
}
export const MOBILE_DEEP_LINKS = {
},
COINBASE_LINK: {
name: 'Open in Coinbase Wallet',
iconName: 'coinbaseWalletIcon.svg',
description: 'Open in Coinbase Wallet app.',
href: 'https://go.cb-w.com/mtUDhEZPy1',
color: '#315CF5'
color: '#315CF5',
mobile: true,
mobileOnly: true
},
TRUST_WALLET_LINK: {
name: 'Open in Trust Wallet',
iconName: 'trustWallet.png',
description: 'iOS and Android app.',
href: 'https://link.trustwallet.com/open_url?coin_id=60&url=https://uniswap.exchange/swap',
color: '#1C74CC'
color: '#1C74CC',
mobile: true,
mobileOnly: true
},
FORTMATIC: {
connector: fortmatic,
name: 'Fortmatic',
iconName: 'fortmaticIcon.png',
description: 'Login using Fortmatic hosted wallet',
href: null,
color: '#6748FF',
mobile: true
},
Portis: {
connector: portis,
name: 'Portis',
iconName: 'portisIcon.png',
description: 'Login using Portis hosted wallet',
href: null,
color: '#4A6C9B',
mobile: true
}
}
......
import { useState, useMemo, useCallback, useEffect, useRef } from 'react'
import { useWeb3React as useWeb3ReactCore } from '@web3-react/core'
import copy from 'copy-to-clipboard'
import { isMobile } from 'react-device-detect'
import { NetworkContextName } from '../constants'
import ERC20_ABI from '../constants/abis/erc20'
......@@ -26,7 +27,13 @@ export function useEagerConnect() {
setTried(true)
})
} else {
setTried(true)
if (isMobile && window.ethereum) {
activate(injected, undefined, true).catch(() => {
setTried(true)
})
} else {
setTried(true)
}
}
})
}, [activate]) // intentionally only running on mount (make sure it's only mounted once :))
......@@ -52,7 +59,7 @@ export function useInactiveListener(suppress = false) {
const { ethereum } = window
if (ethereum && ethereum.on && !active && !error && !suppress) {
const handleNetworkChanged = () => {
const handleChainChanged = () => {
// eat errors
activate(injected, undefined, true).catch(() => {})
}
......@@ -64,12 +71,21 @@ export function useInactiveListener(suppress = false) {
}
}
const handleNetworkChanged = () => {
// eat errors
activate(injected, undefined, true).catch(() => {})
}
ethereum.on('chainChanged', handleChainChanged)
ethereum.on('networkChanged', handleNetworkChanged)
ethereum.on('accountsChanged', handleAccountsChanged)
return () => {
ethereum.removeListener('networkChanged', handleNetworkChanged)
ethereum.removeListener('accountsChanged', handleAccountsChanged)
if (ethereum.removeListener) {
ethereum.removeListener('chainChanged', handleChainChanged)
ethereum.removeListener('networkChanged', handleNetworkChanged)
ethereum.removeListener('accountsChanged', handleAccountsChanged)
}
}
}
......
......@@ -61,6 +61,7 @@ const theme = darkMode => ({
chaliceGray: darkMode ? '#7B7B7B' : '#AEAEAE',
doveGray: darkMode ? '#C4C4C4' : '#737373',
mineshaftGray: darkMode ? '#E1E1E1' : '#2B2B2B',
activeGray: darkMode ? '#292C2F' : '#F7F8FA',
buttonOutlineGrey: darkMode ? '#FAFAFA' : '#F2F2F2',
tokenRowHover: darkMode ? '#404040' : '#F2F2F2',
......
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