Wallet Support with web3-react v6 (#514)

* basic support for desktop and mobile, with or without web3

* stable before mobile view update

* mobile modal views

* remove unused modules

* create global context for wallet modal, update to click button to connect

* first pass

* drag with react pose

* try without pose

* replace context with new syntax, basic setup

* stable version on all browser types

* remove dev flags

* fix swap broken

* update to clickable connect button if logged out

* stable, good entry

* fix bugs, exit animations

* prep for merge

* stable version with updated application context

* update animations with correct gesture package

* refactor wallet logic to multi-root

* small fixes

* Style Updates

* remove unused imports

* refactor wallet page
This commit is contained in:
Ian Lapham 2019-11-25 17:56:31 -05:00 committed by Noah Zinsmeister
parent 116a7f3833
commit 795caac4fd
42 changed files with 3599 additions and 2407 deletions

@ -8,15 +8,21 @@
"@reach/dialog": "^0.2.8",
"@reach/tooltip": "^0.2.0",
"@uniswap/sdk": "^1.0.0-beta.4",
"@web3-react/core": "^6.0.0-beta.15",
"@web3-react/injected-connector": "^6.0.0-beta.17",
"@web3-react/network-connector": "^6.0.0-beta.15",
"@web3-react/walletconnect-connector": "^6.0.0-beta.18",
"@web3-react/walletlink-connector": "^6.0.0-beta.15",
"copy-to-clipboard": "^3.2.0",
"escape-string-regexp": "^2.0.0",
"history": "^4.9.0",
"ethers": "^4.0.36",
"history": "^4.9.0",
"i18next": "^15.0.9",
"i18next-browser-languagedetector": "^3.0.1",
"i18next-xhr-backend": "^2.0.1",
"jazzicon": "^1.5.0",
"polished": "^3.3.2",
"qrcode.react": "^0.9.3",
"react": "^16.8.6",
"react-device-detect": "^1.6.2",
"react-dom": "^16.8.6",
@ -25,10 +31,10 @@
"react-i18next": "^10.7.0",
"react-router-dom": "^5.0.0",
"react-scripts": "^3.0.1",
"react-spring": "^8.0.20",
"react-spring": "^8.0.27",
"react-switch": "^5.0.1",
"styled-components": "^4.2.0",
"web3-react": "5.0.5"
"react-use-gesture": "^6.0.14",
"styled-components": "^4.2.0"
},
"scripts": {
"start": "react-scripts start",

@ -1,100 +0,0 @@
import { Connectors } from 'web3-react'
const { Connector, ErrorCodeMixin } = Connectors
const InjectedConnectorErrorCodes = ['ETHEREUM_ACCESS_DENIED', 'NO_WEB3', 'UNLOCK_REQUIRED']
export default class InjectedConnector extends ErrorCodeMixin(Connector, InjectedConnectorErrorCodes) {
constructor(args = {}) {
super(args)
this.runOnDeactivation = []
this.networkChangedHandler = this.networkChangedHandler.bind(this)
this.accountsChangedHandler = this.accountsChangedHandler.bind(this)
const { ethereum } = window
if (ethereum && ethereum.isMetaMask) {
ethereum.autoRefreshOnNetworkChange = false
}
}
async onActivation() {
const { ethereum, web3 } = window
if (ethereum) {
await ethereum.enable().catch(error => {
const deniedAccessError = Error(error)
deniedAccessError.code = InjectedConnector.errorCodes.ETHEREUM_ACCESS_DENIED
throw deniedAccessError
})
// initialize event listeners
if (ethereum.on) {
ethereum.on('networkChanged', this.networkChangedHandler)
ethereum.on('accountsChanged', this.accountsChangedHandler)
this.runOnDeactivation.push(() => {
if (ethereum.removeListener) {
ethereum.removeListener('networkChanged', this.networkChangedHandler)
ethereum.removeListener('accountsChanged', this.accountsChangedHandler)
}
})
}
} else if (web3) {
console.warn('Your web3 provider is outdated, please upgrade to a modern provider.')
} else {
const noWeb3Error = Error('Your browser is not equipped with web3 capabilities.')
noWeb3Error.code = InjectedConnector.errorCodes.NO_WEB3
throw noWeb3Error
}
}
async getProvider() {
const { ethereum, web3 } = window
return ethereum || web3.currentProvider
}
async getAccount(provider) {
const account = await super.getAccount(provider)
if (account === null) {
const unlockRequiredError = Error('Ethereum account locked.')
unlockRequiredError.code = InjectedConnector.errorCodes.UNLOCK_REQUIRED
throw unlockRequiredError
}
return account
}
onDeactivation() {
this.runOnDeactivation.forEach(runner => runner())
this.runOnDeactivation = []
}
// event handlers
networkChangedHandler(networkId) {
const networkIdNumber = Number(networkId)
try {
super._validateNetworkId(networkIdNumber)
super._web3ReactUpdateHandler({
updateNetworkId: true,
networkId: networkIdNumber
})
} catch (error) {
super._web3ReactErrorHandler(error)
}
}
accountsChangedHandler(accounts) {
if (!accounts[0]) {
const unlockRequiredError = Error('Ethereum account locked.')
unlockRequiredError.code = InjectedConnector.errorCodes.UNLOCK_REQUIRED
super._web3ReactErrorHandler(unlockRequiredError)
} else {
super._web3ReactUpdateHandler({
updateAccount: true,
account: accounts[0]
})
}
}
}

@ -1,51 +0,0 @@
import { ethers } from 'ethers'
import { Connectors } from 'web3-react'
const { Connector } = Connectors
function getFallbackProvider(providerURL) {
if (Number(process.env.REACT_APP_NETWORK_ID) === 1) {
const etherscan = new ethers.providers.EtherscanProvider()
const infura = new ethers.providers.JsonRpcProvider(providerURL)
const providers = [infura, etherscan]
return new ethers.providers.FallbackProvider(providers)
} else {
const infura = new ethers.providers.JsonRpcProvider(providerURL)
const providers = [infura]
return new ethers.providers.FallbackProvider(providers)
}
}
export default class NetworkOnlyConnector extends Connector {
constructor(kwargs) {
const { providerURL, ...rest } = kwargs || {}
super(rest)
this.providerURL = providerURL
}
async onActivation() {
if (!this.engine) {
const provider = getFallbackProvider(this.providerURL)
provider.polling = false
provider.pollingInterval = 300000 // 5 minutes
this.engine = provider
}
}
async getNetworkId(provider) {
const networkId = await provider.getNetwork().then(network => network.chainId)
return super._validateNetworkId(networkId)
}
async getProvider() {
return this.engine
}
async getAccount() {
return null
}
}

@ -0,0 +1,3 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.79261 16.1108L17.5398 8.36364L9.79261 0.616477L8.25852 2.15057L13.3807 7.25568H0V9.47159H13.3807L8.25852 14.5852L9.79261 16.1108Z" fill="#333639"/>
</svg>

After

Width:  |  Height:  |  Size: 263 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>

Before

Width:  |  Height:  |  Size: 299 B

After

Width:  |  Height:  |  Size: 299 B

@ -1,12 +1,12 @@
import React from 'react'
import styled, { keyframes } from 'styled-components'
import { useWeb3Context } from 'web3-react'
import Copy from './Copy'
import { Check } from 'react-feather'
import { useWeb3React } from '../../hooks'
import { getEtherscanLink } from '../../utils'
import { Link, Spinner } from '../../theme'
import Copy from './Copy'
import Circle from '../../assets/images/circle.svg'
import { Check } from 'react-feather'
import { transparentize } from 'polished'
@ -75,17 +75,17 @@ const ButtonWrapper = styled.div`
`
export default function Transaction({ hash, pending }) {
const { networkId } = useWeb3Context()
const { chainId } = useWeb3React()
return (
<TransactionWrapper key={hash}>
<TransactionStatusWrapper>
<Link href={getEtherscanLink(networkId, hash, 'transaction')}>{hash} </Link>
<Link href={getEtherscanLink(chainId, hash, 'transaction')}>{hash} </Link>
<Copy toCopy={hash} />
</TransactionStatusWrapper>
{pending ? (
<ButtonWrapper pending={pending}>
<Link href={getEtherscanLink(networkId, hash, 'transaction')}>
<Link href={getEtherscanLink(chainId, hash, 'transaction')}>
<TransactionState pending={pending}>
<Spinner src={Circle} id="pending" />
<TransactionStatusText>Pending</TransactionStatusText>
@ -94,7 +94,7 @@ export default function Transaction({ hash, pending }) {
</ButtonWrapper>
) : (
<ButtonWrapper pending={pending}>
<Link href={getEtherscanLink(networkId, hash, 'transaction')}>
<Link href={getEtherscanLink(chainId, hash, 'transaction')}>
<TransactionState pending={pending}>
<Check size="16" />
<TransactionStatusText>Confirmed</TransactionStatusText>

@ -0,0 +1,327 @@
import React from 'react'
import styled from 'styled-components'
import { useWeb3React } from '@web3-react/core'
import { isMobile } from 'react-device-detect'
import Copy from './Copy'
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 CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg'
import WalletConnectIcon from '../../assets/images/walletConnectIcon.svg'
import Identicon from '../Identicon'
import { Link } from '../../theme'
const OptionButton = styled.div`
${({ theme }) => theme.flexColumnNoWrap}
justify-content: center;
align-items: center;
border-radius: 20px;
border: 1px solid ${({ theme }) => theme.royalBlue};
color: ${({ theme }) => theme.royalBlue};
padding: 8px 24px;
&:hover {
border: 1px solid ${({ theme }) => theme.malibuBlue};
cursor: pointer;
}
${({ theme }) => theme.mediaWidth.upToMedium`
font-size: 12px;
`};
`
const HeaderRow = styled.div`
${({ theme }) => theme.flexRowNoWrap};
padding: 1.5rem 1.5rem;
font-weight: 500;
color: ${props => (props.color === 'blue' ? ({ theme }) => theme.royalBlue : 'inherit')};
${({ theme }) => theme.mediaWidth.upToMedium`
padding: 1rem;
`};
`
const UpperSection = styled.div`
position: relative;
background-color: ${({ theme }) => theme.concreteGray};
h5 {
margin: 0;
margin-bottom: 0.5rem;
font-size: 1rem;
font-weight: 400;
}
h5:last-child {
margin-bottom: 0px;
}
h4 {
margin-top: 0;
font-weight: 500;
}
`
const InfoCard = styled.div`
padding: 1rem;
border: 1px solid ${({ theme }) => theme.placeholderGray};
border-radius: 20px;
`
const AccountGroupingRow = styled.div`
${({ theme }) => theme.flexRowNoWrap};
justify-content: space-between;
align-items: center;
font-weight: 500;
color: ${({ theme }) => theme.textColor};
div {
${({ theme }) => theme.flexRowNoWrap}
align-items: center;
}
&:first-of-type {
margin-bottom: 20px;
}
`
const AccountSection = styled.div`
background-color: ${({ theme }) => theme.concreteGray};
padding: 0rem 1.5rem;
${({ theme }) => theme.mediaWidth.upToMedium`padding: 0rem 1rem 1rem 1rem;`};
`
const YourAccount = styled.div`
h5 {
margin: 0 0 1rem 0;
font-weight: 400;
}
h4 {
margin: 0;
font-weight: 500;
}
`
const GreenCircle = styled.div`
${({ theme }) => theme.flexRowNoWrap}
justify-content: center;
align-items: center;
&:first-child {
height: 8px;
width: 8px;
margin-left: 12px;
margin-right: 2px;
margin-top: -6px;
background-color: ${({ theme }) => theme.connectedGreen};
border-radius: 50%;
}
`
const GreenText = styled.div`
color: ${({ theme }) => theme.connectedGreen};
`
const LowerSection = styled.div`
${({ theme }) => theme.flexColumnNoWrap}
padding: 2rem;
flex-grow: 1;
overflow: auto;
h5 {
margin: 0;
font-weight: 400;
color: ${({ theme }) => theme.doveGray};
}
`
const AccountControl = styled.div`
${({ theme }) => theme.flexRowNoWrap};
align-items: center;
min-width: 0;
font-weight: ${({ hasENS, isENS }) => (hasENS ? (isENS ? 500 : 400) : 500)};
font-size: ${({ hasENS, isENS }) => (hasENS ? (isENS ? '1rem' : '0.8rem') : '1rem')};
a:hover {
text-decoration: underline;
}
a {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
`
const ConnectButtonRow = styled.div`
${({ theme }) => theme.flexRowNoWrap}
align-items: center;
justify-content: center;
margin: 30px;
`
const StyledLink = styled(Link)`
color: ${({ hasENS, isENS, theme }) => (hasENS ? (isENS ? theme.royalBlue : theme.doveGray) : theme.royalBlue)};
`
const CloseIcon = styled.div`
position: absolute;
right: 1rem;
top: 14px;
&:hover {
cursor: pointer;
opacity: 0.6;
}
`
const CloseColor = styled(Close)`
path {
stroke: ${({ theme }) => theme.chaliceGray};
}
`
const WalletName = styled.div`
padding-left: 0.5rem;
width: initial;
`
const IconWrapper = styled.div`
${({ theme }) => theme.flexColumnNoWrap};
align-items: center;
justify-content: center;
& > img,
span {
height: ${({ size }) => (size ? size + 'px' : '32px')};
width: ${({ size }) => (size ? size + 'px' : '32px')};
}
${({ theme }) => theme.mediaWidth.upToMedium`
align-items: flex-end;
`};
`
const TransactionListWrapper = styled.div`
${({ theme }) => theme.flexColumnNoWrap};
`
function renderTransactions(transactions, pending) {
return (
<TransactionListWrapper>
{transactions.map((hash, i) => {
return <Transaction key={i} hash={hash} pending={pending} />
})}
</TransactionListWrapper>
)
}
export default function AccountDetails({
toggleWalletModal,
pendingTransactions,
confirmedTransactions,
ENSName,
openOptions
}) {
const { chainId, account, connector } = useWeb3React()
function formatConnectorName() {
const isMetaMask = window.ethereum && window.ethereum.isMetaMask
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>
}
function getStatusIcon() {
if (connector === injected) {
return (
<IconWrapper size={16}>
<Identicon /> {formatConnectorName()}
</IconWrapper>
)
} else if (connector === walletconnect) {
return (
<IconWrapper size={16}>
<img src={WalletConnectIcon} alt={''} /> {formatConnectorName()}
</IconWrapper>
)
} else if (connector === walletlink) {
return (
<IconWrapper size={16}>
<img src={CoinbaseWalletIcon} alt={''} /> {formatConnectorName()}
</IconWrapper>
)
}
}
return (
<>
<UpperSection>
<CloseIcon onClick={toggleWalletModal}>
<CloseColor alt={'close icon'} />
</CloseIcon>
<HeaderRow>Account</HeaderRow>
<AccountSection>
<YourAccount>
<InfoCard>
<AccountGroupingRow>
{getStatusIcon()}
<GreenText>
<GreenCircle>
<div />
</GreenCircle>
</GreenText>
</AccountGroupingRow>
<AccountGroupingRow>
{ENSName ? (
<AccountControl hasENS={!!ENSName} isENS={true}>
<StyledLink hasENS={!!ENSName} isENS={true} href={getEtherscanLink(chainId, ENSName, 'address')}>
{ENSName} {' '}
</StyledLink>
<Copy toCopy={ENSName} />
</AccountControl>
) : (
<AccountControl hasENS={!!ENSName} isENS={false}>
<StyledLink hasENS={!!ENSName} isENS={false} href={getEtherscanLink(chainId, account, 'address')}>
{account} {' '}
</StyledLink>
<Copy toCopy={account} />
</AccountControl>
)}
</AccountGroupingRow>
</InfoCard>
</YourAccount>
{!isMobile && (
<ConnectButtonRow>
<OptionButton
onClick={() => {
openOptions()
}}
>
Connect to a different wallet
</OptionButton>
</ConnectButtonRow>
)}
</AccountSection>
</UpperSection>
{!!pendingTransactions.length || !!confirmedTransactions.length ? (
<LowerSection>
<h5>Recent Transactions</h5>
{renderTransactions(pendingTransactions, true)}
{renderTransactions(confirmedTransactions, false)}
</LowerSection>
) : (
<LowerSection>
<h5>Your transactions will appear here...</h5>
</LowerSection>
)}
</>
)
}

@ -1,11 +1,10 @@
import React, { useState, useEffect } from 'react'
import styled from 'styled-components'
import { useTranslation } from 'react-i18next'
import { useWeb3Context } from 'web3-react'
import { transparentize } from 'polished'
import { isAddress } from '../../utils'
import { useDebounce } from '../../hooks'
import { useWeb3React, useDebounce } from '../../hooks'
const InputPanel = styled.div`
${({ theme }) => theme.flexColumnNoWrap}
@ -73,7 +72,7 @@ const Input = styled.input`
export default function AddressInputPanel({ title, initialInput = '', onChange = () => {}, onError = () => {} }) {
const { t } = useTranslation()
const { library } = useWeb3Context()
const { library } = useWeb3React()
const [input, setInput] = useState(initialInput.address ? initialInput.address : '')

@ -3,7 +3,6 @@ import { Link } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import { ethers } from 'ethers'
import { BigNumber } from '@uniswap/sdk'
import { useWeb3Context } from 'web3-react'
import styled from 'styled-components'
import escapeStringRegex from 'escape-string-regexp'
import { darken } from 'polished'
@ -12,7 +11,7 @@ import '@reach/tooltip/styles.css'
import { isMobile } from 'react-device-detect'
import { BorderlessInput } from '../../theme'
import { useTokenContract } from '../../hooks'
import { useWeb3React, useTokenContract } from '../../hooks'
import { isAddress, calculateGasMargin, formatToUsd, formatTokenBalance, formatEthBalance } from '../../utils'
import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg'
import Modal from '../Modal'
@ -225,7 +224,10 @@ const TokenModalRow = styled.div`
background-color: ${({ theme }) => theme.tokenRowHover};
}
${({ theme }) => theme.mediaWidth.upToMedium`padding: 0.8rem 1rem;`}
${({ theme }) => theme.mediaWidth.upToMedium`
padding: 0.8rem 1rem;
padding-right: 2rem;
`}
`
const TokenRowLeft = styled.div`
@ -297,7 +299,7 @@ export default function CurrencyInputPanel({
const allTokens = useAllTokenDetails()
const { account } = useWeb3Context()
const { account } = useWeb3React()
const userTokenBalance = useAddressBalance(account, selectedTokenAddress)
@ -426,7 +428,6 @@ export default function CurrencyInputPanel({
{!disableTokenSelect && (
<CurrencySelectModal
isOpen={modalIsOpen}
// isOpen={true}
onDismiss={() => {
setModalIsOpen(false)
}}
@ -446,7 +447,7 @@ function CurrencySelectModal({ isOpen, onDismiss, onTokenSelect, allBalances })
const allTokens = useAllTokenDetails()
const { account } = useWeb3Context()
const { account } = useWeb3React()
// BigNumber.js instance
const ethPrice = useUSDPrice()
@ -606,6 +607,7 @@ function CurrencySelectModal({ isOpen, onDismiss, onTokenSelect, allBalances })
isOpen={isOpen}
onDismiss={clearInputAndDismiss}
minHeight={60}
maxHeight={50}
initialFocusRef={isMobile ? undefined : inputRef}
>
<TokenModal>

@ -1,14 +1,12 @@
import React, { useState, useReducer, useEffect } from 'react'
import ReactGA from 'react-ga'
import { createBrowserHistory } from 'history'
import { useTranslation } from 'react-i18next'
import { useWeb3Context } from 'web3-react'
import { ethers } from 'ethers'
import styled from 'styled-components'
import { useTranslation } from 'react-i18next'
import { Button } from '../../theme'
import { useWeb3React } from '../../hooks'
import CurrencyInputPanel from '../CurrencyInputPanel'
import AddressInputPanel from '../AddressInputPanel'
import OversizedPanel from '../OversizedPanel'
@ -21,6 +19,7 @@ import { useTransactionAdder } from '../../contexts/Transactions'
import { useAddressBalance, useExchangeReserves } from '../../contexts/Balances'
import { useFetchAllBalances } from '../../contexts/AllBalances'
import { useAddressAllowance } from '../../contexts/Allowances'
import { useWalletModalToggle } from '../../contexts/Application'
const INPUT = 0
const OUTPUT = 1
@ -246,7 +245,7 @@ function getMarketRate(
export default function ExchangePage({ initialCurrency, sending = false, params }) {
const { t } = useTranslation()
const { account } = useWeb3Context()
const { account, error } = useWeb3React()
const addTransaction = useTransactionAdder()
@ -294,7 +293,10 @@ export default function ExchangePage({ initialCurrency, sending = false, params
const { independentValue, dependentValue, independentField, inputCurrency, outputCurrency } = swapState
const [recipient, setRecipient] = useState({ address: initialRecipient(), name: '' })
const [recipient, setRecipient] = useState({
address: initialRecipient(),
name: ''
})
const [recipientError, setRecipientError] = useState()
// get swap type from the currency types
@ -416,7 +418,10 @@ export default function ExchangePage({ initialCurrency, sending = false, params
throw Error()
}
dispatchSwapState({ type: 'UPDATE_DEPENDENT', payload: calculatedDependentValue })
dispatchSwapState({
type: 'UPDATE_DEPENDENT',
payload: calculatedDependentValue
})
} catch {
setIndependentError(t('insufficientLiquidity'))
}
@ -439,7 +444,10 @@ export default function ExchangePage({ initialCurrency, sending = false, params
throw Error()
}
dispatchSwapState({ type: 'UPDATE_DEPENDENT', payload: calculatedDependentValue })
dispatchSwapState({
type: 'UPDATE_DEPENDENT',
payload: calculatedDependentValue
})
} catch {
setIndependentError(t('insufficientLiquidity'))
}
@ -469,7 +477,10 @@ export default function ExchangePage({ initialCurrency, sending = false, params
if (calculatedDependentValue.lte(ethers.constants.Zero)) {
throw Error()
}
dispatchSwapState({ type: 'UPDATE_DEPENDENT', payload: calculatedDependentValue })
dispatchSwapState({
type: 'UPDATE_DEPENDENT',
payload: calculatedDependentValue
})
} else {
const intermediateValue = calculateEtherTokenInputFromOutput(amount, reserveETHSecond, reserveTokenSecond)
if (intermediateValue.lte(ethers.constants.Zero)) {
@ -483,7 +494,10 @@ export default function ExchangePage({ initialCurrency, sending = false, params
if (calculatedDependentValue.lte(ethers.constants.Zero)) {
throw Error()
}
dispatchSwapState({ type: 'UPDATE_DEPENDENT', payload: calculatedDependentValue })
dispatchSwapState({
type: 'UPDATE_DEPENDENT',
payload: calculatedDependentValue
})
}
} catch {
setIndependentError(t('insufficientLiquidity'))
@ -621,7 +635,10 @@ export default function ExchangePage({ initialCurrency, sending = false, params
}
const estimatedGasLimit = await estimate(...args, { value })
method(...args, { value, gasLimit: calculateGasMargin(estimatedGasLimit, GAS_MARGIN) }).then(response => {
method(...args, {
value,
gasLimit: calculateGasMargin(estimatedGasLimit, GAS_MARGIN)
}).then(response => {
addTransaction(response)
})
}
@ -630,6 +647,8 @@ export default function ExchangePage({ initialCurrency, sending = false, params
const allBalances = useFetchAllBalances()
const toggleWalletModal = useWalletModalToggle()
return (
<>
<CurrencyInputPanel
@ -643,16 +662,25 @@ export default function ExchangePage({ initialCurrency, sending = false, params
if (valueToSet.gt(ethers.constants.Zero)) {
dispatchSwapState({
type: 'UPDATE_INDEPENDENT',
payload: { value: amountFormatter(valueToSet, inputDecimals, inputDecimals, false), field: INPUT }
payload: {
value: amountFormatter(valueToSet, inputDecimals, inputDecimals, false),
field: INPUT
}
})
}
}
}}
onCurrencySelected={inputCurrency => {
dispatchSwapState({ type: 'SELECT_CURRENCY', payload: { currency: inputCurrency, field: INPUT } })
dispatchSwapState({
type: 'SELECT_CURRENCY',
payload: { currency: inputCurrency, field: INPUT }
})
}}
onValueChange={inputValue => {
dispatchSwapState({ type: 'UPDATE_INDEPENDENT', payload: { value: inputValue, field: INPUT } })
dispatchSwapState({
type: 'UPDATE_INDEPENDENT',
payload: { value: inputValue, field: INPUT }
})
}}
showUnlock={showUnlock}
selectedTokens={[inputCurrency, outputCurrency]}
@ -678,10 +706,16 @@ export default function ExchangePage({ initialCurrency, sending = false, params
description={outputValueFormatted && independentField === INPUT ? estimatedText : ''}
extraText={outputBalanceFormatted && formatBalance(outputBalanceFormatted)}
onCurrencySelected={outputCurrency => {
dispatchSwapState({ type: 'SELECT_CURRENCY', payload: { currency: outputCurrency, field: OUTPUT } })
dispatchSwapState({
type: 'SELECT_CURRENCY',
payload: { currency: outputCurrency, field: OUTPUT }
})
}}
onValueChange={outputValue => {
dispatchSwapState({ type: 'UPDATE_INDEPENDENT', payload: { value: outputValue, field: OUTPUT } })
dispatchSwapState({
type: 'UPDATE_INDEPENDENT',
payload: { value: outputValue, field: OUTPUT }
})
}}
selectedTokens={[inputCurrency, outputCurrency]}
selectedTokenAddress={outputCurrency}
@ -753,11 +787,14 @@ export default function ExchangePage({ initialCurrency, sending = false, params
/>
<Flex>
<Button
disabled={!isValid || customSlippageError === 'invalid'}
onClick={onSwap}
disabled={!account && !error ? false : !isValid || customSlippageError === 'invalid'}
onClick={account && !error ? onSwap : toggleWalletModal}
warning={highSlippageWarning || customSlippageError === 'warning'}
loggedOut={!account}
>
{sending
{!account
? 'Connect to a Wallet'
: sending
? highSlippageWarning || customSlippageError === 'warning'
? t('sendAnyway')
: t('send')

@ -0,0 +1,28 @@
import React, { useEffect, useRef } from 'react'
import styled from 'styled-components'
import { useWeb3React } from '../../hooks'
import Jazzicon from 'jazzicon'
const StyledIdenticon = styled.div`
height: 1rem;
width: 1rem;
border-radius: 1.125rem;
background-color: ${({ theme }) => theme.silverGray};
`
export default function Identicon() {
const ref = useRef()
const { account } = useWeb3React()
useEffect(() => {
if (account && ref.current) {
ref.current.innerHTML = ''
ref.current.appendChild(Jazzicon(16, parseInt(account.slice(2, 10), 16)))
}
})
return <StyledIdenticon ref={ref} />
}

@ -1,12 +1,16 @@
import React from 'react'
import styled, { css } from 'styled-components'
import { animated, useTransition } from 'react-spring'
import { animated, useTransition, useSpring } from 'react-spring'
import { Spring } from 'react-spring/renderprops'
import { DialogOverlay, DialogContent } from '@reach/dialog'
import { isMobile } from 'react-device-detect'
import '@reach/dialog/styles.css'
import { transparentize } from 'polished'
import { useGesture } from 'react-use-gesture'
const AnimatedDialogOverlay = animated(DialogOverlay)
const WrappedDialogOverlay = ({ suppressClassNameWarning, ...rest }) => <AnimatedDialogOverlay {...rest} />
const WrappedDialogOverlay = ({ suppressClassNameWarning, mobile, ...rest }) => <AnimatedDialogOverlay {...rest} />
const StyledDialogOverlay = styled(WrappedDialogOverlay).attrs({
suppressClassNameWarning: true
})`
@ -15,34 +19,71 @@ const StyledDialogOverlay = styled(WrappedDialogOverlay).attrs({
display: flex;
align-items: center;
justify-content: center;
background-color: ${({ theme }) => 'transparent'};
${({ mobile }) =>
mobile &&
css`
align-items: flex-end;
`}
&::after {
content: '';
background-color: ${({ theme }) => theme.modalBackground};
opacity: 0.5;
top: 0;
left: 0;
bottom: 0;
right: 0;
/* position: absolute; */
position: fixed;
z-index: -1;
}
}
`
const FilteredDialogContent = ({ minHeight, ...rest }) => <DialogContent {...rest} />
const FilteredDialogContent = ({ minHeight, maxHeight, isOpen, slideInAnimation, mobile, ...rest }) => (
<DialogContent {...rest} />
)
const StyledDialogContent = styled(FilteredDialogContent)`
&[data-reach-dialog-content] {
margin: 0 0 2rem 0;
border: 1px solid ${({ theme }) => theme.concreteGray};
background-color: ${({ theme }) => theme.inputBackground};
box-shadow: 0 4px 8px 0 ${({ theme }) => transparentize(0.95, theme.shadowColor)};
${({ theme }) => theme.mediaWidth.upToMedium`margin: 0;`};
padding: 0px;
width: 50vw;
max-width: 650px;
${({ theme }) => theme.mediaWidth.upToMedium`width: 65vw;`}
${({ theme }) => theme.mediaWidth.upToSmall`width: 85vw;`}
max-height: 50vh;
${({ maxHeight }) =>
maxHeight &&
css`
max-height: ${maxHeight}vh;
`}
${({ minHeight }) =>
minHeight &&
css`
min-height: ${minHeight}vh;
`}
${({ theme }) => theme.mediaWidth.upToMedium`max-height: 65vh;`}
${({ theme }) => theme.mediaWidth.upToSmall`max-height: 80vh;`}
display: flex;
overflow: hidden;
border-radius: 10px;
${({ theme }) => theme.mediaWidth.upToMedium`
width: 65vw;
max-height: 65vh;
margin: 0;
`}
${({ theme, mobile, isOpen }) => theme.mediaWidth.upToSmall`
width: 85vw;
max-height: 66vh;
${mobile &&
css`
width: 100vw;
border-radius: 20px;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
`}
`}
}
`
@ -54,23 +95,97 @@ const HiddenCloseButton = styled.button`
border: none;
`
export default function Modal({ isOpen, onDismiss, minHeight = false, initialFocusRef, children }) {
export default function Modal({ isOpen, onDismiss, minHeight = false, maxHeight = 50, initialFocusRef, children }) {
const transitions = useTransition(isOpen, null, {
config: { duration: 150 },
config: { duration: 200 },
from: { opacity: 0 },
enter: { opacity: 1 },
leave: { opacity: 0 }
})
const [{ xy }, set] = useSpring(() => ({ xy: [0, 0] }))
const bind = useGesture({
onDrag: state => {
let velocity = state.velocity
if (velocity < 1) {
velocity = 1
}
if (velocity > 8) {
velocity = 8
}
set({
xy: state.down ? state.movement : [0, 0],
config: { mass: 1, tension: 210, friction: 20 }
})
if (velocity > 3 && state.direction[1] > 0) {
onDismiss()
}
}
})
if (isMobile) {
return transitions.map(
({ item, key, props }) =>
item && (
<StyledDialogOverlay key={key} style={props} onDismiss={onDismiss} initialFocusRef={initialFocusRef}>
<StyledDialogContent hidden={true} minHeight={minHeight}>
<StyledDialogOverlay
key={key}
style={props}
onDismiss={onDismiss}
initialFocusRef={initialFocusRef}
mobile={isMobile}
>
<Spring // animation for entrance and exit
from={{
transform: isOpen ? 'translateY(200px)' : 'translateY(100px)'
}}
to={{
transform: isOpen ? 'translateY(0px)' : 'translateY(200px)'
}}
>
{props => (
<animated.div
{...bind()}
style={{ transform: xy.interpolate((x, y) => `translate3d(${0}px,${y > 0 ? y : 0}px,0)`) }}
>
<StyledDialogContent
style={props}
hidden={true}
minHeight={minHeight}
maxHeight={maxHeight}
mobile={isMobile}
>
<HiddenCloseButton onClick={onDismiss} />
{children}
</StyledDialogContent>
</animated.div>
)}
</Spring>
</StyledDialogOverlay>
)
)
} else {
return transitions.map(
({ item, key, props }) =>
item && (
<StyledDialogOverlay
key={key}
style={props}
onDismiss={onDismiss}
initialFocusRef={initialFocusRef}
mobile={isMobile}
>
<StyledDialogContent
hidden={true}
minHeight={minHeight}
maxHeight={maxHeight}
isOpen={isOpen}
mobile={isMobile}
>
<HiddenCloseButton onClick={onDismiss} />
{children}
</StyledDialogContent>
</StyledDialogOverlay>
)
)
}
}

@ -4,9 +4,7 @@ import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import { transparentize, darken } from 'polished'
import { Link } from '../../theme/components'
import { useBodyKeyDown } from '../../hooks'
import { useWeb3React, useBodyKeyDown } from '../../hooks'
import { useAddressBalance } from '../../contexts/Balances'
import { isAddress } from '../../utils'
import {
@ -14,7 +12,7 @@ import {
useSaiHolderMessageManager,
useGeneralDaiMessageManager
} from '../../contexts/LocalStorage'
import { useWeb3Context } from 'web3-react'
import { Link } from '../../theme/components'
const tabOrder = [
{
@ -102,7 +100,7 @@ const WarningHeader = styled.div`
const WarningFooter = styled.div`
margin-top: 10px;
font-size: 10px;
textdecoration: italic;
text-decoration: italic;
color: ${({ theme }) => theme.greyText};
`
@ -164,7 +162,7 @@ function NavigationTabs({ location: { pathname }, history }) {
const [showSaiHolderMessage, dismissSaiHolderMessage] = useSaiHolderMessageManager()
const { account } = useWeb3Context()
const { account } = useWeb3React()
const daiBalance = useAddressBalance(account, isAddress('0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359'))

@ -1,96 +0,0 @@
import React from 'react'
import styled from 'styled-components'
import { useWeb3Context } from 'web3-react'
import { getEtherscanLink } from '../../utils'
import { Link, Spinner } from '../../theme'
import Copy from './Copy'
import { Check } from 'react-feather'
import Circle from '../../assets/images/circle.svg'
import { transparentize } from 'polished'
const TransactionStatusWrapper = styled.div`
display: flex;
align-items: center;
min-width: 12px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`
const TransactionWrapper = styled.div`
${({ theme }) => theme.flexRowNoWrap}
justify-content: space-between;
width: 100%;
margin-top: 0.75rem;
a {
/* flex: 1 1 auto; */
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
max-width: 250px;
}
`
const TransactionStatusText = styled.span`
margin-left: 0.25rem;
`
const TransactionState = styled.div`
background-color: ${({ pending, theme }) =>
pending ? transparentize(0.95, theme.royalBlue) : transparentize(0.95, theme.connectedGreen)};
border-radius: 1.5rem;
padding: 0.5rem 0.75rem;
font-weight: 500;
font-size: 0.75rem;
border: 1px solid;
border-color: ${({ pending, theme }) =>
pending ? transparentize(0.75, theme.royalBlue) : transparentize(0.75, theme.connectedGreen)};
:hover {
border-color: ${({ pending, theme }) =>
pending ? transparentize(0, theme.royalBlue) : transparentize(0, theme.connectedGreen)};
}
`
const ButtonWrapper = styled.div`
a {
color: ${({ pending, theme }) => (pending ? theme.royalBlue : theme.connectedGreen)};
}
`
export default function Info({ hash, pending }) {
const { networkId } = useWeb3Context()
return (
<TransactionWrapper key={hash}>
<TransactionStatusWrapper>
<Link href={getEtherscanLink(networkId, hash, 'transaction')}>{hash} </Link>
<Copy />
</TransactionStatusWrapper>
{pending ? (
<ButtonWrapper pending={pending}>
<Link href={getEtherscanLink(networkId, hash, 'transaction')}>
<TransactionState pending={pending}>
<Spinner src={Circle} alt="loader" />
<TransactionStatusText>Pending</TransactionStatusText>
</TransactionState>
</Link>
</ButtonWrapper>
) : (
<ButtonWrapper pending={pending}>
<Link href={getEtherscanLink(networkId, hash, 'transaction')}>
<TransactionState pending={pending}>
<Check size="16" />
<TransactionStatusText>Confirmed</TransactionStatusText>
</TransactionState>
</Link>
</ButtonWrapper>
)}
</TransactionWrapper>
)
}

@ -0,0 +1,90 @@
import React from 'react'
import styled from 'styled-components'
import { transparentize } from 'polished'
import { Link } from '../../theme'
const InfoCard = styled.div`
background-color: ${({ theme }) => 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)};
`
const OptionCard = styled(InfoCard)`
display: grid;
grid-template-columns: 1fr 48px;
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;
`
const OptionCardClickable = styled(OptionCard)`
margin-top: 0;
margin-bottom: 1rem;
&:hover {
cursor: pointer;
border: 1px solid ${({ theme }) => theme.malibuBlue};
}
`
const HeaderText = styled.div`
color: ${props => (props.color === 'blue' ? ({ theme }) => theme.royalBlue : props.color)};
font-size: 1rem;
font-weight: 500;
${({ theme }) => theme.mediaWidth.upToMedium`
font-size: 12px;
`};
`
const SubHeader = styled.div`
color: ${({ theme }) => theme.textColor};
margin-top: 10px;
font-size: 12px;
${({ theme }) => theme.mediaWidth.upToMedium`
font-size: 10px;
`};
`
const IconWrapper = styled.div`
${({ theme }) => theme.flexColumnNoWrap};
align-items: center;
justify-content: center;
& > 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 Option({ link = null, size = null, onClick = null, color, header, subheader = null, icon }) {
const content = (
<OptionCardClickable onClick={onClick}>
<OptionCardLeft>
<HeaderText color={color}>{header}</HeaderText>
{subheader && <SubHeader>{subheader}</SubHeader>}
</OptionCardLeft>
<IconWrapper size={size}>
<img src={icon} alt={'Icon'} />
</IconWrapper>
</OptionCardClickable>
)
if (link) {
return <Link href={link}>{content}</Link>
}
return content
}

@ -0,0 +1,41 @@
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;
margin-bottom: 20px;
border: 1px solid ${({ theme }) => theme.placeholderGray};
`
export default function QrCode({ uri, size, header, subheader, icon }) {
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>
)
}

@ -1,23 +1,64 @@
import React from 'react'
import styled, { css } from 'styled-components'
import { useWeb3Context } from 'web3-react'
import React, { useState, useEffect } from 'react'
import styled from 'styled-components'
import { isMobile } from 'react-device-detect'
import { useWeb3React, UnsupportedChainIdError } from '@web3-react/core'
import { URI_AVAILABLE } from '@web3-react/walletconnect-connector'
import Transaction from './Transaction'
import Copy from './Copy'
import Modal from '../Modal'
import { getEtherscanLink } from '../../utils'
import AccountDetails from '../AccountDetails'
import QrCode from './QrCode'
import Option from './Option'
import { SUPPORTED_WALLETS, MOBILE_DEEP_LINKS } 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 { useWalletModalToggle, useWalletModalOpen } from '../../contexts/Application'
const CloseIcon = styled.div`
position: absolute;
right: 1rem;
top: 14px;
&:hover {
cursor: pointer;
opacity: 0.6;
}
`
const CloseColor = styled(Close)`
path {
stroke: ${({ theme }) => theme.chaliceGray};
}
`
const Wrapper = styled.div`
${({ theme }) => theme.flexColumnNoWrap}
margin: 0;
padding: 0;
width: 100%;
${({ theme }) => theme.flexColumnNoWrap}
background-color: ${({ theme }) => theme.backgroundColor};
`
const HeaderRow = styled.div`
${({ theme }) => theme.flexRowNoWrap};
padding: 1.5rem 1.5rem;
font-weight: 500;
color: ${props => (props.color === 'blue' ? ({ theme }) => theme.royalBlue : 'inherit')};
${({ theme }) => theme.mediaWidth.upToMedium`
padding: 1rem;
`};
`
const ContentWrapper = styled.div`
background-color: ${({ theme }) => theme.backgroundColor};
padding: 2rem;
${({ theme }) => theme.mediaWidth.upToMedium`padding: 1rem`};
`
const UpperSection = styled.div`
padding: 2rem;
position: relative;
background-color: ${({ theme }) => theme.concreteGray};
h5 {
@ -37,172 +78,229 @@ const UpperSection = styled.div`
}
`
const YourAccount = styled.div`
h5 {
margin: 0 0 1rem 0;
font-weight: 400;
color: ${({ theme }) => theme.doveGray};
}
h4 {
margin: 0;
font-weight: 500;
}
`
const LowerSection = styled.div`
${({ theme }) => theme.flexColumnNoWrap}
padding: 2rem;
flex-grow: 1;
overflow: auto;
h5 {
margin: 0;
font-weight: 400;
color: ${({ theme }) => theme.doveGray};
}
div:last-child {
/* margin-bottom: 0; */
}
`
const AccountControl = styled.div`
const Blurb = styled.div`
${({ theme }) => theme.flexRowNoWrap}
align-items: center;
min-width: 0;
justify-content: center;
margin-top: 2rem;
${({ theme }) => theme.mediaWidth.upToMedium`
margin: 1rem;
font-size: 12px;
`};
`
${({ hasENS, isENS }) =>
hasENS &&
isENS &&
css`
margin-bottom: 0.75rem;
`}
font-weight: ${({ hasENS, isENS }) => (hasENS ? (isENS ? css`500` : css`400`) : css`500`)};
font-size: ${({ hasENS, isENS }) => (hasENS ? (isENS ? css`1rem` : css`0.8rem`) : css`1rem`)};
const OptionGrid = styled.div`
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 10px;
${({ theme }) => theme.mediaWidth.upToMedium`
grid-template-columns: 1fr;
grid-gap: 10px;
`};
`
a:hover {
text-decoration: underline;
}
a {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
const HoverText = styled.div`
:hover {
cursor: pointer;
}
`
const TransactionListWrapper = styled.div`
${({ theme }) => theme.flexColumnNoWrap} /* margin: 0 0 1rem 0; */
`
const WALLET_VIEWS = {
OPTIONS: 'options',
ACCOUNT: 'account',
WALLET_CONNECT: 'walletConnect'
}
const StyledLink = styled(Link)`
color: ${({ hasENS, isENS, theme }) => (hasENS ? (isENS ? theme.royalBlue : theme.doveGray) : theme.royalBlue)};
`
export default function WalletModal({ pendingTransactions, confirmedTransactions, ENSName }) {
const { active, account, connector, activate, error } = useWeb3React()
// function getErrorMessage(event) {
// switch (event.code) {
// case InjectedConnector.errorCodes.ETHEREUM_ACCESS_DENIED: {
// return 'Permission Required'
// }
// case InjectedConnector.errorCodes.UNLOCK_REQUIRED: {
// return 'Account Unlock Required'
// }
// case InjectedConnector.errorCodes.NO_WEB3: {
// return 'Not a Web3 Browser'
// }
// default: {
// return 'Connection Error'
// }
// }
// }
const [walletView, setWalletView] = useState(WALLET_VIEWS.ACCOUNT)
export default function WalletModal({ isOpen, error, onDismiss, pendingTransactions, confirmedTransactions, ENSName }) {
const { account, networkId } = useWeb3Context()
const walletModalOpen = useWalletModalOpen()
const toggleWalletModal = useWalletModalToggle()
function renderTransactions(transactions, pending) {
// always reset to account view
useEffect(() => {
if (walletModalOpen) {
setWalletView(WALLET_VIEWS.ACCOUNT)
}
}, [walletModalOpen])
// set up uri listener for walletconnect
const [uri, setUri] = useState()
useEffect(() => {
const activateWC = uri => {
setUri(uri)
setWalletView(WALLET_VIEWS.WALLET_CONNECT)
}
walletconnect.on(URI_AVAILABLE, activateWC)
return () => {
walletconnect.off(URI_AVAILABLE, activateWC)
}
}, [])
// close modal when a connection is successful
const activePrevious = usePrevious(active)
const connectorPrevious = usePrevious(connector)
useEffect(() => {
if (walletModalOpen && ((active && !activePrevious) || (connector && connector !== connectorPrevious && !error))) {
setWalletView(WALLET_VIEWS.ACCOUNT)
}
}, [setWalletView, active, error, connector, walletModalOpen, activePrevious, connectorPrevious])
// 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 (
<TransactionListWrapper>
{transactions.map((hash, i) => {
return <Transaction key={i} hash={hash} pending={pending} />
})}
</TransactionListWrapper>
<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
}
function wrappedOnDismiss() {
onDismiss()
}
function getWalletDisplay() {
if (error) {
if (option.connector === injected) {
// don't show injected if there's no injected provider
if (!(window.web3 || window.ethereum)) {
if (option.name === 'MetaMask') {
return (
<>
<UpperSection>
<h4>Wrong Network</h4>
<h5>Please connect to the main Ethereum network.</h5>
</UpperSection>
</>
)
} else if (account) {
return (
<>
<UpperSection>
<YourAccount>
<h5>Your Account</h5>
{ENSName && (
<AccountControl hasENS={!!ENSName} isENS={true}>
<StyledLink hasENS={!!ENSName} isENS={true} href={getEtherscanLink(networkId, ENSName, 'address')}>
{ENSName} {' '}
</StyledLink>
<Copy toCopy={ENSName} />
</AccountControl>
)}
<AccountControl hasENS={!!ENSName} isENS={false}>
<StyledLink hasENS={!!ENSName} isENS={false} href={getEtherscanLink(networkId, account, 'address')}>
{account} {' '}
</StyledLink>
<Copy toCopy={account} />
</AccountControl>
</YourAccount>
</UpperSection>
{!!pendingTransactions.length || !!confirmedTransactions.length ? (
<LowerSection>
<h5>Recent Transactions</h5>
{renderTransactions(pendingTransactions, true)}
{renderTransactions(confirmedTransactions, false)}
</LowerSection>
) : (
<LowerSection>
<h5>Your transactions will appear here...</h5>
</LowerSection>
)}
</>
<Option
key={key}
color={'#E8831D'}
header={'Install Metamask'}
subheader={'Easy to use browser extension.'}
link={'https://metamask.io/'}
icon={MetamaskIcon}
/>
)
} else {
return (
<>
<UpperSection>
<h4>No Ethereum account found</h4>
<h5>Please visit this page in a Web3 enabled browser.</h5>
<h5>
<Link href={'https://ethereum.org/use/#_3-what-is-a-wallet-and-which-one-should-i-use'}>
Learn more
</Link>
</h5>
</UpperSection>
</>
)
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 (
<Modal isOpen={isOpen} onDismiss={wrappedOnDismiss} minHeight={null}>
<Wrapper>{getWalletDisplay()}</Wrapper>
<Option
onClick={() => {
activate(option.connector)
}}
key={key}
color={option.color}
header={option.name}
subheader={walletView === WALLET_VIEWS.OPTIONS ? null : option.description}
icon={require('../../assets/images/' + option.iconName)}
/>
)
})
}
}
function getModalContent() {
if (error) {
return (
<UpperSection>
<CloseIcon onClick={toggleWalletModal}>
<CloseColor alt={'close icon'} />
</CloseIcon>
<HeaderRow>{error instanceof UnsupportedChainIdError ? 'Wrong Network' : 'Error connecting'}</HeaderRow>
<ContentWrapper>
{error instanceof UnsupportedChainIdError ? (
<h5>Please connect to the main Ethereum network.</h5>
) : (
'Error connecting. Try refreshing the page.'
)}
</ContentWrapper>
</UpperSection>
)
}
if (account && walletView === WALLET_VIEWS.ACCOUNT) {
return (
<AccountDetails
toggleWalletModal={toggleWalletModal}
pendingTransactions={pendingTransactions}
confirmedTransactions={confirmedTransactions}
ENSName={ENSName}
openOptions={() => setWalletView(WALLET_VIEWS.OPTIONS)}
/>
)
}
return (
<UpperSection>
<CloseIcon onClick={toggleWalletModal}>
<CloseColor alt={'close icon'} />
</CloseIcon>
{walletView !== WALLET_VIEWS.ACCOUNT ? (
<HeaderRow
color="blue"
onClick={() => {
setWalletView(WALLET_VIEWS.ACCOUNT)
}}
>
<HoverText>Back</HoverText>
</HeaderRow>
) : (
<HeaderRow>
<HoverText>Connect To A Wallet</HoverText>
</HeaderRow>
)}
<ContentWrapper>
{walletView === WALLET_VIEWS.WALLET_CONNECT ? (
<QrCode
uri={uri}
header={'Connect with Wallet Connect'}
subheader={'Open protocol supported by major mobile wallets'}
size={220}
icon={WalletConnectIcon}
/>
) : !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>
</ContentWrapper>
</UpperSection>
)
}
return (
<Modal
style={{ userSelect: 'none' }}
isOpen={walletModalOpen}
onDismiss={toggleWalletModal}
minHeight={null}
maxHeight={90}
>
<Wrapper>{getModalContent()}</Wrapper>
</Modal>
)
}

@ -1,14 +1,13 @@
import React, { useState, useEffect } from 'react'
import { useWeb3Context, Connectors } from 'web3-react'
import { useWeb3React } from '@web3-react/core'
import styled from 'styled-components'
import { ethers } from 'ethers'
import { useTranslation } from 'react-i18next'
import { isMobile } from 'react-device-detect'
import { network } from '../../connectors'
import { useEagerConnect, useInactiveListener } from '../../hooks'
import { Spinner } from '../../theme'
import Circle from '../../assets/images/circle.svg'
const { Connector } = Connectors
import { NetworkContextName } from '../../constants'
const MessageWrapper = styled.div`
display: flex;
@ -31,82 +30,73 @@ const SpinnerWrapper = styled(Spinner)`
}
`
function tryToSetConnector(setConnector, setError) {
setConnector('Injected', { suppressAndThrowErrors: true }).catch(() => {
setConnector('Network', { suppressAndThrowErrors: true }).catch(error => {
setError(error)
})
})
}
export default function Web3ReactManager({ children }) {
const { t } = useTranslation()
const { active, error, setConnector, setError } = useWeb3Context()
// control whether or not we render the error, after parsing
const blockRender = error && error.code && error.code === Connector.errorCodes.UNSUPPORTED_NETWORK
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
// TODO think about not doing this at all
useEffect(() => {
if (!active && !error) {
if (window.ethereum || window.web3) {
if (isMobile) {
tryToSetConnector(setConnector, setError)
} else {
const library = new ethers.providers.Web3Provider(window.ethereum || window.web3)
library.listAccounts().then(accounts => {
if (accounts.length >= 1) {
tryToSetConnector(setConnector, setError)
} else {
setConnector('Network', { suppressAndThrowErrors: true }).catch(error => {
setError(error)
})
if (triedEager && !networkActive && !networkError && !active) {
activateNetwork(network)
}
})
}
} else {
setConnector('Network', { suppressAndThrowErrors: true }).catch(error => {
setError(error)
})
}
}
})
}, [triedEager, networkActive, networkError, activateNetwork, active])
// parse the error
// 'pause' the network connector if we're ever connected to an account and it's active
useEffect(() => {
if (error) {
// if the user changes to the wrong network, unset the connector
if (error.code === Connector.errorCodes.UNSUPPORTED_NETWORK) {
setConnector('Network', { suppressAndThrowErrors: true }).catch(error => {
setError(error)
})
if (active && networkActive) {
network.pause()
}
}
})
}, [active, networkActive])
// 'resume' the network connector if we're ever not connected to an account and it's active
useEffect(() => {
if (!active && networkActive) {
network.resume()
}
}, [active, networkActive])
// when there's no account connected, react to logins (broadly speaking) on the injected provider, if it exists
useInactiveListener(!triedEager)
// handle delayed loader state
const [showLoader, setShowLoader] = useState(false)
useEffect(() => {
const timeout = setTimeout(() => {
setShowLoader(true)
}, 600)
return () => {
clearTimeout(timeout)
}
}, [])
if (blockRender) {
// on page load, do nothing until we've tried to connect to the injected connector
if (!triedEager) {
return null
} else if (error) {
}
// if the account context isn't active, and there's an error on the network context, it's an irrecoverable error
if (!active && networkError) {
return (
<MessageWrapper>
<Message>{t('unknownError')}</Message>
</MessageWrapper>
)
} else if (!active) {
}
// if neither context is active, spin
if (!active && !networkActive) {
return showLoader ? (
<MessageWrapper>
<SpinnerWrapper src={Circle} />
</MessageWrapper>
) : null
} else {
return children
}
return children
}

@ -1,20 +1,22 @@
import React, { useReducer, useEffect, useRef } from 'react'
import styled from 'styled-components'
import React from 'react'
import styled, { css } from 'styled-components'
import { useTranslation } from 'react-i18next'
import { useWeb3Context, Connectors } from 'web3-react'
import { useWeb3React, UnsupportedChainIdError } from '@web3-react/core'
import { darken, transparentize } from 'polished'
import Jazzicon from 'jazzicon'
import { ethers } from 'ethers'
import { Activity } from 'react-feather'
import { shortenAddress } from '../../utils'
import { useENSName } from '../../hooks'
import WalletModal from '../WalletModal'
import { useAllTransactions } from '../../contexts/Transactions'
import { useWalletModalToggle } from '../../contexts/Application'
import { Spinner } from '../../theme'
import Circle from '../../assets/images/circle.svg'
const { Connector } = Connectors
import { injected, walletconnect, walletlink } from '../../connectors'
import WalletConnectIcon from '../../assets/images/walletConnectIcon.svg'
import CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg'
import { NetworkContextName } from '../../constants'
import Identicon from '../Identicon'
const Web3StatusGeneric = styled.button`
${({ theme }) => theme.flexRowNoWrap}
@ -42,15 +44,30 @@ const Web3StatusError = styled(Web3StatusGeneric)`
`
const Web3StatusConnect = styled(Web3StatusGeneric)`
background-color: ${({ theme }) => theme.royalBlue};
background-color: transparent;
border: 1px solid ${({ theme }) => theme.royalBlue};
color: ${({ theme }) => theme.white};
color: ${({ theme }) => theme.royalBlue};
font-weight: 500;
:hover,
:focus {
background-color: ${({ theme }) => darken(0.1, theme.royalBlue)};
border: 1px solid ${({ theme }) => darken(0.1, theme.royalBlue)};
color: ${({ theme }) => darken(0.1, theme.royalBlue)};
}
${({ faded }) =>
faded &&
css`
background-color: transparent;
border: 1px solid ${({ theme }) => theme.royalBlue};
color: ${({ theme }) => theme.royalBlue};
:hover,
:focus {
border: 1px solid ${({ theme }) => darken(0.1, theme.royalBlue)};
color: ${({ theme }) => darken(0.1, theme.royalBlue)};
}
`}
`
const Web3StatusConnected = styled(Web3StatusGeneric)`
@ -59,10 +76,6 @@ const Web3StatusConnected = styled(Web3StatusGeneric)`
color: ${({ pending, theme }) => (pending ? theme.royalBlue : theme.doveGray)};
font-weight: 400;
:hover {
/* > P {
color: ${({ theme }) => theme.uniswapPink};
} */
background-color: ${({ pending, theme }) =>
pending ? transparentize(0.9, theme.royalBlue) : darken(0.05, theme.inputBackground)};
@ -70,6 +83,7 @@ const Web3StatusConnected = styled(Web3StatusGeneric)`
border: 1px solid
${({ pending, theme }) => (pending ? darken(0.1, theme.royalBlue) : darken(0.1, theme.mercuryGray))};
}
}
`
const Text = styled.p`
@ -81,13 +95,6 @@ const Text = styled.p`
font-size: 0.83rem;
`
const Identicon = styled.div`
height: 1rem;
width: 1rem;
border-radius: 1.125rem;
background-color: ${({ theme }) => theme.silverGray};
`
const NetworkIcon = styled(Activity)`
margin-left: 0.25rem;
margin-right: 0.5rem;
@ -99,41 +106,20 @@ const SpinnerWrapper = styled(Spinner)`
margin: 0 0.25rem 0 0.25rem;
`
const walletModalInitialState = {
open: false,
error: undefined
}
const WALLET_MODAL_ERROR = 'WALLET_MODAL_ERROR'
const WALLET_MODAL_OPEN = 'WALLET_MODAL_OPEN'
const WALLET_MODAL_OPEN_ERROR = 'WALLET_MODAL_OPEN_ERROR'
const WALLET_MODAL_CLOSE = 'WALLET_MODAL_CLOSE'
function walletModalReducer(state, { type, payload }) {
switch (type) {
case WALLET_MODAL_ERROR: {
const { error } = payload
return { ...state, error }
const IconWrapper = styled.div`
${({ theme }) => theme.flexColumnNoWrap};
align-items: center;
justify-content: center;
& > * {
height: ${({ size }) => (size ? size + 'px' : '32px')};
width: ${({ size }) => (size ? size + 'px' : '32px')};
}
case WALLET_MODAL_OPEN: {
return { ...state, open: true }
}
case WALLET_MODAL_OPEN_ERROR: {
const { error } = payload || {}
return { open: true, error }
}
case WALLET_MODAL_CLOSE: {
return { ...state, open: false }
}
default: {
throw Error(`Unexpected action type in walletModalReducer reducer: '${type}'.`)
}
}
}
`
export default function Web3Status() {
const { t } = useTranslation()
const { active, account, connectorName, setConnector } = useWeb3Context()
const { active, account, connector, error } = useWeb3React()
const contextNetwork = useWeb3React(NetworkContextName)
const ENSName = useENSName(account)
@ -143,137 +129,60 @@ export default function Web3Status() {
const hasPendingTransactions = !!pending.length
const [{ open: walletModalIsOpen, error: walletModalError }, dispatch] = useReducer(
walletModalReducer,
walletModalInitialState
const toggleWalletModal = useWalletModalToggle()
// handle the logo we want to show with the account
function getStatusIcon() {
if (connector === injected) {
return <Identicon />
} else if (connector === walletconnect) {
return (
<IconWrapper size={16}>
<img src={WalletConnectIcon} alt={''} />
</IconWrapper>
)
} else if (connector === walletlink) {
return (
<IconWrapper size={16}>
<img src={CoinbaseWalletIcon} alt={''} />
</IconWrapper>
)
function setError(error) {
dispatch({ type: WALLET_MODAL_ERROR, payload: { error } })
}
function openWalletModal(error) {
dispatch({ type: WALLET_MODAL_OPEN, ...(error ? { payload: { error } } : {}) })
}
function closeWalletModal() {
dispatch({ type: WALLET_MODAL_CLOSE })
}
// janky logic to detect log{ins,outs}...
useEffect(() => {
// if the injected connector is not active...
const { ethereum } = window
if (connectorName !== 'Injected') {
if (connectorName === 'Network' && ethereum && ethereum.on && ethereum.removeListener) {
function tryToActivateInjected() {
const library = new ethers.providers.Web3Provider(window.ethereum)
// if calling enable won't pop an approve modal, then try to activate injected...
library.listAccounts().then(accounts => {
if (accounts.length >= 1) {
setConnector('Injected', { suppressAndThrowErrors: true })
.then(() => {
setError()
})
.catch(error => {
// ...and if the error is that they're on the wrong network, display it, otherwise eat it
if (error.code === Connector.errorCodes.UNSUPPORTED_NETWORK) {
setError(error)
}
})
}
})
}
ethereum.on('networkChanged', tryToActivateInjected)
ethereum.on('accountsChanged', tryToActivateInjected)
return () => {
if (ethereum.removeListener) {
ethereum.removeListener('networkChanged', tryToActivateInjected)
ethereum.removeListener('accountsChanged', tryToActivateInjected)
}
}
}
} else {
// ...poll to check the accounts array, and if it's ever 0 i.e. the user logged out, update the connector
if (ethereum) {
const accountPoll = setInterval(() => {
const library = new ethers.providers.Web3Provider(ethereum)
library.listAccounts().then(accounts => {
if (accounts.length === 0) {
setConnector('Network')
}
})
}, 750)
return () => {
clearInterval(accountPoll)
}
}
}
}, [connectorName, setConnector])
function onClick() {
if (walletModalError) {
openWalletModal()
} else if (connectorName === 'Network' && (window.ethereum || window.web3)) {
setConnector('Injected', { suppressAndThrowErrors: true }).catch(error => {
if (error.code === Connector.errorCodes.UNSUPPORTED_NETWORK) {
setError(error)
}
})
} else {
openWalletModal()
}
}
const ref = useRef()
useEffect(() => {
if (ref.current) {
ref.current.innerHTML = ''
if (account) {
ref.current.appendChild(Jazzicon(16, parseInt(account.slice(2, 10), 16)))
}
}
}, [account, walletModalError])
function getWeb3Status() {
if (walletModalError) {
// this is ok because we're guaranteed that the error is a wrong network error
if (account) {
return (
<Web3StatusError onClick={onClick}>
<NetworkIcon />
<Text>Wrong Network</Text>
</Web3StatusError>
<Web3StatusConnected onClick={toggleWalletModal} pending={hasPendingTransactions}>
{hasPendingTransactions && <SpinnerWrapper src={Circle} alt="loader" />}
<Text>{ENSName || shortenAddress(account)}</Text>
{getStatusIcon()}
</Web3StatusConnected>
)
} else if (!account) {
} else if (error) {
return (
<Web3StatusConnect onClick={onClick}>
<Text>{t('Connect')}</Text>
</Web3StatusConnect>
<Web3StatusError onClick={toggleWalletModal}>
<NetworkIcon />
<Text>{error instanceof UnsupportedChainIdError ? 'Wrong Network' : 'Error'}</Text>
</Web3StatusError>
)
} else {
return (
<Web3StatusConnected onClick={onClick} pending={hasPendingTransactions}>
{hasPendingTransactions && <SpinnerWrapper src={Circle} alt="loader" />}
<Text>{ENSName || shortenAddress(account)}</Text>
<Identicon ref={ref} />
</Web3StatusConnected>
<Web3StatusConnect onClick={toggleWalletModal} faded={!account}>
<Text>{t('Connect to a Wallet')}</Text>
</Web3StatusConnect>
)
}
}
if (!contextNetwork.active && !active) {
return null
}
return (
active && (
<>
{getWeb3Status()}
<WalletModal
isOpen={walletModalIsOpen}
error={walletModalError}
onDismiss={closeWalletModal}
ENSName={ENSName}
pendingTransactions={pending}
confirmedTransactions={confirmed}
/>
<WalletModal ENSName={ENSName} pendingTransactions={pending} confirmedTransactions={confirmed} />
</>
)
)
}

115
src/connectors/Injected.js Normal file

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

15
src/connectors/Network.js Normal file

@ -0,0 +1,15 @@
import { NetworkConnector as NetworkConnectorCore } from '@web3-react/network-connector'
export class NetworkConnector extends NetworkConnectorCore {
pause() {
if (this.active) {
this.providers[this.currentChainId].stop()
}
}
resume() {
if (this.active) {
this.providers[this.currentChainId].start()
}
}
}

30
src/connectors/index.js Normal file

@ -0,0 +1,30 @@
import { WalletConnectConnector } from '@web3-react/walletconnect-connector'
import { WalletLinkConnector } from '@web3-react/walletlink-connector'
import { InjectedConnector } from './Injected'
import { NetworkConnector } from './Network'
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 walletconnect = new WalletConnectConnector({
rpc: { 1: process.env.REACT_APP_NETWORK_URL },
bridge: 'https://bridge.walletconnect.org',
qrcode: false,
pollingInterval: POLLING_INTERVAL
})
export const walletlink = new WalletLinkConnector({
url: process.env.REACT_APP_NETWORK_URL,
appName: 'Uniswap',
appLogoUrl:
'https://mpng.pngfly.com/20181202/bex/kisspng-emoji-domain-unicorn-pin-badges-sticker-unicorn-tumblr-emoji-unicorn-iphoneemoji-5c046729264a77.5671679315437924251569.jpg'
})

@ -1,3 +1,5 @@
import { injected, walletconnect, walletlink } from '../connectors'
export const FACTORY_ADDRESSES = {
1: '0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95',
3: '0x9c83dCE8CA20E9aAF9D3efc003b2ea62aBC08351',
@ -10,9 +12,63 @@ export const SUPPORTED_THEMES = {
LIGHT: 'LIGHT'
}
export const SUPPORTED_WALLETS = {
INJECTED: {
connector: injected,
id: 'Injected',
name: 'Injected',
iconName: 'arrow-right.svg',
description: 'Injected web3 provider.',
color: '#010101'
},
METAMASK: {
connector: injected,
id: 'MetaMask',
name: 'MetaMask',
iconName: 'metamask.png',
description: 'Easy-to-use browser extension.',
color: '#E8831D'
},
WALLET_CONNECT: {
connector: walletconnect,
id: 'WalletConnect',
name: 'Wallet Connect',
iconName: 'walletConnectIcon.svg',
description: 'Connect to Trust Wallet, Rainbow Wallet and more...',
color: '#4196FC'
},
WALLET_LINK: {
connector: walletlink,
id: 'WalletLink',
name: 'Coinbase Wallet',
iconName: 'coinbaseWalletIcon.svg',
description: 'Use Coinbase Wallet app on mobile device',
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'
},
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'
}
}
// list of tokens that lock fund on adding liquidity - used to disable button
export const brokenTokens = [
'0xb8c77482e45f1f44de1745f52c74426c631bdd52',
'0x95daaab98046846bf4b2853e23cba236fa394a31',
'0x55296f69f40ea6d20e478533c15a6b08b654e758'
'0xB8c77482e45F1F44dE1745F52C74426C631bDD52',
'0x95dAaaB98046846bF4B2853e23cba236fa394A31',
'0x55296f69f40Ea6d20E478533C15A6B08B654E758'
]
export const NetworkContextName = 'NETWORK'

@ -1,8 +1,8 @@
import React, { createContext, useContext, useReducer, useMemo, useCallback } from 'react'
import { ethers } from 'ethers'
import { getTokenReserves, getMarketDetails, BigNumber } from '@uniswap/sdk'
import { useWeb3Context } from 'web3-react'
import { useWeb3React } from '../hooks'
import { safeAccess, isAddress, getEtherBalance, getTokenBalance } from '../utils'
import { useAllTokenDetails } from './Tokens'
@ -53,13 +53,13 @@ export default function Provider({ children }) {
}
export function useFetchAllBalances() {
const { account, networkId, library } = useWeb3Context()
const { library, chainId, account } = useWeb3React()
const allTokens = useAllTokenDetails()
const [state, { update }] = useAllBalancesContext()
const { allBalanceData } = safeAccess(state, [networkId, account]) || {}
const { allBalanceData } = safeAccess(state, [chainId, account]) || {}
const getData = async () => {
if (!!library && !!account) {
@ -90,7 +90,7 @@ export function useFetchAllBalances() {
}
})
)
update(newBalances, networkId, account)
update(newBalances, chainId, account)
}
}

@ -1,6 +1,7 @@
import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react'
import { useWeb3Context } from 'web3-react'
import {} from '@web3-react/core'
import { useWeb3React } from '../hooks'
import { safeAccess, isAddress, getTokenAllowance } from '../utils'
import { useBlockNumber } from './Application'
@ -54,12 +55,12 @@ export default function Provider({ children }) {
}
export function useAddressAllowance(address, tokenAddress, spenderAddress) {
const { networkId, library } = useWeb3Context()
const { library, chainId } = useWeb3React()
const globalBlockNumber = useBlockNumber()
const [state, { update }] = useAllowancesContext()
const { value, blockNumber } = safeAccess(state, [networkId, address, tokenAddress, spenderAddress]) || {}
const { value, blockNumber } = safeAccess(state, [chainId, address, tokenAddress, spenderAddress]) || {}
useEffect(() => {
if (
@ -67,7 +68,7 @@ export function useAddressAllowance(address, tokenAddress, spenderAddress) {
isAddress(tokenAddress) &&
isAddress(spenderAddress) &&
(value === undefined || blockNumber !== globalBlockNumber) &&
(networkId || networkId === 0) &&
(chainId || chainId === 0) &&
library
) {
let stale = false
@ -75,12 +76,12 @@ export function useAddressAllowance(address, tokenAddress, spenderAddress) {
getTokenAllowance(address, tokenAddress, spenderAddress, library)
.then(value => {
if (!stale) {
update(networkId, address, tokenAddress, spenderAddress, value, globalBlockNumber)
update(chainId, address, tokenAddress, spenderAddress, value, globalBlockNumber)
}
})
.catch(() => {
if (!stale) {
update(networkId, address, tokenAddress, spenderAddress, null, globalBlockNumber)
update(chainId, address, tokenAddress, spenderAddress, null, globalBlockNumber)
}
})
@ -88,7 +89,7 @@ export function useAddressAllowance(address, tokenAddress, spenderAddress) {
stale = true
}
}
}, [address, tokenAddress, spenderAddress, value, blockNumber, globalBlockNumber, networkId, library, update])
}, [address, tokenAddress, spenderAddress, value, blockNumber, globalBlockNumber, chainId, library, update])
return value
}

@ -1,14 +1,16 @@
import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react'
import { useWeb3Context } from 'web3-react'
import { useWeb3React } from '../hooks'
import { safeAccess } from '../utils'
import { getUSDPrice } from '../utils/price'
const BLOCK_NUMBER = 'BLOCK_NUMBER'
const USD_PRICE = 'USD_PRICE'
const WALLET_MODAL_OPEN = 'WALLET_MODAL_OPEN'
const UPDATE_BLOCK_NUMBER = 'UPDATE_BLOCK_NUMBER'
const UPDATE_USD_PRICE = 'UPDATE_USD_PRICE'
const TOGGLE_WALLET_MODAL = 'TOGGLE_WALLET_MODAL'
const ApplicationContext = createContext()
@ -38,6 +40,9 @@ function reducer(state, { type, payload }) {
}
}
}
case TOGGLE_WALLET_MODAL: {
return { ...state, [WALLET_MODAL_OPEN]: !state[WALLET_MODAL_OPEN] }
}
default: {
throw Error(`Unexpected action type in ApplicationContext reducer: '${type}'.`)
}
@ -47,7 +52,8 @@ function reducer(state, { type, payload }) {
export default function Provider({ children }) {
const [state, dispatch] = useReducer(reducer, {
[BLOCK_NUMBER]: {},
[USD_PRICE]: {}
[USD_PRICE]: {},
[WALLET_MODAL_OPEN]: false
})
const updateBlockNumber = useCallback((networkId, blockNumber) => {
@ -58,9 +64,18 @@ export default function Provider({ children }) {
dispatch({ type: UPDATE_USD_PRICE, payload: { networkId, USDPrice } })
}, [])
const toggleWalletModal = useCallback(() => {
dispatch({ type: TOGGLE_WALLET_MODAL })
}, [])
return (
<ApplicationContext.Provider
value={useMemo(() => [state, { updateBlockNumber, updateUSDPrice }], [state, updateBlockNumber, updateUSDPrice])}
value={useMemo(() => [state, { updateBlockNumber, updateUSDPrice, toggleWalletModal }], [
state,
updateBlockNumber,
updateUSDPrice,
toggleWalletModal
])}
>
{children}
</ApplicationContext.Provider>
@ -68,36 +83,29 @@ export default function Provider({ children }) {
}
export function Updater() {
const { networkId, library } = useWeb3Context()
const { library, chainId } = useWeb3React()
const globalBlockNumber = useBlockNumber()
const [, { updateBlockNumber, updateUSDPrice }] = useApplicationContext()
// slow down polling interval
// if (library && connectorName === 'Network' && library.pollingInterval !== 15) {
// library.pollingInterval = 15
// } else if (library && library.pollingInterval !== 5) {
// library.pollingInterval = 5
// }
// update usd price
useEffect(() => {
if (library && networkId === 1) {
if (library && chainId === 1) {
let stale = false
getUSDPrice(library)
.then(([price]) => {
if (!stale) {
updateUSDPrice(networkId, price)
updateUSDPrice(chainId, price)
}
})
.catch(() => {
if (!stale) {
updateUSDPrice(networkId, null)
updateUSDPrice(chainId, null)
}
})
}
}, [globalBlockNumber, library, networkId, updateUSDPrice])
}, [globalBlockNumber, library, chainId, updateUSDPrice])
// update block number
useEffect(() => {
@ -109,12 +117,12 @@ export function Updater() {
.getBlockNumber()
.then(blockNumber => {
if (!stale) {
updateBlockNumber(networkId, blockNumber)
updateBlockNumber(chainId, blockNumber)
}
})
.catch(() => {
if (!stale) {
updateBlockNumber(networkId, null)
updateBlockNumber(chainId, null)
}
})
}
@ -127,23 +135,35 @@ export function Updater() {
library.removeListener('block', update)
}
}
}, [networkId, library, updateBlockNumber])
}, [chainId, library, updateBlockNumber])
return null
}
export function useBlockNumber() {
const { networkId } = useWeb3Context()
const { chainId } = useWeb3React()
const [state] = useApplicationContext()
return safeAccess(state, [BLOCK_NUMBER, networkId])
return safeAccess(state, [BLOCK_NUMBER, chainId])
}
export function useUSDPrice() {
const { networkId } = useWeb3Context()
const { chainId } = useWeb3React()
const [state] = useApplicationContext()
return safeAccess(state, [USD_PRICE, networkId])
return safeAccess(state, [USD_PRICE, chainId])
}
export function useWalletModalOpen() {
const [state] = useApplicationContext()
return state[WALLET_MODAL_OPEN]
}
export function useWalletModalToggle() {
const [, { toggleWalletModal }] = useApplicationContext()
return toggleWalletModal
}

@ -1,6 +1,6 @@
import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react'
import { useWeb3Context } from 'web3-react'
import { useWeb3React } from '../hooks'
import { safeAccess, isAddress, getEtherBalance, getTokenBalance } from '../utils'
import { useBlockNumber } from './Application'
import { useTokenDetails } from './Tokens'
@ -52,38 +52,38 @@ export default function Provider({ children }) {
}
export function useAddressBalance(address, tokenAddress) {
const { networkId, library } = useWeb3Context()
const { library, chainId } = useWeb3React()
const globalBlockNumber = useBlockNumber()
const [state, { update }] = useBalancesContext()
const { value, blockNumber } = safeAccess(state, [networkId, address, tokenAddress]) || {}
const { value, blockNumber } = safeAccess(state, [chainId, address, tokenAddress]) || {}
useEffect(() => {
if (
isAddress(address) &&
(tokenAddress === 'ETH' || isAddress(tokenAddress)) &&
(value === undefined || blockNumber !== globalBlockNumber) &&
(networkId || networkId === 0) &&
(chainId || chainId === 0) &&
library
) {
let stale = false
;(tokenAddress === 'ETH' ? getEtherBalance(address, library) : getTokenBalance(tokenAddress, address, library))
.then(value => {
if (!stale) {
update(networkId, address, tokenAddress, value, globalBlockNumber)
update(chainId, address, tokenAddress, value, globalBlockNumber)
}
})
.catch(() => {
if (!stale) {
update(networkId, address, tokenAddress, null, globalBlockNumber)
update(chainId, address, tokenAddress, null, globalBlockNumber)
}
})
return () => {
stale = true
}
}
}, [address, tokenAddress, value, blockNumber, globalBlockNumber, networkId, library, update])
}, [address, tokenAddress, value, blockNumber, globalBlockNumber, chainId, library, update])
return value
}

@ -1,7 +1,7 @@
import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react'
import { useWeb3Context } from 'web3-react'
import { ethers } from 'ethers'
import { useWeb3React } from '../hooks'
import {
isAddress,
getTokenName,
@ -558,10 +558,10 @@ export default function Provider({ children }) {
}
export function useTokenDetails(tokenAddress) {
const { networkId, library } = useWeb3Context()
const { library, chainId } = useWeb3React()
const [state, { update }] = useTokensContext()
const allTokensInNetwork = { ...ETH, ...(safeAccess(state, [networkId]) || {}) }
const allTokensInNetwork = { ...ETH, ...(safeAccess(state, [chainId]) || {}) }
const { [NAME]: name, [SYMBOL]: symbol, [DECIMALS]: decimals, [EXCHANGE_ADDRESS]: exchangeAddress } =
safeAccess(allTokensInNetwork, [tokenAddress]) || {}
@ -569,7 +569,7 @@ export function useTokenDetails(tokenAddress) {
if (
isAddress(tokenAddress) &&
(name === undefined || symbol === undefined || decimals === undefined || exchangeAddress === undefined) &&
(networkId || networkId === 0) &&
(chainId || chainId === 0) &&
library
) {
let stale = false
@ -577,14 +577,14 @@ export function useTokenDetails(tokenAddress) {
const namePromise = getTokenName(tokenAddress, library).catch(() => null)
const symbolPromise = getTokenSymbol(tokenAddress, library).catch(() => null)
const decimalsPromise = getTokenDecimals(tokenAddress, library).catch(() => null)
const exchangeAddressPromise = getTokenExchangeAddressFromFactory(tokenAddress, networkId, library).catch(
const exchangeAddressPromise = getTokenExchangeAddressFromFactory(tokenAddress, chainId, library).catch(
() => null
)
Promise.all([namePromise, symbolPromise, decimalsPromise, exchangeAddressPromise]).then(
([resolvedName, resolvedSymbol, resolvedDecimals, resolvedExchangeAddress]) => {
if (!stale) {
update(networkId, tokenAddress, resolvedName, resolvedSymbol, resolvedDecimals, resolvedExchangeAddress)
update(chainId, tokenAddress, resolvedName, resolvedSymbol, resolvedDecimals, resolvedExchangeAddress)
}
}
)
@ -592,16 +592,16 @@ export function useTokenDetails(tokenAddress) {
stale = true
}
}
}, [tokenAddress, name, symbol, decimals, exchangeAddress, networkId, library, update])
}, [tokenAddress, name, symbol, decimals, exchangeAddress, chainId, library, update])
return { name, symbol, decimals, exchangeAddress }
}
export function useAllTokenDetails(requireExchange = true) {
const { networkId } = useWeb3Context()
const { chainId } = useWeb3React()
const [state] = useTokensContext()
const tokenDetails = { ...ETH, ...(safeAccess(state, [networkId]) || {}) }
const tokenDetails = { ...ETH, ...(safeAccess(state, [chainId]) || {}) }
return requireExchange
? Object.keys(tokenDetails)

@ -1,6 +1,6 @@
import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react'
import { useWeb3Context } from 'web3-react'
import { useWeb3React } from '../hooks'
import { safeAccess } from '../utils'
import { useBlockNumber } from './Application'
@ -103,15 +103,15 @@ export default function Provider({ children }) {
}
export function Updater() {
const { networkId, library } = useWeb3Context()
const { chainId, library } = useWeb3React()
const globalBlockNumber = useBlockNumber()
const [state, { check, finalize }] = useTransactionsContext()
const allTransactions = safeAccess(state, [networkId]) || {}
const allTransactions = safeAccess(state, [chainId]) || {}
useEffect(() => {
if ((networkId || networkId === 0) && library) {
if ((chainId || chainId === 0) && library) {
let stale = false
Object.keys(allTransactions)
.filter(
@ -123,14 +123,14 @@ export function Updater() {
.then(receipt => {
if (!stale) {
if (!receipt) {
check(networkId, hash, globalBlockNumber)
check(chainId, hash, globalBlockNumber)
} else {
finalize(networkId, hash, receipt)
finalize(chainId, hash, receipt)
}
}
})
.catch(() => {
check(networkId, hash, globalBlockNumber)
check(chainId, hash, globalBlockNumber)
})
})
@ -138,20 +138,20 @@ export function Updater() {
stale = true
}
}
}, [networkId, library, allTransactions, globalBlockNumber, check, finalize])
}, [chainId, library, allTransactions, globalBlockNumber, check, finalize])
return null
}
export function useTransactionAdder() {
const { networkId } = useWeb3Context()
const { chainId } = useWeb3React()
const [, { add }] = useTransactionsContext()
return useCallback(
(response, customData = {}) => {
if (!(networkId || networkId === 0)) {
throw Error(`Invalid networkId '${networkId}`)
if (!(chainId || chainId === 0)) {
throw Error(`Invalid networkId '${chainId}`)
}
const hash = safeAccess(response, ['hash'])
@ -159,18 +159,18 @@ export function useTransactionAdder() {
if (!hash) {
throw Error('No transaction hash found.')
}
add(networkId, hash, { ...response, [CUSTOM_DATA]: customData })
add(chainId, hash, { ...response, [CUSTOM_DATA]: customData })
},
[networkId, add]
[chainId, add]
)
}
export function useAllTransactions() {
const { networkId } = useWeb3Context()
const { chainId } = useWeb3React()
const [state] = useTransactionsContext()
return safeAccess(state, [networkId]) || {}
return safeAccess(state, [chainId]) || {}
}
export function usePendingApproval(tokenAddress) {

@ -1,9 +1,81 @@
import { useState, useMemo, useCallback, useEffect } from 'react'
import { useWeb3Context } from 'web3-react'
import { useState, useMemo, useCallback, useEffect, useRef } from 'react'
import { useWeb3React as useWeb3ReactCore } from '@web3-react/core'
import copy from 'copy-to-clipboard'
import { NetworkContextName } from '../constants'
import ERC20_ABI from '../constants/abis/erc20'
import { getContract, getFactoryContract, getExchangeContract, isAddress } from '../utils'
import copy from 'copy-to-clipboard'
import { injected } from '../connectors'
export function useWeb3React() {
const context = useWeb3ReactCore()
const contextNetwork = useWeb3ReactCore(NetworkContextName)
return context.active ? context : contextNetwork
}
export function useEagerConnect() {
const { activate, active } = useWeb3ReactCore() // specifically using useWeb3ReactCore because of what this hook does
const [tried, setTried] = useState(false)
useEffect(() => {
injected.isAuthorized().then(isAuthorized => {
if (isAuthorized) {
activate(injected, undefined, true).catch(() => {
setTried(true)
})
} else {
setTried(true)
}
})
}, [activate]) // intentionally only running on mount (make sure it's only mounted once :))
// if the connection worked, wait until we get confirmation of that 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 } = useWeb3ReactCore() // specifically using useWeb3React because of what this hook does
useEffect(() => {
const { ethereum } = window
if (ethereum && ethereum.on && !active && !error && !suppress) {
const handleNetworkChanged = () => {
// eat errors
activate(injected, undefined, true).catch(() => {})
}
const handleAccountsChanged = accounts => {
if (accounts.length > 0) {
// eat errors
activate(injected, undefined, true).catch(() => {})
}
}
ethereum.on('networkChanged', handleNetworkChanged)
ethereum.on('accountsChanged', handleAccountsChanged)
return () => {
ethereum.removeListener('networkChanged', handleNetworkChanged)
ethereum.removeListener('accountsChanged', handleAccountsChanged)
}
}
return () => {}
}, [active, error, suppress, activate])
}
// modified from https://usehooks.com/useDebounce/
export function useDebounce(value, delay) {
@ -51,7 +123,7 @@ export function useBodyKeyDown(targetKey, onKeyDown, suppressOnKeyDown = false)
}
export function useENSName(address) {
const { library } = useWeb3Context()
const { library } = useWeb3React()
const [ENSName, setENSName] = useState()
@ -84,7 +156,7 @@ export function useENSName(address) {
// returns null on errors
export function useContract(address, ABI, withSignerIfPossible = true) {
const { library, account } = useWeb3Context()
const { library, account } = useWeb3React()
return useMemo(() => {
try {
@ -97,7 +169,7 @@ export function useContract(address, ABI, withSignerIfPossible = true) {
// returns null on errors
export function useTokenContract(tokenAddress, withSignerIfPossible = true) {
const { library, account } = useWeb3Context()
const { library, account } = useWeb3React()
return useMemo(() => {
try {
@ -110,7 +182,7 @@ export function useTokenContract(tokenAddress, withSignerIfPossible = true) {
// returns null on errors
export function useFactoryContract(withSignerIfPossible = true) {
const { networkId, library, account } = useWeb3Context()
const { networkId, library, account } = useWeb3React()
return useMemo(() => {
try {
@ -122,7 +194,7 @@ export function useFactoryContract(withSignerIfPossible = true) {
}
export function useExchangeContract(exchangeAddress, withSignerIfPossible = true) {
const { library, account } = useWeb3Context()
const { library, account } = useWeb3React()
return useMemo(() => {
try {
@ -155,3 +227,18 @@ export function useCopyClipboard(timeout = 500) {
return [isCopied, staticCopy]
}
// modified from https://usehooks.com/usePrevious/
export function usePrevious(value) {
// The ref object is a generic container whose current property is mutable ...
// ... and can hold any value, similar to an instance property on a class
const ref = useRef()
// Store current value in ref
useEffect(() => {
ref.current = value
}, [value]) // Only re-run if value changes
// Return previous value (happens before update in useEffect above)
return ref.current
}

@ -1,9 +1,10 @@
import React from 'react'
import ReactDOM from 'react-dom'
import ReactGA from 'react-ga'
import Web3Provider from 'web3-react'
import { Web3ReactProvider, createWeb3ReactRoot } from '@web3-react/core'
import { ethers } from 'ethers'
import ThemeProvider, { GlobalStyle } from './theme'
import { NetworkContextName } from './constants'
import LocalStorageContextProvider, { Updater as LocalStorageContextUpdater } from './contexts/LocalStorage'
import ApplicationContextProvider, { Updater as ApplicationContextUpdater } from './contexts/Application'
import TransactionContextProvider, { Updater as TransactionContextUpdater } from './contexts/Transactions'
@ -11,13 +12,18 @@ import TokensContextProvider from './contexts/Tokens'
import BalancesContextProvider from './contexts/Balances'
import AllowancesContextProvider from './contexts/Allowances'
import AllBalancesContextProvider from './contexts/AllBalances'
import App from './pages/App'
import NetworkOnlyConnector from './NetworkOnlyConnector'
import InjectedConnector from './InjectedConnector'
import ThemeProvider, { GlobalStyle } from './theme'
import './i18n'
const Web3ProviderNetwork = createWeb3ReactRoot(NetworkContextName)
function getLibrary(provider) {
const library = new ethers.providers.Web3Provider(provider)
library.pollingInterval = 10000
return library
}
if (process.env.NODE_ENV === 'production') {
ReactGA.initialize('UA-128182339-1')
} else {
@ -25,10 +31,6 @@ if (process.env.NODE_ENV === 'production') {
}
ReactGA.pageview(window.location.pathname + window.location.search)
const Network = new NetworkOnlyConnector({ providerURL: process.env.REACT_APP_NETWORK_URL || '' })
const Injected = new InjectedConnector({ supportedNetworks: [Number(process.env.REACT_APP_NETWORK_ID || '1')] })
const connectors = { Injected, Network }
function ContextProviders({ children }) {
return (
<LocalStorageContextProvider>
@ -58,7 +60,8 @@ function Updaters() {
}
ReactDOM.render(
<Web3Provider connectors={connectors} libraryName="ethers.js">
<Web3ReactProvider getLibrary={getLibrary}>
<Web3ProviderNetwork getLibrary={getLibrary}>
<ContextProviders>
<Updaters />
<ThemeProvider>
@ -68,6 +71,7 @@ ReactDOM.render(
</>
</ThemeProvider>
</ContextProviders>
</Web3Provider>,
</Web3ProviderNetwork>
</Web3ReactProvider>,
document.getElementById('root')
)

@ -1,6 +1,5 @@
import React, { useReducer, useState, useCallback, useEffect, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { useWeb3Context } from 'web3-react'
import { createBrowserHistory } from 'history'
import { ethers } from 'ethers'
import ReactGA from 'react-ga'
@ -11,8 +10,7 @@ import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import OversizedPanel from '../../components/OversizedPanel'
import ContextualInfo from '../../components/ContextualInfo'
import { ReactComponent as Plus } from '../../assets/images/plus-blue.svg'
import { useExchangeContract } from '../../hooks'
import { useWeb3React, useExchangeContract } from '../../hooks'
import { brokenTokens } from '../../constants'
import { amountFormatter, calculateGasMargin } from '../../utils'
import { useTransactionAdder } from '../../contexts/Transactions'
@ -201,7 +199,7 @@ function getMarketRate(reserveETH, reserveToken, decimals, invert = false) {
export default function AddLiquidity({ params }) {
const { t } = useTranslation()
const { library, active, account } = useWeb3Context()
const { library, account, active } = useWeb3React()
// clear url of query
useEffect(() => {

@ -1,15 +1,15 @@
import React, { useState, useEffect } from 'react'
import { withRouter } from 'react-router'
import { useWeb3Context } from 'web3-react'
import { createBrowserHistory } from 'history'
import { ethers } from 'ethers'
import styled from 'styled-components'
import { useTranslation } from 'react-i18next'
import ReactGA from 'react-ga'
import { useWeb3React, useFactoryContract } from '../../hooks'
import { Button } from '../../theme'
import AddressInputPanel from '../../components/AddressInputPanel'
import OversizedPanel from '../../components/OversizedPanel'
import { useFactoryContract } from '../../hooks'
import { useTokenDetails } from '../../contexts/Tokens'
import { useTransactionAdder } from '../../contexts/Transactions'
@ -56,7 +56,8 @@ const Flex = styled.div`
function CreateExchange({ location, params }) {
const { t } = useTranslation()
const { account } = useWeb3Context()
const { account } = useWeb3React()
const factory = useFactoryContract()
const [tokenAddress, setTokenAddress] = useState({

@ -126,6 +126,7 @@ function ModeSelector({ location: { pathname }, history }) {
</LiquidityContainer>
<Modal
isOpen={modalIsOpen}
maxHeight={50}
onDismiss={() => {
setModalIsOpen(false)
}}

@ -2,17 +2,15 @@ import React, { useState, useEffect, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import ReactGA from 'react-ga'
import { createBrowserHistory } from 'history'
import { useWeb3Context } from 'web3-react'
import { ethers } from 'ethers'
import styled from 'styled-components'
import { useWeb3React, useExchangeContract } from '../../hooks'
import { Button } from '../../theme'
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import ContextualInfo from '../../components/ContextualInfo'
import OversizedPanel from '../../components/OversizedPanel'
import ArrowDown from '../../assets/svg/SVGArrowDown'
import { useExchangeContract } from '../../hooks'
import { useTransactionAdder } from '../../contexts/Transactions'
import { useTokenDetails } from '../../contexts/Tokens'
import { useAddressBalance } from '../../contexts/Balances'
@ -143,8 +141,8 @@ function calculateSlippageBounds(value) {
}
export default function RemoveLiquidity({ params }) {
const { library, account, active } = useWeb3Context()
const { t } = useTranslation()
const { library, account, active } = useWeb3React()
const addTransaction = useTransactionAdder()

@ -21,16 +21,6 @@ const mediaWidthTemplates = Object.keys(MEDIA_WIDTHS).reduce((accumulator, size)
return accumulator
}, {})
const flexColumnNoWrap = css`
display: flex;
flex-flow: column nowrap;
`
const flexRowNoWrap = css`
display: flex;
flex-flow: row nowrap;
`
const white = '#FFFFFF'
const black = '#000000'
@ -59,7 +49,7 @@ const theme = darkMode => ({
// for setting css on <html>
backgroundColor: darkMode ? '#333639' : white,
modalBackground: darkMode ? 'rgba(0,0,0,0.6)' : 'rgba(0,0,0,0.3)',
modalBackground: darkMode ? 'rgba(0,0,0,0.6)' : 'rgba(0,0,0,0.5)',
inputBackground: darkMode ? '#202124' : white,
placeholderGray: darkMode ? '#5F5F5F' : '#E1E1E1',
shadowColor: darkMode ? '#000' : '#2F80ED',
@ -92,16 +82,29 @@ const theme = darkMode => ({
warningYellow: '#FFE270',
// pink
uniswapPink: '#DC6BE5',
//green
connectedGreen: '#27AE60',
//branded
metaMaskOrange: '#E8831D',
//specific
textHover: darkMode ? theme.uniswapPink : theme.doveGray,
// connect button when loggedout
buttonFaded: darkMode ? '#DC6BE5' : '#737373',
// media queries
mediaWidth: mediaWidthTemplates,
// css snippets
flexColumnNoWrap,
flexRowNoWrap
flexColumnNoWrap: css`
display: flex;
flex-flow: column nowrap;
`,
flexRowNoWrap: css`
display: flex;
flex-flow: row nowrap;
`
})
export const GlobalStyle = createGlobalStyle`

3633
yarn.lock

File diff suppressed because it is too large Load Diff