Commit 795caac4 authored by Ian Lapham's avatar Ian Lapham Committed by Noah Zinsmeister

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
parent 116a7f38
...@@ -8,15 +8,21 @@ ...@@ -8,15 +8,21 @@
"@reach/dialog": "^0.2.8", "@reach/dialog": "^0.2.8",
"@reach/tooltip": "^0.2.0", "@reach/tooltip": "^0.2.0",
"@uniswap/sdk": "^1.0.0-beta.4", "@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", "copy-to-clipboard": "^3.2.0",
"escape-string-regexp": "^2.0.0", "escape-string-regexp": "^2.0.0",
"history": "^4.9.0",
"ethers": "^4.0.36", "ethers": "^4.0.36",
"history": "^4.9.0",
"i18next": "^15.0.9", "i18next": "^15.0.9",
"i18next-browser-languagedetector": "^3.0.1", "i18next-browser-languagedetector": "^3.0.1",
"i18next-xhr-backend": "^2.0.1", "i18next-xhr-backend": "^2.0.1",
"jazzicon": "^1.5.0", "jazzicon": "^1.5.0",
"polished": "^3.3.2", "polished": "^3.3.2",
"qrcode.react": "^0.9.3",
"react": "^16.8.6", "react": "^16.8.6",
"react-device-detect": "^1.6.2", "react-device-detect": "^1.6.2",
"react-dom": "^16.8.6", "react-dom": "^16.8.6",
...@@ -25,10 +31,10 @@ ...@@ -25,10 +31,10 @@
"react-i18next": "^10.7.0", "react-i18next": "^10.7.0",
"react-router-dom": "^5.0.0", "react-router-dom": "^5.0.0",
"react-scripts": "^3.0.1", "react-scripts": "^3.0.1",
"react-spring": "^8.0.20", "react-spring": "^8.0.27",
"react-switch": "^5.0.1", "react-switch": "^5.0.1",
"styled-components": "^4.2.0", "react-use-gesture": "^6.0.14",
"web3-react": "5.0.5" "styled-components": "^4.2.0"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",
......
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]
})
}
}
}
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
}
}
<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>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect width="24" height="24" fill="url(#pattern0)"/>
<defs>
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image0" transform="scale(0.00416667)"/>
</pattern>
<image id="image0" width="240" height="240" xlink:href="data:image/png;base64,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"/>
</defs>
</svg>
<svg width="24" height="16" viewBox="0 0 24 16" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect width="24" height="15.0867" fill="url(#pattern0)"/>
<defs>
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image0" transform="translate(-0.00968744) scale(0.00339792 0.00540541)"/>
</pattern>
<image id="image0" width="300" height="185" xlink:href="data:image/png;base64,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"/>
</defs>
</svg>
<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>
\ No newline at end of file \ No newline at end of file
import React from 'react' import React from 'react'
import styled, { keyframes } from 'styled-components' import styled, { keyframes } from 'styled-components'
import { useWeb3Context } from 'web3-react' import { Check } from 'react-feather'
import Copy from './Copy'
import { useWeb3React } from '../../hooks'
import { getEtherscanLink } from '../../utils' import { getEtherscanLink } from '../../utils'
import { Link, Spinner } from '../../theme' import { Link, Spinner } from '../../theme'
import Copy from './Copy'
import Circle from '../../assets/images/circle.svg' import Circle from '../../assets/images/circle.svg'
import { Check } from 'react-feather'
import { transparentize } from 'polished' import { transparentize } from 'polished'
...@@ -75,17 +75,17 @@ const ButtonWrapper = styled.div` ...@@ -75,17 +75,17 @@ const ButtonWrapper = styled.div`
` `
export default function Transaction({ hash, pending }) { export default function Transaction({ hash, pending }) {
const { networkId } = useWeb3Context() const { chainId } = useWeb3React()
return ( return (
<TransactionWrapper key={hash}> <TransactionWrapper key={hash}>
<TransactionStatusWrapper> <TransactionStatusWrapper>
<Link href={getEtherscanLink(networkId, hash, 'transaction')}>{hash} </Link> <Link href={getEtherscanLink(chainId, hash, 'transaction')}>{hash} </Link>
<Copy toCopy={hash} /> <Copy toCopy={hash} />
</TransactionStatusWrapper> </TransactionStatusWrapper>
{pending ? ( {pending ? (
<ButtonWrapper pending={pending}> <ButtonWrapper pending={pending}>
<Link href={getEtherscanLink(networkId, hash, 'transaction')}> <Link href={getEtherscanLink(chainId, hash, 'transaction')}>
<TransactionState pending={pending}> <TransactionState pending={pending}>
<Spinner src={Circle} id="pending" /> <Spinner src={Circle} id="pending" />
<TransactionStatusText>Pending</TransactionStatusText> <TransactionStatusText>Pending</TransactionStatusText>
...@@ -94,7 +94,7 @@ export default function Transaction({ hash, pending }) { ...@@ -94,7 +94,7 @@ export default function Transaction({ hash, pending }) {
</ButtonWrapper> </ButtonWrapper>
) : ( ) : (
<ButtonWrapper pending={pending}> <ButtonWrapper pending={pending}>
<Link href={getEtherscanLink(networkId, hash, 'transaction')}> <Link href={getEtherscanLink(chainId, hash, 'transaction')}>
<TransactionState pending={pending}> <TransactionState pending={pending}>
<Check size="16" /> <Check size="16" />
<TransactionStatusText>Confirmed</TransactionStatusText> <TransactionStatusText>Confirmed</TransactionStatusText>
......
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>
)}
</>
)
}
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useWeb3Context } from 'web3-react'
import { transparentize } from 'polished' import { transparentize } from 'polished'
import { isAddress } from '../../utils' import { isAddress } from '../../utils'
import { useDebounce } from '../../hooks' import { useWeb3React, useDebounce } from '../../hooks'
const InputPanel = styled.div` const InputPanel = styled.div`
${({ theme }) => theme.flexColumnNoWrap} ${({ theme }) => theme.flexColumnNoWrap}
...@@ -73,7 +72,7 @@ const Input = styled.input` ...@@ -73,7 +72,7 @@ const Input = styled.input`
export default function AddressInputPanel({ title, initialInput = '', onChange = () => {}, onError = () => {} }) { export default function AddressInputPanel({ title, initialInput = '', onChange = () => {}, onError = () => {} }) {
const { t } = useTranslation() const { t } = useTranslation()
const { library } = useWeb3Context() const { library } = useWeb3React()
const [input, setInput] = useState(initialInput.address ? initialInput.address : '') const [input, setInput] = useState(initialInput.address ? initialInput.address : '')
......
...@@ -3,7 +3,6 @@ import { Link } from 'react-router-dom' ...@@ -3,7 +3,6 @@ import { Link } from 'react-router-dom'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { ethers } from 'ethers' import { ethers } from 'ethers'
import { BigNumber } from '@uniswap/sdk' import { BigNumber } from '@uniswap/sdk'
import { useWeb3Context } from 'web3-react'
import styled from 'styled-components' import styled from 'styled-components'
import escapeStringRegex from 'escape-string-regexp' import escapeStringRegex from 'escape-string-regexp'
import { darken } from 'polished' import { darken } from 'polished'
...@@ -12,7 +11,7 @@ import '@reach/tooltip/styles.css' ...@@ -12,7 +11,7 @@ import '@reach/tooltip/styles.css'
import { isMobile } from 'react-device-detect' import { isMobile } from 'react-device-detect'
import { BorderlessInput } from '../../theme' import { BorderlessInput } from '../../theme'
import { useTokenContract } from '../../hooks' import { useWeb3React, useTokenContract } from '../../hooks'
import { isAddress, calculateGasMargin, formatToUsd, formatTokenBalance, formatEthBalance } from '../../utils' import { isAddress, calculateGasMargin, formatToUsd, formatTokenBalance, formatEthBalance } from '../../utils'
import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg' import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg'
import Modal from '../Modal' import Modal from '../Modal'
...@@ -225,7 +224,10 @@ const TokenModalRow = styled.div` ...@@ -225,7 +224,10 @@ const TokenModalRow = styled.div`
background-color: ${({ theme }) => theme.tokenRowHover}; 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` const TokenRowLeft = styled.div`
...@@ -297,7 +299,7 @@ export default function CurrencyInputPanel({ ...@@ -297,7 +299,7 @@ export default function CurrencyInputPanel({
const allTokens = useAllTokenDetails() const allTokens = useAllTokenDetails()
const { account } = useWeb3Context() const { account } = useWeb3React()
const userTokenBalance = useAddressBalance(account, selectedTokenAddress) const userTokenBalance = useAddressBalance(account, selectedTokenAddress)
...@@ -426,7 +428,6 @@ export default function CurrencyInputPanel({ ...@@ -426,7 +428,6 @@ export default function CurrencyInputPanel({
{!disableTokenSelect && ( {!disableTokenSelect && (
<CurrencySelectModal <CurrencySelectModal
isOpen={modalIsOpen} isOpen={modalIsOpen}
// isOpen={true}
onDismiss={() => { onDismiss={() => {
setModalIsOpen(false) setModalIsOpen(false)
}} }}
...@@ -446,7 +447,7 @@ function CurrencySelectModal({ isOpen, onDismiss, onTokenSelect, allBalances }) ...@@ -446,7 +447,7 @@ function CurrencySelectModal({ isOpen, onDismiss, onTokenSelect, allBalances })
const allTokens = useAllTokenDetails() const allTokens = useAllTokenDetails()
const { account } = useWeb3Context() const { account } = useWeb3React()
// BigNumber.js instance // BigNumber.js instance
const ethPrice = useUSDPrice() const ethPrice = useUSDPrice()
...@@ -606,6 +607,7 @@ function CurrencySelectModal({ isOpen, onDismiss, onTokenSelect, allBalances }) ...@@ -606,6 +607,7 @@ function CurrencySelectModal({ isOpen, onDismiss, onTokenSelect, allBalances })
isOpen={isOpen} isOpen={isOpen}
onDismiss={clearInputAndDismiss} onDismiss={clearInputAndDismiss}
minHeight={60} minHeight={60}
maxHeight={50}
initialFocusRef={isMobile ? undefined : inputRef} initialFocusRef={isMobile ? undefined : inputRef}
> >
<TokenModal> <TokenModal>
......
import React, { useState, useReducer, useEffect } from 'react' import React, { useState, useReducer, useEffect } from 'react'
import ReactGA from 'react-ga' import ReactGA from 'react-ga'
import { createBrowserHistory } from 'history' import { createBrowserHistory } from 'history'
import { useTranslation } from 'react-i18next'
import { useWeb3Context } from 'web3-react'
import { ethers } from 'ethers' import { ethers } from 'ethers'
import styled from 'styled-components' import styled from 'styled-components'
import { useTranslation } from 'react-i18next'
import { Button } from '../../theme' import { Button } from '../../theme'
import { useWeb3React } from '../../hooks'
import CurrencyInputPanel from '../CurrencyInputPanel' import CurrencyInputPanel from '../CurrencyInputPanel'
import AddressInputPanel from '../AddressInputPanel' import AddressInputPanel from '../AddressInputPanel'
import OversizedPanel from '../OversizedPanel' import OversizedPanel from '../OversizedPanel'
...@@ -21,6 +19,7 @@ import { useTransactionAdder } from '../../contexts/Transactions' ...@@ -21,6 +19,7 @@ import { useTransactionAdder } from '../../contexts/Transactions'
import { useAddressBalance, useExchangeReserves } from '../../contexts/Balances' import { useAddressBalance, useExchangeReserves } from '../../contexts/Balances'
import { useFetchAllBalances } from '../../contexts/AllBalances' import { useFetchAllBalances } from '../../contexts/AllBalances'
import { useAddressAllowance } from '../../contexts/Allowances' import { useAddressAllowance } from '../../contexts/Allowances'
import { useWalletModalToggle } from '../../contexts/Application'
const INPUT = 0 const INPUT = 0
const OUTPUT = 1 const OUTPUT = 1
...@@ -246,7 +245,7 @@ function getMarketRate( ...@@ -246,7 +245,7 @@ function getMarketRate(
export default function ExchangePage({ initialCurrency, sending = false, params }) { export default function ExchangePage({ initialCurrency, sending = false, params }) {
const { t } = useTranslation() const { t } = useTranslation()
const { account } = useWeb3Context() const { account, error } = useWeb3React()
const addTransaction = useTransactionAdder() const addTransaction = useTransactionAdder()
...@@ -294,7 +293,10 @@ export default function ExchangePage({ initialCurrency, sending = false, params ...@@ -294,7 +293,10 @@ export default function ExchangePage({ initialCurrency, sending = false, params
const { independentValue, dependentValue, independentField, inputCurrency, outputCurrency } = swapState 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() const [recipientError, setRecipientError] = useState()
// get swap type from the currency types // get swap type from the currency types
...@@ -416,7 +418,10 @@ export default function ExchangePage({ initialCurrency, sending = false, params ...@@ -416,7 +418,10 @@ export default function ExchangePage({ initialCurrency, sending = false, params
throw Error() throw Error()
} }
dispatchSwapState({ type: 'UPDATE_DEPENDENT', payload: calculatedDependentValue }) dispatchSwapState({
type: 'UPDATE_DEPENDENT',
payload: calculatedDependentValue
})
} catch { } catch {
setIndependentError(t('insufficientLiquidity')) setIndependentError(t('insufficientLiquidity'))
} }
...@@ -439,7 +444,10 @@ export default function ExchangePage({ initialCurrency, sending = false, params ...@@ -439,7 +444,10 @@ export default function ExchangePage({ initialCurrency, sending = false, params
throw Error() throw Error()
} }
dispatchSwapState({ type: 'UPDATE_DEPENDENT', payload: calculatedDependentValue }) dispatchSwapState({
type: 'UPDATE_DEPENDENT',
payload: calculatedDependentValue
})
} catch { } catch {
setIndependentError(t('insufficientLiquidity')) setIndependentError(t('insufficientLiquidity'))
} }
...@@ -469,7 +477,10 @@ export default function ExchangePage({ initialCurrency, sending = false, params ...@@ -469,7 +477,10 @@ export default function ExchangePage({ initialCurrency, sending = false, params
if (calculatedDependentValue.lte(ethers.constants.Zero)) { if (calculatedDependentValue.lte(ethers.constants.Zero)) {
throw Error() throw Error()
} }
dispatchSwapState({ type: 'UPDATE_DEPENDENT', payload: calculatedDependentValue }) dispatchSwapState({
type: 'UPDATE_DEPENDENT',
payload: calculatedDependentValue
})
} else { } else {
const intermediateValue = calculateEtherTokenInputFromOutput(amount, reserveETHSecond, reserveTokenSecond) const intermediateValue = calculateEtherTokenInputFromOutput(amount, reserveETHSecond, reserveTokenSecond)
if (intermediateValue.lte(ethers.constants.Zero)) { if (intermediateValue.lte(ethers.constants.Zero)) {
...@@ -483,7 +494,10 @@ export default function ExchangePage({ initialCurrency, sending = false, params ...@@ -483,7 +494,10 @@ export default function ExchangePage({ initialCurrency, sending = false, params
if (calculatedDependentValue.lte(ethers.constants.Zero)) { if (calculatedDependentValue.lte(ethers.constants.Zero)) {
throw Error() throw Error()
} }
dispatchSwapState({ type: 'UPDATE_DEPENDENT', payload: calculatedDependentValue }) dispatchSwapState({
type: 'UPDATE_DEPENDENT',
payload: calculatedDependentValue
})
} }
} catch { } catch {
setIndependentError(t('insufficientLiquidity')) setIndependentError(t('insufficientLiquidity'))
...@@ -621,7 +635,10 @@ export default function ExchangePage({ initialCurrency, sending = false, params ...@@ -621,7 +635,10 @@ export default function ExchangePage({ initialCurrency, sending = false, params
} }
const estimatedGasLimit = await estimate(...args, { value }) 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) addTransaction(response)
}) })
} }
...@@ -630,6 +647,8 @@ export default function ExchangePage({ initialCurrency, sending = false, params ...@@ -630,6 +647,8 @@ export default function ExchangePage({ initialCurrency, sending = false, params
const allBalances = useFetchAllBalances() const allBalances = useFetchAllBalances()
const toggleWalletModal = useWalletModalToggle()
return ( return (
<> <>
<CurrencyInputPanel <CurrencyInputPanel
...@@ -643,16 +662,25 @@ export default function ExchangePage({ initialCurrency, sending = false, params ...@@ -643,16 +662,25 @@ export default function ExchangePage({ initialCurrency, sending = false, params
if (valueToSet.gt(ethers.constants.Zero)) { if (valueToSet.gt(ethers.constants.Zero)) {
dispatchSwapState({ dispatchSwapState({
type: 'UPDATE_INDEPENDENT', type: 'UPDATE_INDEPENDENT',
payload: { value: amountFormatter(valueToSet, inputDecimals, inputDecimals, false), field: INPUT } payload: {
value: amountFormatter(valueToSet, inputDecimals, inputDecimals, false),
field: INPUT
}
}) })
} }
} }
}} }}
onCurrencySelected={inputCurrency => { onCurrencySelected={inputCurrency => {
dispatchSwapState({ type: 'SELECT_CURRENCY', payload: { currency: inputCurrency, field: INPUT } }) dispatchSwapState({
type: 'SELECT_CURRENCY',
payload: { currency: inputCurrency, field: INPUT }
})
}} }}
onValueChange={inputValue => { onValueChange={inputValue => {
dispatchSwapState({ type: 'UPDATE_INDEPENDENT', payload: { value: inputValue, field: INPUT } }) dispatchSwapState({
type: 'UPDATE_INDEPENDENT',
payload: { value: inputValue, field: INPUT }
})
}} }}
showUnlock={showUnlock} showUnlock={showUnlock}
selectedTokens={[inputCurrency, outputCurrency]} selectedTokens={[inputCurrency, outputCurrency]}
...@@ -678,10 +706,16 @@ export default function ExchangePage({ initialCurrency, sending = false, params ...@@ -678,10 +706,16 @@ export default function ExchangePage({ initialCurrency, sending = false, params
description={outputValueFormatted && independentField === INPUT ? estimatedText : ''} description={outputValueFormatted && independentField === INPUT ? estimatedText : ''}
extraText={outputBalanceFormatted && formatBalance(outputBalanceFormatted)} extraText={outputBalanceFormatted && formatBalance(outputBalanceFormatted)}
onCurrencySelected={outputCurrency => { onCurrencySelected={outputCurrency => {
dispatchSwapState({ type: 'SELECT_CURRENCY', payload: { currency: outputCurrency, field: OUTPUT } }) dispatchSwapState({
type: 'SELECT_CURRENCY',
payload: { currency: outputCurrency, field: OUTPUT }
})
}} }}
onValueChange={outputValue => { onValueChange={outputValue => {
dispatchSwapState({ type: 'UPDATE_INDEPENDENT', payload: { value: outputValue, field: OUTPUT } }) dispatchSwapState({
type: 'UPDATE_INDEPENDENT',
payload: { value: outputValue, field: OUTPUT }
})
}} }}
selectedTokens={[inputCurrency, outputCurrency]} selectedTokens={[inputCurrency, outputCurrency]}
selectedTokenAddress={outputCurrency} selectedTokenAddress={outputCurrency}
...@@ -753,11 +787,14 @@ export default function ExchangePage({ initialCurrency, sending = false, params ...@@ -753,11 +787,14 @@ export default function ExchangePage({ initialCurrency, sending = false, params
/> />
<Flex> <Flex>
<Button <Button
disabled={!isValid || customSlippageError === 'invalid'} disabled={!account && !error ? false : !isValid || customSlippageError === 'invalid'}
onClick={onSwap} onClick={account && !error ? onSwap : toggleWalletModal}
warning={highSlippageWarning || customSlippageError === 'warning'} warning={highSlippageWarning || customSlippageError === 'warning'}
loggedOut={!account}
> >
{sending {!account
? 'Connect to a Wallet'
: sending
? highSlippageWarning || customSlippageError === 'warning' ? highSlippageWarning || customSlippageError === 'warning'
? t('sendAnyway') ? t('sendAnyway')
: t('send') : t('send')
......
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} />
}
import React from 'react' import React from 'react'
import styled, { css } from 'styled-components' 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 { DialogOverlay, DialogContent } from '@reach/dialog'
import { isMobile } from 'react-device-detect'
import '@reach/dialog/styles.css' import '@reach/dialog/styles.css'
import { transparentize } from 'polished' import { transparentize } from 'polished'
import { useGesture } from 'react-use-gesture'
const AnimatedDialogOverlay = animated(DialogOverlay) const AnimatedDialogOverlay = animated(DialogOverlay)
const WrappedDialogOverlay = ({ suppressClassNameWarning, ...rest }) => <AnimatedDialogOverlay {...rest} /> const WrappedDialogOverlay = ({ suppressClassNameWarning, mobile, ...rest }) => <AnimatedDialogOverlay {...rest} />
const StyledDialogOverlay = styled(WrappedDialogOverlay).attrs({ const StyledDialogOverlay = styled(WrappedDialogOverlay).attrs({
suppressClassNameWarning: true suppressClassNameWarning: true
})` })`
...@@ -15,34 +19,71 @@ const StyledDialogOverlay = styled(WrappedDialogOverlay).attrs({ ...@@ -15,34 +19,71 @@ const StyledDialogOverlay = styled(WrappedDialogOverlay).attrs({
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background-color: ${({ theme }) => theme.modalBackground}; 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)` const StyledDialogContent = styled(FilteredDialogContent)`
&[data-reach-dialog-content] { &[data-reach-dialog-content] {
margin: 0 0 2rem 0; margin: 0 0 2rem 0;
border: 1px solid ${({ theme }) => theme.concreteGray}; border: 1px solid ${({ theme }) => theme.concreteGray};
background-color: ${({ theme }) => theme.inputBackground}; background-color: ${({ theme }) => theme.inputBackground};
box-shadow: 0 4px 8px 0 ${({ theme }) => transparentize(0.95, theme.shadowColor)}; box-shadow: 0 4px 8px 0 ${({ theme }) => transparentize(0.95, theme.shadowColor)};
${({ theme }) => theme.mediaWidth.upToMedium`margin: 0;`};
padding: 0px; padding: 0px;
width: 50vw; width: 50vw;
max-width: 650px; max-width: 650px;
${({ theme }) => theme.mediaWidth.upToMedium`width: 65vw;`} ${({ maxHeight }) =>
${({ theme }) => theme.mediaWidth.upToSmall`width: 85vw;`} maxHeight &&
max-height: 50vh; css`
max-height: ${maxHeight}vh;
`}
${({ minHeight }) => ${({ minHeight }) =>
minHeight && minHeight &&
css` css`
min-height: ${minHeight}vh; min-height: ${minHeight}vh;
`} `}
${({ theme }) => theme.mediaWidth.upToMedium`max-height: 65vh;`}
${({ theme }) => theme.mediaWidth.upToSmall`max-height: 80vh;`}
display: flex; display: flex;
overflow: hidden; overflow: hidden;
border-radius: 10px; 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` ...@@ -54,23 +95,97 @@ const HiddenCloseButton = styled.button`
border: none; 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, { const transitions = useTransition(isOpen, null, {
config: { duration: 150 }, config: { duration: 200 },
from: { opacity: 0 }, from: { opacity: 0 },
enter: { opacity: 1 }, enter: { opacity: 1 },
leave: { opacity: 0 } leave: { opacity: 0 }
}) })
return transitions.map( const [{ xy }, set] = useSpring(() => ({ xy: [0, 0] }))
({ item, key, props }) => const bind = useGesture({
item && ( onDrag: state => {
<StyledDialogOverlay key={key} style={props} onDismiss={onDismiss} initialFocusRef={initialFocusRef}> let velocity = state.velocity
<StyledDialogContent hidden={true} minHeight={minHeight}> if (velocity < 1) {
<HiddenCloseButton onClick={onDismiss} /> velocity = 1
{children} }
</StyledDialogContent> if (velocity > 8) {
</StyledDialogOverlay> 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}
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' ...@@ -4,9 +4,7 @@ import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
import { transparentize, darken } from 'polished' import { transparentize, darken } from 'polished'
import { Link } from '../../theme/components' import { useWeb3React, useBodyKeyDown } from '../../hooks'
import { useBodyKeyDown } from '../../hooks'
import { useAddressBalance } from '../../contexts/Balances' import { useAddressBalance } from '../../contexts/Balances'
import { isAddress } from '../../utils' import { isAddress } from '../../utils'
import { import {
...@@ -14,7 +12,7 @@ import { ...@@ -14,7 +12,7 @@ import {
useSaiHolderMessageManager, useSaiHolderMessageManager,
useGeneralDaiMessageManager useGeneralDaiMessageManager
} from '../../contexts/LocalStorage' } from '../../contexts/LocalStorage'
import { useWeb3Context } from 'web3-react' import { Link } from '../../theme/components'
const tabOrder = [ const tabOrder = [
{ {
...@@ -102,7 +100,7 @@ const WarningHeader = styled.div` ...@@ -102,7 +100,7 @@ const WarningHeader = styled.div`
const WarningFooter = styled.div` const WarningFooter = styled.div`
margin-top: 10px; margin-top: 10px;
font-size: 10px; font-size: 10px;
textdecoration: italic; text-decoration: italic;
color: ${({ theme }) => theme.greyText}; color: ${({ theme }) => theme.greyText};
` `
...@@ -164,7 +162,7 @@ function NavigationTabs({ location: { pathname }, history }) { ...@@ -164,7 +162,7 @@ function NavigationTabs({ location: { pathname }, history }) {
const [showSaiHolderMessage, dismissSaiHolderMessage] = useSaiHolderMessageManager() const [showSaiHolderMessage, dismissSaiHolderMessage] = useSaiHolderMessageManager()
const { account } = useWeb3Context() const { account } = useWeb3React()
const daiBalance = useAddressBalance(account, isAddress('0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359')) const daiBalance = useAddressBalance(account, isAddress('0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359'))
......
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>
)
}
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
}
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>
)
}
import React from 'react' import React, { useState, useEffect } from 'react'
import styled, { css } from 'styled-components' import styled from 'styled-components'
import { useWeb3Context } from 'web3-react' 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 Modal from '../Modal'
import AccountDetails from '../AccountDetails'
import { getEtherscanLink } from '../../utils' 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 { 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` const Wrapper = styled.div`
${({ theme }) => theme.flexColumnNoWrap}
margin: 0; margin: 0;
padding: 0; padding: 0;
width: 100%; width: 100%;
${({ theme }) => theme.flexColumnNoWrap} background-color: ${({ theme }) => theme.backgroundColor};
` `
const UpperSection = styled.div` 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; padding: 2rem;
${({ theme }) => theme.mediaWidth.upToMedium`padding: 1rem`};
`
const UpperSection = styled.div`
position: relative;
background-color: ${({ theme }) => theme.concreteGray}; background-color: ${({ theme }) => theme.concreteGray};
h5 { h5 {
...@@ -37,172 +78,229 @@ const UpperSection = styled.div` ...@@ -37,172 +78,229 @@ const UpperSection = styled.div`
} }
` `
const YourAccount = styled.div` const Blurb = styled.div`
h5 { ${({ theme }) => theme.flexRowNoWrap}
margin: 0 0 1rem 0; align-items: center;
font-weight: 400; justify-content: center;
color: ${({ theme }) => theme.doveGray}; margin-top: 2rem;
} ${({ theme }) => theme.mediaWidth.upToMedium`
margin: 1rem;
font-size: 12px;
`};
`
h4 { const OptionGrid = styled.div`
margin: 0; display: grid;
font-weight: 500; grid-template-columns: 1fr 1fr;
grid-gap: 10px;
${({ theme }) => theme.mediaWidth.upToMedium`
grid-template-columns: 1fr;
grid-gap: 10px;
`};
`
const HoverText = styled.div`
:hover {
cursor: pointer;
} }
` `
const LowerSection = styled.div` const WALLET_VIEWS = {
${({ theme }) => theme.flexColumnNoWrap} OPTIONS: 'options',
padding: 2rem; ACCOUNT: 'account',
flex-grow: 1; WALLET_CONNECT: 'walletConnect'
overflow: auto; }
h5 { export default function WalletModal({ pendingTransactions, confirmedTransactions, ENSName }) {
margin: 0; const { active, account, connector, activate, error } = useWeb3React()
font-weight: 400;
color: ${({ theme }) => theme.doveGray};
}
div:last-child { const [walletView, setWalletView] = useState(WALLET_VIEWS.ACCOUNT)
/* margin-bottom: 0; */
}
`
const AccountControl = styled.div` const walletModalOpen = useWalletModalOpen()
${({ theme }) => theme.flexRowNoWrap} const toggleWalletModal = useWalletModalToggle()
align-items: center;
min-width: 0;
${({ 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`)};
a:hover {
text-decoration: underline;
}
a { // always reset to account view
min-width: 0; useEffect(() => {
overflow: hidden; if (walletModalOpen) {
text-overflow: ellipsis; setWalletView(WALLET_VIEWS.ACCOUNT)
white-space: nowrap; }
} }, [walletModalOpen])
`
const TransactionListWrapper = styled.div` // set up uri listener for walletconnect
${({ theme }) => theme.flexColumnNoWrap} /* margin: 0 0 1rem 0; */ 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)
}
}, [])
const StyledLink = styled(Link)` // close modal when a connection is successful
color: ${({ hasENS, isENS, theme }) => (hasENS ? (isENS ? theme.royalBlue : theme.doveGray) : theme.royalBlue)}; 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])
// function getErrorMessage(event) { // get wallets user can switch too, depending on device/browser
// switch (event.code) { function getOptions() {
// case InjectedConnector.errorCodes.ETHEREUM_ACCESS_DENIED: { const isMetamask = window.ethereum && window.ethereum.isMetaMask
// return 'Permission Required'
// } if (isMobile && !window.web3 && !window.ethereum) {
// case InjectedConnector.errorCodes.UNLOCK_REQUIRED: { return Object.keys(MOBILE_DEEP_LINKS).map(key => {
// return 'Account Unlock Required' const option = MOBILE_DEEP_LINKS[key]
// } return (
// case InjectedConnector.errorCodes.NO_WEB3: { <Option
// return 'Not a Web3 Browser' key={key}
// } color={option.color}
// default: { header={option.name}
// return 'Connection Error' link={option.href}
// } subheader={option.description}
// } icon={require('../../assets/images/' + option.iconName)}
// } />
)
export default function WalletModal({ isOpen, error, onDismiss, pendingTransactions, confirmedTransactions, ENSName }) { })
const { account, networkId } = useWeb3Context() } else {
return Object.keys(SUPPORTED_WALLETS).map(key => {
function renderTransactions(transactions, pending) { const option = SUPPORTED_WALLETS[key]
return (
<TransactionListWrapper> // don't show the option we're currently connected to
{transactions.map((hash, i) => { if (option.connector === connector) {
return <Transaction key={i} hash={hash} pending={pending} /> return null
})} }
</TransactionListWrapper>
)
}
function wrappedOnDismiss() { if (option.connector === injected) {
onDismiss() // don't show injected if there's no injected provider
if (!(window.web3 || window.ethereum)) {
if (option.name === 'MetaMask') {
return (
<Option
key={key}
color={'#E8831D'}
header={'Install Metamask'}
subheader={'Easy to use browser extension.'}
link={'https://metamask.io/'}
icon={MetamaskIcon}
/>
)
} else {
return null //dont want to return install twice
}
}
// don't return metamask if injected provider isn't metamask
else if (option.name === 'MetaMask' && !isMetamask) {
return null
}
// likewise for generic
else if (option.name === 'Injected' && isMetamask) {
return null
}
}
return (
<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 getWalletDisplay() { function getModalContent() {
if (error) { if (error) {
return ( return (
<> <UpperSection>
<UpperSection> <CloseIcon onClick={toggleWalletModal}>
<h4>Wrong Network</h4> <CloseColor alt={'close icon'} />
<h5>Please connect to the main Ethereum network.</h5> </CloseIcon>
</UpperSection> <HeaderRow>{error instanceof UnsupportedChainIdError ? 'Wrong Network' : 'Error connecting'}</HeaderRow>
</> <ContentWrapper>
) {error instanceof UnsupportedChainIdError ? (
} else if (account) { <h5>Please connect to the main Ethereum network.</h5>
return ( ) : (
<> 'Error connecting. Try refreshing the page.'
<UpperSection> )}
<YourAccount> </ContentWrapper>
<h5>Your Account</h5> </UpperSection>
{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>
)}
</>
) )
} else { }
if (account && walletView === WALLET_VIEWS.ACCOUNT) {
return ( return (
<> <AccountDetails
<UpperSection> toggleWalletModal={toggleWalletModal}
<h4>No Ethereum account found</h4> pendingTransactions={pendingTransactions}
<h5>Please visit this page in a Web3 enabled browser.</h5> confirmedTransactions={confirmedTransactions}
<h5> ENSName={ENSName}
<Link href={'https://ethereum.org/use/#_3-what-is-a-wallet-and-which-one-should-i-use'}> openOptions={() => setWalletView(WALLET_VIEWS.OPTIONS)}
Learn more />
</Link>
</h5>
</UpperSection>
</>
) )
} }
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 ( return (
<Modal isOpen={isOpen} onDismiss={wrappedOnDismiss} minHeight={null}> <Modal
<Wrapper>{getWalletDisplay()}</Wrapper> style={{ userSelect: 'none' }}
isOpen={walletModalOpen}
onDismiss={toggleWalletModal}
minHeight={null}
maxHeight={90}
>
<Wrapper>{getModalContent()}</Wrapper>
</Modal> </Modal>
) )
} }
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react'
import { useWeb3Context, Connectors } from 'web3-react' import { useWeb3React } from '@web3-react/core'
import styled from 'styled-components' import styled from 'styled-components'
import { ethers } from 'ethers'
import { useTranslation } from 'react-i18next' 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 { Spinner } from '../../theme'
import Circle from '../../assets/images/circle.svg' import Circle from '../../assets/images/circle.svg'
import { NetworkContextName } from '../../constants'
const { Connector } = Connectors
const MessageWrapper = styled.div` const MessageWrapper = styled.div`
display: flex; display: flex;
...@@ -31,82 +30,73 @@ const SpinnerWrapper = styled(Spinner)` ...@@ -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 }) { export default function Web3ReactManager({ children }) {
const { t } = useTranslation() const { t } = useTranslation()
const { active, error, setConnector, setError } = useWeb3Context() const { active } = useWeb3React()
// control whether or not we render the error, after parsing const { active: networkActive, error: networkError, activate: activateNetwork } = useWeb3React(NetworkContextName)
const blockRender = error && error.code && error.code === Connector.errorCodes.UNSUPPORTED_NETWORK
// 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 (triedEager && !networkActive && !networkError && !active) {
activateNetwork(network)
}
}, [triedEager, networkActive, networkError, activateNetwork, active])
// 'pause' the network connector if we're ever connected to an account and it's active
useEffect(() => { useEffect(() => {
if (!active && !error) { if (active && networkActive) {
if (window.ethereum || window.web3) { network.pause()
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)
})
}
})
}
} else {
setConnector('Network', { suppressAndThrowErrors: true }).catch(error => {
setError(error)
})
}
} }
}) }, [active, networkActive])
// parse the error // 'resume' the network connector if we're ever not connected to an account and it's active
useEffect(() => { useEffect(() => {
if (error) { if (!active && networkActive) {
// if the user changes to the wrong network, unset the connector network.resume()
if (error.code === Connector.errorCodes.UNSUPPORTED_NETWORK) {
setConnector('Network', { suppressAndThrowErrors: true }).catch(error => {
setError(error)
})
}
} }
}) }, [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) const [showLoader, setShowLoader] = useState(false)
useEffect(() => { useEffect(() => {
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
setShowLoader(true) setShowLoader(true)
}, 600) }, 600)
return () => { return () => {
clearTimeout(timeout) clearTimeout(timeout)
} }
}, []) }, [])
if (blockRender) { // on page load, do nothing until we've tried to connect to the injected connector
if (!triedEager) {
return null 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 ( return (
<MessageWrapper> <MessageWrapper>
<Message>{t('unknownError')}</Message> <Message>{t('unknownError')}</Message>
</MessageWrapper> </MessageWrapper>
) )
} else if (!active) { }
// if neither context is active, spin
if (!active && !networkActive) {
return showLoader ? ( return showLoader ? (
<MessageWrapper> <MessageWrapper>
<SpinnerWrapper src={Circle} /> <SpinnerWrapper src={Circle} />
</MessageWrapper> </MessageWrapper>
) : null ) : null
} else {
return children
} }
return children
} }
import React, { useReducer, useEffect, useRef } from 'react' import React from 'react'
import styled from 'styled-components' import styled, { css } from 'styled-components'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useWeb3Context, Connectors } from 'web3-react' import { useWeb3React, UnsupportedChainIdError } from '@web3-react/core'
import { darken, transparentize } from 'polished' import { darken, transparentize } from 'polished'
import Jazzicon from 'jazzicon'
import { ethers } from 'ethers'
import { Activity } from 'react-feather' import { Activity } from 'react-feather'
import { shortenAddress } from '../../utils' import { shortenAddress } from '../../utils'
import { useENSName } from '../../hooks' import { useENSName } from '../../hooks'
import WalletModal from '../WalletModal' import WalletModal from '../WalletModal'
import { useAllTransactions } from '../../contexts/Transactions' import { useAllTransactions } from '../../contexts/Transactions'
import { useWalletModalToggle } from '../../contexts/Application'
import { Spinner } from '../../theme' import { Spinner } from '../../theme'
import Circle from '../../assets/images/circle.svg' import Circle from '../../assets/images/circle.svg'
import { injected, walletconnect, walletlink } from '../../connectors'
const { Connector } = 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` const Web3StatusGeneric = styled.button`
${({ theme }) => theme.flexRowNoWrap} ${({ theme }) => theme.flexRowNoWrap}
...@@ -42,15 +44,30 @@ const Web3StatusError = styled(Web3StatusGeneric)` ...@@ -42,15 +44,30 @@ const Web3StatusError = styled(Web3StatusGeneric)`
` `
const Web3StatusConnect = styled(Web3StatusGeneric)` const Web3StatusConnect = styled(Web3StatusGeneric)`
background-color: ${({ theme }) => theme.royalBlue}; background-color: transparent;
border: 1px solid ${({ theme }) => theme.royalBlue}; border: 1px solid ${({ theme }) => theme.royalBlue};
color: ${({ theme }) => theme.white}; color: ${({ theme }) => theme.royalBlue};
font-weight: 500; font-weight: 500;
:hover, :hover,
:focus { :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)` const Web3StatusConnected = styled(Web3StatusGeneric)`
...@@ -59,16 +76,13 @@ const Web3StatusConnected = styled(Web3StatusGeneric)` ...@@ -59,16 +76,13 @@ const Web3StatusConnected = styled(Web3StatusGeneric)`
color: ${({ pending, theme }) => (pending ? theme.royalBlue : theme.doveGray)}; color: ${({ pending, theme }) => (pending ? theme.royalBlue : theme.doveGray)};
font-weight: 400; font-weight: 400;
:hover { :hover {
/* > P {
color: ${({ theme }) => theme.uniswapPink};
} */
background-color: ${({ pending, theme }) => background-color: ${({ pending, theme }) =>
pending ? transparentize(0.9, theme.royalBlue) : darken(0.05, theme.inputBackground)}; pending ? transparentize(0.9, theme.royalBlue) : darken(0.05, theme.inputBackground)};
:focus { :focus {
border: 1px solid border: 1px solid
${({ pending, theme }) => (pending ? darken(0.1, theme.royalBlue) : darken(0.1, theme.mercuryGray))}; ${({ pending, theme }) => (pending ? darken(0.1, theme.royalBlue) : darken(0.1, theme.mercuryGray))};
}
} }
` `
...@@ -81,13 +95,6 @@ const Text = styled.p` ...@@ -81,13 +95,6 @@ const Text = styled.p`
font-size: 0.83rem; 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)` const NetworkIcon = styled(Activity)`
margin-left: 0.25rem; margin-left: 0.25rem;
margin-right: 0.5rem; margin-right: 0.5rem;
...@@ -99,41 +106,20 @@ const SpinnerWrapper = styled(Spinner)` ...@@ -99,41 +106,20 @@ const SpinnerWrapper = styled(Spinner)`
margin: 0 0.25rem 0 0.25rem; margin: 0 0.25rem 0 0.25rem;
` `
const walletModalInitialState = { const IconWrapper = styled.div`
open: false, ${({ theme }) => theme.flexColumnNoWrap};
error: undefined align-items: center;
} justify-content: center;
& > * {
const WALLET_MODAL_ERROR = 'WALLET_MODAL_ERROR' height: ${({ size }) => (size ? size + 'px' : '32px')};
const WALLET_MODAL_OPEN = 'WALLET_MODAL_OPEN' width: ${({ size }) => (size ? size + 'px' : '32px')};
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 }
}
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() { export default function Web3Status() {
const { t } = useTranslation() const { t } = useTranslation()
const { active, account, connectorName, setConnector } = useWeb3Context() const { active, account, connector, error } = useWeb3React()
const contextNetwork = useWeb3React(NetworkContextName)
const ENSName = useENSName(account) const ENSName = useENSName(account)
...@@ -143,137 +129,60 @@ export default function Web3Status() { ...@@ -143,137 +129,60 @@ export default function Web3Status() {
const hasPendingTransactions = !!pending.length const hasPendingTransactions = !!pending.length
const [{ open: walletModalIsOpen, error: walletModalError }, dispatch] = useReducer( const toggleWalletModal = useWalletModalToggle()
walletModalReducer,
walletModalInitialState
)
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}... // handle the logo we want to show with the account
useEffect(() => { function getStatusIcon() {
// if the injected connector is not active... if (connector === injected) {
const { ethereum } = window return <Identicon />
if (connectorName !== 'Injected') { } else if (connector === walletconnect) {
if (connectorName === 'Network' && ethereum && ethereum.on && ethereum.removeListener) { return (
function tryToActivateInjected() { <IconWrapper size={16}>
const library = new ethers.providers.Web3Provider(window.ethereum) <img src={WalletConnectIcon} alt={''} />
// if calling enable won't pop an approve modal, then try to activate injected... </IconWrapper>
library.listAccounts().then(accounts => { )
if (accounts.length >= 1) { } else if (connector === walletlink) {
setConnector('Injected', { suppressAndThrowErrors: true }) return (
.then(() => { <IconWrapper size={16}>
setError() <img src={CoinbaseWalletIcon} alt={''} />
}) </IconWrapper>
.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() { function getWeb3Status() {
if (walletModalError) { if (account) {
// this is ok because we're guaranteed that the error is a wrong network error
return ( return (
<Web3StatusError onClick={onClick}> <Web3StatusConnected onClick={toggleWalletModal} pending={hasPendingTransactions}>
<NetworkIcon /> {hasPendingTransactions && <SpinnerWrapper src={Circle} alt="loader" />}
<Text>Wrong Network</Text> <Text>{ENSName || shortenAddress(account)}</Text>
</Web3StatusError> {getStatusIcon()}
</Web3StatusConnected>
) )
} else if (!account) { } else if (error) {
return ( return (
<Web3StatusConnect onClick={onClick}> <Web3StatusError onClick={toggleWalletModal}>
<Text>{t('Connect')}</Text> <NetworkIcon />
</Web3StatusConnect> <Text>{error instanceof UnsupportedChainIdError ? 'Wrong Network' : 'Error'}</Text>
</Web3StatusError>
) )
} else { } else {
return ( return (
<Web3StatusConnected onClick={onClick} pending={hasPendingTransactions}> <Web3StatusConnect onClick={toggleWalletModal} faded={!account}>
{hasPendingTransactions && <SpinnerWrapper src={Circle} alt="loader" />} <Text>{t('Connect to a Wallet')}</Text>
<Text>{ENSName || shortenAddress(account)}</Text> </Web3StatusConnect>
<Identicon ref={ref} />
</Web3StatusConnected>
) )
} }
} }
if (!contextNetwork.active && !active) {
return null
}
return ( return (
active && ( <>
<> {getWeb3Status()}
{getWeb3Status()} <WalletModal ENSName={ENSName} pendingTransactions={pending} confirmedTransactions={confirmed} />
<WalletModal </>
isOpen={walletModalIsOpen}
error={walletModalError}
onDismiss={closeWalletModal}
ENSName={ENSName}
pendingTransactions={pending}
confirmedTransactions={confirmed}
/>
</>
)
) )
} }
import {
InjectedConnector as InjectedConnectorCore,
NoEthereumProviderError,
UserRejectedRequestError
} from '@web3-react/injected-connector'
export class InjectedConnector extends InjectedConnectorCore {
async activate() {
if (!window.ethereum) {
throw new NoEthereumProviderError()
}
if (window.ethereum.on) {
window.ethereum.on('connect', this.handleConnect)
window.ethereum.on('chainChanged', this.handleChainChanged)
window.ethereum.on('networkChanged', this.handleNetworkChanged)
window.ethereum.on('accountsChanged', this.handleAccountsChanged)
window.ethereum.on('close', this.handleClose)
}
// provides support for most dapp browsers
let account = undefined
if (window.ethereum.isMetaMask) {
window.ethereum.autoRefreshOnNetworkChange = false
account = await window.ethereum
.send('eth_requestAccounts')
.then(({ result: accounts }) => accounts[0])
.catch(error => {
if (error && error.code === 4001) {
throw new UserRejectedRequestError()
} else {
throw error
}
})
} else {
account = await window.ethereum
.enable()
.then(accounts => accounts[0])
.catch(error => {
if (error && error.code === 4001) {
throw new UserRejectedRequestError()
} else {
throw error
}
})
}
return { provider: window.ethereum, account }
}
async getChainId() {
if (!window.ethereum) {
throw new NoEthereumProviderError()
}
if (window.ethereum.isMetaMask) {
return window.ethereum.send('eth_chainId').then(({ result: chainId }) => chainId)
} else {
return window.ethereum.networkVersion ? window.ethereum.networkVersion : 1
}
}
async getAccount() {
if (!window.ethereum) {
throw new NoEthereumProviderError()
}
if (window.ethereum.isMetaMask) {
return window.ethereum.send('eth_accounts').then(({ result: accounts }) => accounts[0])
} else {
return window.ethereum.enable().then(accounts => accounts[0])
}
}
deactivate() {
if (window.ethereum && window.ethereum.removeListener) {
window.ethereum.removeListener('connect', this.handleConnect)
window.ethereum.removeListener('chainChanged', this.handleChainChanged)
window.ethereum.removeListener('networkChanged', this.handleNetworkChanged)
window.ethereum.removeListener('accountsChanged', this.handleAccountsChanged)
window.ethereum.removeListener('close', this.handleClose)
}
}
async isAuthorized() {
if (window.ethereum) {
if (window.ethereum.isMetaMask) {
return window.ethereum
.send('eth_accounts')
.then(({ result: accounts }) => {
if (accounts.length > 0) {
return true
}
return false
})
.catch(() => {
return false
})
} else {
return window.ethereum
.enable()
.then(accounts => {
if (accounts.length > 0) {
return true
}
return false
})
.catch(() => {
return false
})
}
}
return false
}
}
import { 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()
}
}
}
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'
})
import { injected, walletconnect, walletlink } from '../connectors'
export const FACTORY_ADDRESSES = { export const FACTORY_ADDRESSES = {
1: '0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95', 1: '0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95',
3: '0x9c83dCE8CA20E9aAF9D3efc003b2ea62aBC08351', 3: '0x9c83dCE8CA20E9aAF9D3efc003b2ea62aBC08351',
...@@ -10,9 +12,63 @@ export const SUPPORTED_THEMES = { ...@@ -10,9 +12,63 @@ export const SUPPORTED_THEMES = {
LIGHT: 'LIGHT' 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 // list of tokens that lock fund on adding liquidity - used to disable button
export const brokenTokens = [ export const brokenTokens = [
'0xb8c77482e45f1f44de1745f52c74426c631bdd52', '0xB8c77482e45F1F44dE1745F52C74426C631bDD52',
'0x95daaab98046846bf4b2853e23cba236fa394a31', '0x95dAaaB98046846bF4B2853e23cba236fa394A31',
'0x55296f69f40ea6d20e478533c15a6b08b654e758' '0x55296f69f40Ea6d20E478533C15A6B08B654E758'
] ]
export const NetworkContextName = 'NETWORK'
import React, { createContext, useContext, useReducer, useMemo, useCallback } from 'react' import React, { createContext, useContext, useReducer, useMemo, useCallback } from 'react'
import { ethers } from 'ethers' import { ethers } from 'ethers'
import { getTokenReserves, getMarketDetails, BigNumber } from '@uniswap/sdk' import { getTokenReserves, getMarketDetails, BigNumber } from '@uniswap/sdk'
import { useWeb3Context } from 'web3-react'
import { useWeb3React } from '../hooks'
import { safeAccess, isAddress, getEtherBalance, getTokenBalance } from '../utils' import { safeAccess, isAddress, getEtherBalance, getTokenBalance } from '../utils'
import { useAllTokenDetails } from './Tokens' import { useAllTokenDetails } from './Tokens'
...@@ -53,13 +53,13 @@ export default function Provider({ children }) { ...@@ -53,13 +53,13 @@ export default function Provider({ children }) {
} }
export function useFetchAllBalances() { export function useFetchAllBalances() {
const { account, networkId, library } = useWeb3Context() const { library, chainId, account } = useWeb3React()
const allTokens = useAllTokenDetails() const allTokens = useAllTokenDetails()
const [state, { update }] = useAllBalancesContext() const [state, { update }] = useAllBalancesContext()
const { allBalanceData } = safeAccess(state, [networkId, account]) || {} const { allBalanceData } = safeAccess(state, [chainId, account]) || {}
const getData = async () => { const getData = async () => {
if (!!library && !!account) { if (!!library && !!account) {
...@@ -90,7 +90,7 @@ export function useFetchAllBalances() { ...@@ -90,7 +90,7 @@ export function useFetchAllBalances() {
} }
}) })
) )
update(newBalances, networkId, account) update(newBalances, chainId, account)
} }
} }
......
import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react' 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 { safeAccess, isAddress, getTokenAllowance } from '../utils'
import { useBlockNumber } from './Application' import { useBlockNumber } from './Application'
...@@ -54,12 +55,12 @@ export default function Provider({ children }) { ...@@ -54,12 +55,12 @@ export default function Provider({ children }) {
} }
export function useAddressAllowance(address, tokenAddress, spenderAddress) { export function useAddressAllowance(address, tokenAddress, spenderAddress) {
const { networkId, library } = useWeb3Context() const { library, chainId } = useWeb3React()
const globalBlockNumber = useBlockNumber() const globalBlockNumber = useBlockNumber()
const [state, { update }] = useAllowancesContext() const [state, { update }] = useAllowancesContext()
const { value, blockNumber } = safeAccess(state, [networkId, address, tokenAddress, spenderAddress]) || {} const { value, blockNumber } = safeAccess(state, [chainId, address, tokenAddress, spenderAddress]) || {}
useEffect(() => { useEffect(() => {
if ( if (
...@@ -67,7 +68,7 @@ export function useAddressAllowance(address, tokenAddress, spenderAddress) { ...@@ -67,7 +68,7 @@ export function useAddressAllowance(address, tokenAddress, spenderAddress) {
isAddress(tokenAddress) && isAddress(tokenAddress) &&
isAddress(spenderAddress) && isAddress(spenderAddress) &&
(value === undefined || blockNumber !== globalBlockNumber) && (value === undefined || blockNumber !== globalBlockNumber) &&
(networkId || networkId === 0) && (chainId || chainId === 0) &&
library library
) { ) {
let stale = false let stale = false
...@@ -75,12 +76,12 @@ export function useAddressAllowance(address, tokenAddress, spenderAddress) { ...@@ -75,12 +76,12 @@ export function useAddressAllowance(address, tokenAddress, spenderAddress) {
getTokenAllowance(address, tokenAddress, spenderAddress, library) getTokenAllowance(address, tokenAddress, spenderAddress, library)
.then(value => { .then(value => {
if (!stale) { if (!stale) {
update(networkId, address, tokenAddress, spenderAddress, value, globalBlockNumber) update(chainId, address, tokenAddress, spenderAddress, value, globalBlockNumber)
} }
}) })
.catch(() => { .catch(() => {
if (!stale) { 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) { ...@@ -88,7 +89,7 @@ export function useAddressAllowance(address, tokenAddress, spenderAddress) {
stale = true stale = true
} }
} }
}, [address, tokenAddress, spenderAddress, value, blockNumber, globalBlockNumber, networkId, library, update]) }, [address, tokenAddress, spenderAddress, value, blockNumber, globalBlockNumber, chainId, library, update])
return value return value
} }
import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react' import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react'
import { useWeb3Context } from 'web3-react'
import { useWeb3React } from '../hooks'
import { safeAccess } from '../utils' import { safeAccess } from '../utils'
import { getUSDPrice } from '../utils/price' import { getUSDPrice } from '../utils/price'
const BLOCK_NUMBER = 'BLOCK_NUMBER' const BLOCK_NUMBER = 'BLOCK_NUMBER'
const USD_PRICE = 'USD_PRICE' const USD_PRICE = 'USD_PRICE'
const WALLET_MODAL_OPEN = 'WALLET_MODAL_OPEN'
const UPDATE_BLOCK_NUMBER = 'UPDATE_BLOCK_NUMBER' const UPDATE_BLOCK_NUMBER = 'UPDATE_BLOCK_NUMBER'
const UPDATE_USD_PRICE = 'UPDATE_USD_PRICE' const UPDATE_USD_PRICE = 'UPDATE_USD_PRICE'
const TOGGLE_WALLET_MODAL = 'TOGGLE_WALLET_MODAL'
const ApplicationContext = createContext() const ApplicationContext = createContext()
...@@ -38,6 +40,9 @@ function reducer(state, { type, payload }) { ...@@ -38,6 +40,9 @@ function reducer(state, { type, payload }) {
} }
} }
} }
case TOGGLE_WALLET_MODAL: {
return { ...state, [WALLET_MODAL_OPEN]: !state[WALLET_MODAL_OPEN] }
}
default: { default: {
throw Error(`Unexpected action type in ApplicationContext reducer: '${type}'.`) throw Error(`Unexpected action type in ApplicationContext reducer: '${type}'.`)
} }
...@@ -47,7 +52,8 @@ function reducer(state, { type, payload }) { ...@@ -47,7 +52,8 @@ function reducer(state, { type, payload }) {
export default function Provider({ children }) { export default function Provider({ children }) {
const [state, dispatch] = useReducer(reducer, { const [state, dispatch] = useReducer(reducer, {
[BLOCK_NUMBER]: {}, [BLOCK_NUMBER]: {},
[USD_PRICE]: {} [USD_PRICE]: {},
[WALLET_MODAL_OPEN]: false
}) })
const updateBlockNumber = useCallback((networkId, blockNumber) => { const updateBlockNumber = useCallback((networkId, blockNumber) => {
...@@ -58,9 +64,18 @@ export default function Provider({ children }) { ...@@ -58,9 +64,18 @@ export default function Provider({ children }) {
dispatch({ type: UPDATE_USD_PRICE, payload: { networkId, USDPrice } }) dispatch({ type: UPDATE_USD_PRICE, payload: { networkId, USDPrice } })
}, []) }, [])
const toggleWalletModal = useCallback(() => {
dispatch({ type: TOGGLE_WALLET_MODAL })
}, [])
return ( return (
<ApplicationContext.Provider <ApplicationContext.Provider
value={useMemo(() => [state, { updateBlockNumber, updateUSDPrice }], [state, updateBlockNumber, updateUSDPrice])} value={useMemo(() => [state, { updateBlockNumber, updateUSDPrice, toggleWalletModal }], [
state,
updateBlockNumber,
updateUSDPrice,
toggleWalletModal
])}
> >
{children} {children}
</ApplicationContext.Provider> </ApplicationContext.Provider>
...@@ -68,36 +83,29 @@ export default function Provider({ children }) { ...@@ -68,36 +83,29 @@ export default function Provider({ children }) {
} }
export function Updater() { export function Updater() {
const { networkId, library } = useWeb3Context() const { library, chainId } = useWeb3React()
const globalBlockNumber = useBlockNumber() const globalBlockNumber = useBlockNumber()
const [, { updateBlockNumber, updateUSDPrice }] = useApplicationContext() 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 // update usd price
useEffect(() => { useEffect(() => {
if (library && networkId === 1) { if (library && chainId === 1) {
let stale = false let stale = false
getUSDPrice(library) getUSDPrice(library)
.then(([price]) => { .then(([price]) => {
if (!stale) { if (!stale) {
updateUSDPrice(networkId, price) updateUSDPrice(chainId, price)
} }
}) })
.catch(() => { .catch(() => {
if (!stale) { if (!stale) {
updateUSDPrice(networkId, null) updateUSDPrice(chainId, null)
} }
}) })
} }
}, [globalBlockNumber, library, networkId, updateUSDPrice]) }, [globalBlockNumber, library, chainId, updateUSDPrice])
// update block number // update block number
useEffect(() => { useEffect(() => {
...@@ -109,12 +117,12 @@ export function Updater() { ...@@ -109,12 +117,12 @@ export function Updater() {
.getBlockNumber() .getBlockNumber()
.then(blockNumber => { .then(blockNumber => {
if (!stale) { if (!stale) {
updateBlockNumber(networkId, blockNumber) updateBlockNumber(chainId, blockNumber)
} }
}) })
.catch(() => { .catch(() => {
if (!stale) { if (!stale) {
updateBlockNumber(networkId, null) updateBlockNumber(chainId, null)
} }
}) })
} }
...@@ -127,23 +135,35 @@ export function Updater() { ...@@ -127,23 +135,35 @@ export function Updater() {
library.removeListener('block', update) library.removeListener('block', update)
} }
} }
}, [networkId, library, updateBlockNumber]) }, [chainId, library, updateBlockNumber])
return null return null
} }
export function useBlockNumber() { export function useBlockNumber() {
const { networkId } = useWeb3Context() const { chainId } = useWeb3React()
const [state] = useApplicationContext() const [state] = useApplicationContext()
return safeAccess(state, [BLOCK_NUMBER, networkId]) return safeAccess(state, [BLOCK_NUMBER, chainId])
} }
export function useUSDPrice() { export function useUSDPrice() {
const { networkId } = useWeb3Context() const { chainId } = useWeb3React()
const [state] = useApplicationContext() 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
} }
import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react' 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 { safeAccess, isAddress, getEtherBalance, getTokenBalance } from '../utils'
import { useBlockNumber } from './Application' import { useBlockNumber } from './Application'
import { useTokenDetails } from './Tokens' import { useTokenDetails } from './Tokens'
...@@ -52,38 +52,38 @@ export default function Provider({ children }) { ...@@ -52,38 +52,38 @@ export default function Provider({ children }) {
} }
export function useAddressBalance(address, tokenAddress) { export function useAddressBalance(address, tokenAddress) {
const { networkId, library } = useWeb3Context() const { library, chainId } = useWeb3React()
const globalBlockNumber = useBlockNumber() const globalBlockNumber = useBlockNumber()
const [state, { update }] = useBalancesContext() const [state, { update }] = useBalancesContext()
const { value, blockNumber } = safeAccess(state, [networkId, address, tokenAddress]) || {} const { value, blockNumber } = safeAccess(state, [chainId, address, tokenAddress]) || {}
useEffect(() => { useEffect(() => {
if ( if (
isAddress(address) && isAddress(address) &&
(tokenAddress === 'ETH' || isAddress(tokenAddress)) && (tokenAddress === 'ETH' || isAddress(tokenAddress)) &&
(value === undefined || blockNumber !== globalBlockNumber) && (value === undefined || blockNumber !== globalBlockNumber) &&
(networkId || networkId === 0) && (chainId || chainId === 0) &&
library library
) { ) {
let stale = false let stale = false
;(tokenAddress === 'ETH' ? getEtherBalance(address, library) : getTokenBalance(tokenAddress, address, library)) ;(tokenAddress === 'ETH' ? getEtherBalance(address, library) : getTokenBalance(tokenAddress, address, library))
.then(value => { .then(value => {
if (!stale) { if (!stale) {
update(networkId, address, tokenAddress, value, globalBlockNumber) update(chainId, address, tokenAddress, value, globalBlockNumber)
} }
}) })
.catch(() => { .catch(() => {
if (!stale) { if (!stale) {
update(networkId, address, tokenAddress, null, globalBlockNumber) update(chainId, address, tokenAddress, null, globalBlockNumber)
} }
}) })
return () => { return () => {
stale = true stale = true
} }
} }
}, [address, tokenAddress, value, blockNumber, globalBlockNumber, networkId, library, update]) }, [address, tokenAddress, value, blockNumber, globalBlockNumber, chainId, library, update])
return value return value
} }
......
import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react' import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react'
import { useWeb3Context } from 'web3-react'
import { ethers } from 'ethers' import { ethers } from 'ethers'
import { useWeb3React } from '../hooks'
import { import {
isAddress, isAddress,
getTokenName, getTokenName,
...@@ -558,10 +558,10 @@ export default function Provider({ children }) { ...@@ -558,10 +558,10 @@ export default function Provider({ children }) {
} }
export function useTokenDetails(tokenAddress) { export function useTokenDetails(tokenAddress) {
const { networkId, library } = useWeb3Context() const { library, chainId } = useWeb3React()
const [state, { update }] = useTokensContext() 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 } = const { [NAME]: name, [SYMBOL]: symbol, [DECIMALS]: decimals, [EXCHANGE_ADDRESS]: exchangeAddress } =
safeAccess(allTokensInNetwork, [tokenAddress]) || {} safeAccess(allTokensInNetwork, [tokenAddress]) || {}
...@@ -569,7 +569,7 @@ export function useTokenDetails(tokenAddress) { ...@@ -569,7 +569,7 @@ export function useTokenDetails(tokenAddress) {
if ( if (
isAddress(tokenAddress) && isAddress(tokenAddress) &&
(name === undefined || symbol === undefined || decimals === undefined || exchangeAddress === undefined) && (name === undefined || symbol === undefined || decimals === undefined || exchangeAddress === undefined) &&
(networkId || networkId === 0) && (chainId || chainId === 0) &&
library library
) { ) {
let stale = false let stale = false
...@@ -577,14 +577,14 @@ export function useTokenDetails(tokenAddress) { ...@@ -577,14 +577,14 @@ export function useTokenDetails(tokenAddress) {
const namePromise = getTokenName(tokenAddress, library).catch(() => null) const namePromise = getTokenName(tokenAddress, library).catch(() => null)
const symbolPromise = getTokenSymbol(tokenAddress, library).catch(() => null) const symbolPromise = getTokenSymbol(tokenAddress, library).catch(() => null)
const decimalsPromise = getTokenDecimals(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 () => null
) )
Promise.all([namePromise, symbolPromise, decimalsPromise, exchangeAddressPromise]).then( Promise.all([namePromise, symbolPromise, decimalsPromise, exchangeAddressPromise]).then(
([resolvedName, resolvedSymbol, resolvedDecimals, resolvedExchangeAddress]) => { ([resolvedName, resolvedSymbol, resolvedDecimals, resolvedExchangeAddress]) => {
if (!stale) { if (!stale) {
update(networkId, tokenAddress, resolvedName, resolvedSymbol, resolvedDecimals, resolvedExchangeAddress) update(chainId, tokenAddress, resolvedName, resolvedSymbol, resolvedDecimals, resolvedExchangeAddress)
} }
} }
) )
...@@ -592,16 +592,16 @@ export function useTokenDetails(tokenAddress) { ...@@ -592,16 +592,16 @@ export function useTokenDetails(tokenAddress) {
stale = true stale = true
} }
} }
}, [tokenAddress, name, symbol, decimals, exchangeAddress, networkId, library, update]) }, [tokenAddress, name, symbol, decimals, exchangeAddress, chainId, library, update])
return { name, symbol, decimals, exchangeAddress } return { name, symbol, decimals, exchangeAddress }
} }
export function useAllTokenDetails(requireExchange = true) { export function useAllTokenDetails(requireExchange = true) {
const { networkId } = useWeb3Context() const { chainId } = useWeb3React()
const [state] = useTokensContext() const [state] = useTokensContext()
const tokenDetails = { ...ETH, ...(safeAccess(state, [networkId]) || {}) } const tokenDetails = { ...ETH, ...(safeAccess(state, [chainId]) || {}) }
return requireExchange return requireExchange
? Object.keys(tokenDetails) ? Object.keys(tokenDetails)
......
import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react' import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react'
import { useWeb3Context } from 'web3-react'
import { useWeb3React } from '../hooks'
import { safeAccess } from '../utils' import { safeAccess } from '../utils'
import { useBlockNumber } from './Application' import { useBlockNumber } from './Application'
...@@ -103,15 +103,15 @@ export default function Provider({ children }) { ...@@ -103,15 +103,15 @@ export default function Provider({ children }) {
} }
export function Updater() { export function Updater() {
const { networkId, library } = useWeb3Context() const { chainId, library } = useWeb3React()
const globalBlockNumber = useBlockNumber() const globalBlockNumber = useBlockNumber()
const [state, { check, finalize }] = useTransactionsContext() const [state, { check, finalize }] = useTransactionsContext()
const allTransactions = safeAccess(state, [networkId]) || {} const allTransactions = safeAccess(state, [chainId]) || {}
useEffect(() => { useEffect(() => {
if ((networkId || networkId === 0) && library) { if ((chainId || chainId === 0) && library) {
let stale = false let stale = false
Object.keys(allTransactions) Object.keys(allTransactions)
.filter( .filter(
...@@ -123,14 +123,14 @@ export function Updater() { ...@@ -123,14 +123,14 @@ export function Updater() {
.then(receipt => { .then(receipt => {
if (!stale) { if (!stale) {
if (!receipt) { if (!receipt) {
check(networkId, hash, globalBlockNumber) check(chainId, hash, globalBlockNumber)
} else { } else {
finalize(networkId, hash, receipt) finalize(chainId, hash, receipt)
} }
} }
}) })
.catch(() => { .catch(() => {
check(networkId, hash, globalBlockNumber) check(chainId, hash, globalBlockNumber)
}) })
}) })
...@@ -138,20 +138,20 @@ export function Updater() { ...@@ -138,20 +138,20 @@ export function Updater() {
stale = true stale = true
} }
} }
}, [networkId, library, allTransactions, globalBlockNumber, check, finalize]) }, [chainId, library, allTransactions, globalBlockNumber, check, finalize])
return null return null
} }
export function useTransactionAdder() { export function useTransactionAdder() {
const { networkId } = useWeb3Context() const { chainId } = useWeb3React()
const [, { add }] = useTransactionsContext() const [, { add }] = useTransactionsContext()
return useCallback( return useCallback(
(response, customData = {}) => { (response, customData = {}) => {
if (!(networkId || networkId === 0)) { if (!(chainId || chainId === 0)) {
throw Error(`Invalid networkId '${networkId}`) throw Error(`Invalid networkId '${chainId}`)
} }
const hash = safeAccess(response, ['hash']) const hash = safeAccess(response, ['hash'])
...@@ -159,18 +159,18 @@ export function useTransactionAdder() { ...@@ -159,18 +159,18 @@ export function useTransactionAdder() {
if (!hash) { if (!hash) {
throw Error('No transaction hash found.') 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() { export function useAllTransactions() {
const { networkId } = useWeb3Context() const { chainId } = useWeb3React()
const [state] = useTransactionsContext() const [state] = useTransactionsContext()
return safeAccess(state, [networkId]) || {} return safeAccess(state, [chainId]) || {}
} }
export function usePendingApproval(tokenAddress) { export function usePendingApproval(tokenAddress) {
......
import { useState, useMemo, useCallback, useEffect } from 'react' import { useState, useMemo, useCallback, useEffect, useRef } from 'react'
import { useWeb3Context } from 'web3-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 ERC20_ABI from '../constants/abis/erc20'
import { getContract, getFactoryContract, getExchangeContract, isAddress } from '../utils' 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/ // modified from https://usehooks.com/useDebounce/
export function useDebounce(value, delay) { export function useDebounce(value, delay) {
...@@ -51,7 +123,7 @@ export function useBodyKeyDown(targetKey, onKeyDown, suppressOnKeyDown = false) ...@@ -51,7 +123,7 @@ export function useBodyKeyDown(targetKey, onKeyDown, suppressOnKeyDown = false)
} }
export function useENSName(address) { export function useENSName(address) {
const { library } = useWeb3Context() const { library } = useWeb3React()
const [ENSName, setENSName] = useState() const [ENSName, setENSName] = useState()
...@@ -84,7 +156,7 @@ export function useENSName(address) { ...@@ -84,7 +156,7 @@ export function useENSName(address) {
// returns null on errors // returns null on errors
export function useContract(address, ABI, withSignerIfPossible = true) { export function useContract(address, ABI, withSignerIfPossible = true) {
const { library, account } = useWeb3Context() const { library, account } = useWeb3React()
return useMemo(() => { return useMemo(() => {
try { try {
...@@ -97,7 +169,7 @@ export function useContract(address, ABI, withSignerIfPossible = true) { ...@@ -97,7 +169,7 @@ export function useContract(address, ABI, withSignerIfPossible = true) {
// returns null on errors // returns null on errors
export function useTokenContract(tokenAddress, withSignerIfPossible = true) { export function useTokenContract(tokenAddress, withSignerIfPossible = true) {
const { library, account } = useWeb3Context() const { library, account } = useWeb3React()
return useMemo(() => { return useMemo(() => {
try { try {
...@@ -110,7 +182,7 @@ export function useTokenContract(tokenAddress, withSignerIfPossible = true) { ...@@ -110,7 +182,7 @@ export function useTokenContract(tokenAddress, withSignerIfPossible = true) {
// returns null on errors // returns null on errors
export function useFactoryContract(withSignerIfPossible = true) { export function useFactoryContract(withSignerIfPossible = true) {
const { networkId, library, account } = useWeb3Context() const { networkId, library, account } = useWeb3React()
return useMemo(() => { return useMemo(() => {
try { try {
...@@ -122,7 +194,7 @@ export function useFactoryContract(withSignerIfPossible = true) { ...@@ -122,7 +194,7 @@ export function useFactoryContract(withSignerIfPossible = true) {
} }
export function useExchangeContract(exchangeAddress, withSignerIfPossible = true) { export function useExchangeContract(exchangeAddress, withSignerIfPossible = true) {
const { library, account } = useWeb3Context() const { library, account } = useWeb3React()
return useMemo(() => { return useMemo(() => {
try { try {
...@@ -155,3 +227,18 @@ export function useCopyClipboard(timeout = 500) { ...@@ -155,3 +227,18 @@ export function useCopyClipboard(timeout = 500) {
return [isCopied, staticCopy] 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
}
import React from 'react' import React from 'react'
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
import ReactGA from 'react-ga' 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 LocalStorageContextProvider, { Updater as LocalStorageContextUpdater } from './contexts/LocalStorage'
import ApplicationContextProvider, { Updater as ApplicationContextUpdater } from './contexts/Application' import ApplicationContextProvider, { Updater as ApplicationContextUpdater } from './contexts/Application'
import TransactionContextProvider, { Updater as TransactionContextUpdater } from './contexts/Transactions' import TransactionContextProvider, { Updater as TransactionContextUpdater } from './contexts/Transactions'
...@@ -11,13 +12,18 @@ import TokensContextProvider from './contexts/Tokens' ...@@ -11,13 +12,18 @@ import TokensContextProvider from './contexts/Tokens'
import BalancesContextProvider from './contexts/Balances' import BalancesContextProvider from './contexts/Balances'
import AllowancesContextProvider from './contexts/Allowances' import AllowancesContextProvider from './contexts/Allowances'
import AllBalancesContextProvider from './contexts/AllBalances' import AllBalancesContextProvider from './contexts/AllBalances'
import App from './pages/App' import App from './pages/App'
import NetworkOnlyConnector from './NetworkOnlyConnector' import ThemeProvider, { GlobalStyle } from './theme'
import InjectedConnector from './InjectedConnector'
import './i18n' 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') { if (process.env.NODE_ENV === 'production') {
ReactGA.initialize('UA-128182339-1') ReactGA.initialize('UA-128182339-1')
} else { } else {
...@@ -25,10 +31,6 @@ if (process.env.NODE_ENV === 'production') { ...@@ -25,10 +31,6 @@ if (process.env.NODE_ENV === 'production') {
} }
ReactGA.pageview(window.location.pathname + window.location.search) 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 }) { function ContextProviders({ children }) {
return ( return (
<LocalStorageContextProvider> <LocalStorageContextProvider>
...@@ -58,16 +60,18 @@ function Updaters() { ...@@ -58,16 +60,18 @@ function Updaters() {
} }
ReactDOM.render( ReactDOM.render(
<Web3Provider connectors={connectors} libraryName="ethers.js"> <Web3ReactProvider getLibrary={getLibrary}>
<ContextProviders> <Web3ProviderNetwork getLibrary={getLibrary}>
<Updaters /> <ContextProviders>
<ThemeProvider> <Updaters />
<> <ThemeProvider>
<GlobalStyle /> <>
<App /> <GlobalStyle />
</> <App />
</ThemeProvider> </>
</ContextProviders> </ThemeProvider>
</Web3Provider>, </ContextProviders>
</Web3ProviderNetwork>
</Web3ReactProvider>,
document.getElementById('root') document.getElementById('root')
) )
import React, { useReducer, useState, useCallback, useEffect, useMemo } from 'react' import React, { useReducer, useState, useCallback, useEffect, useMemo } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useWeb3Context } from 'web3-react'
import { createBrowserHistory } from 'history' import { createBrowserHistory } from 'history'
import { ethers } from 'ethers' import { ethers } from 'ethers'
import ReactGA from 'react-ga' import ReactGA from 'react-ga'
...@@ -11,8 +10,7 @@ import CurrencyInputPanel from '../../components/CurrencyInputPanel' ...@@ -11,8 +10,7 @@ import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import OversizedPanel from '../../components/OversizedPanel' import OversizedPanel from '../../components/OversizedPanel'
import ContextualInfo from '../../components/ContextualInfo' import ContextualInfo from '../../components/ContextualInfo'
import { ReactComponent as Plus } from '../../assets/images/plus-blue.svg' import { ReactComponent as Plus } from '../../assets/images/plus-blue.svg'
import { useWeb3React, useExchangeContract } from '../../hooks'
import { useExchangeContract } from '../../hooks'
import { brokenTokens } from '../../constants' import { brokenTokens } from '../../constants'
import { amountFormatter, calculateGasMargin } from '../../utils' import { amountFormatter, calculateGasMargin } from '../../utils'
import { useTransactionAdder } from '../../contexts/Transactions' import { useTransactionAdder } from '../../contexts/Transactions'
...@@ -201,7 +199,7 @@ function getMarketRate(reserveETH, reserveToken, decimals, invert = false) { ...@@ -201,7 +199,7 @@ function getMarketRate(reserveETH, reserveToken, decimals, invert = false) {
export default function AddLiquidity({ params }) { export default function AddLiquidity({ params }) {
const { t } = useTranslation() const { t } = useTranslation()
const { library, active, account } = useWeb3Context() const { library, account, active } = useWeb3React()
// clear url of query // clear url of query
useEffect(() => { useEffect(() => {
......
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react'
import { withRouter } from 'react-router' import { withRouter } from 'react-router'
import { useWeb3Context } from 'web3-react'
import { createBrowserHistory } from 'history' import { createBrowserHistory } from 'history'
import { ethers } from 'ethers' import { ethers } from 'ethers'
import styled from 'styled-components' import styled from 'styled-components'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import ReactGA from 'react-ga' import ReactGA from 'react-ga'
import { useWeb3React, useFactoryContract } from '../../hooks'
import { Button } from '../../theme' import { Button } from '../../theme'
import AddressInputPanel from '../../components/AddressInputPanel' import AddressInputPanel from '../../components/AddressInputPanel'
import OversizedPanel from '../../components/OversizedPanel' import OversizedPanel from '../../components/OversizedPanel'
import { useFactoryContract } from '../../hooks'
import { useTokenDetails } from '../../contexts/Tokens' import { useTokenDetails } from '../../contexts/Tokens'
import { useTransactionAdder } from '../../contexts/Transactions' import { useTransactionAdder } from '../../contexts/Transactions'
...@@ -56,7 +56,8 @@ const Flex = styled.div` ...@@ -56,7 +56,8 @@ const Flex = styled.div`
function CreateExchange({ location, params }) { function CreateExchange({ location, params }) {
const { t } = useTranslation() const { t } = useTranslation()
const { account } = useWeb3Context() const { account } = useWeb3React()
const factory = useFactoryContract() const factory = useFactoryContract()
const [tokenAddress, setTokenAddress] = useState({ const [tokenAddress, setTokenAddress] = useState({
......
...@@ -126,6 +126,7 @@ function ModeSelector({ location: { pathname }, history }) { ...@@ -126,6 +126,7 @@ function ModeSelector({ location: { pathname }, history }) {
</LiquidityContainer> </LiquidityContainer>
<Modal <Modal
isOpen={modalIsOpen} isOpen={modalIsOpen}
maxHeight={50}
onDismiss={() => { onDismiss={() => {
setModalIsOpen(false) setModalIsOpen(false)
}} }}
......
...@@ -2,17 +2,15 @@ import React, { useState, useEffect, useCallback } from 'react' ...@@ -2,17 +2,15 @@ import React, { useState, useEffect, useCallback } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import ReactGA from 'react-ga' import ReactGA from 'react-ga'
import { createBrowserHistory } from 'history' import { createBrowserHistory } from 'history'
import { useWeb3Context } from 'web3-react'
import { ethers } from 'ethers' import { ethers } from 'ethers'
import styled from 'styled-components' import styled from 'styled-components'
import { useWeb3React, useExchangeContract } from '../../hooks'
import { Button } from '../../theme' import { Button } from '../../theme'
import CurrencyInputPanel from '../../components/CurrencyInputPanel' import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import ContextualInfo from '../../components/ContextualInfo' import ContextualInfo from '../../components/ContextualInfo'
import OversizedPanel from '../../components/OversizedPanel' import OversizedPanel from '../../components/OversizedPanel'
import ArrowDown from '../../assets/svg/SVGArrowDown' import ArrowDown from '../../assets/svg/SVGArrowDown'
import { useExchangeContract } from '../../hooks'
import { useTransactionAdder } from '../../contexts/Transactions' import { useTransactionAdder } from '../../contexts/Transactions'
import { useTokenDetails } from '../../contexts/Tokens' import { useTokenDetails } from '../../contexts/Tokens'
import { useAddressBalance } from '../../contexts/Balances' import { useAddressBalance } from '../../contexts/Balances'
...@@ -143,8 +141,8 @@ function calculateSlippageBounds(value) { ...@@ -143,8 +141,8 @@ function calculateSlippageBounds(value) {
} }
export default function RemoveLiquidity({ params }) { export default function RemoveLiquidity({ params }) {
const { library, account, active } = useWeb3Context()
const { t } = useTranslation() const { t } = useTranslation()
const { library, account, active } = useWeb3React()
const addTransaction = useTransactionAdder() const addTransaction = useTransactionAdder()
......
...@@ -21,16 +21,6 @@ const mediaWidthTemplates = Object.keys(MEDIA_WIDTHS).reduce((accumulator, size) ...@@ -21,16 +21,6 @@ const mediaWidthTemplates = Object.keys(MEDIA_WIDTHS).reduce((accumulator, size)
return accumulator return accumulator
}, {}) }, {})
const flexColumnNoWrap = css`
display: flex;
flex-flow: column nowrap;
`
const flexRowNoWrap = css`
display: flex;
flex-flow: row nowrap;
`
const white = '#FFFFFF' const white = '#FFFFFF'
const black = '#000000' const black = '#000000'
...@@ -59,7 +49,7 @@ const theme = darkMode => ({ ...@@ -59,7 +49,7 @@ const theme = darkMode => ({
// for setting css on <html> // for setting css on <html>
backgroundColor: darkMode ? '#333639' : white, 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, inputBackground: darkMode ? '#202124' : white,
placeholderGray: darkMode ? '#5F5F5F' : '#E1E1E1', placeholderGray: darkMode ? '#5F5F5F' : '#E1E1E1',
shadowColor: darkMode ? '#000' : '#2F80ED', shadowColor: darkMode ? '#000' : '#2F80ED',
...@@ -92,16 +82,29 @@ const theme = darkMode => ({ ...@@ -92,16 +82,29 @@ const theme = darkMode => ({
warningYellow: '#FFE270', warningYellow: '#FFE270',
// pink // pink
uniswapPink: '#DC6BE5', uniswapPink: '#DC6BE5',
//green
connectedGreen: '#27AE60', connectedGreen: '#27AE60',
//branded
metaMaskOrange: '#E8831D',
//specific //specific
textHover: darkMode ? theme.uniswapPink : theme.doveGray, textHover: darkMode ? theme.uniswapPink : theme.doveGray,
// connect button when loggedout
buttonFaded: darkMode ? '#DC6BE5' : '#737373',
// media queries // media queries
mediaWidth: mediaWidthTemplates, mediaWidth: mediaWidthTemplates,
// css snippets // css snippets
flexColumnNoWrap, flexColumnNoWrap: css`
flexRowNoWrap display: flex;
flex-flow: column nowrap;
`,
flexRowNoWrap: css`
display: flex;
flex-flow: row nowrap;
`
}) })
export const GlobalStyle = createGlobalStyle` export const GlobalStyle = createGlobalStyle`
......
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment