typed pages

This commit is contained in:
ianlapham 2020-03-20 16:03:23 -04:00
parent 655b79569b
commit 6211dff044
30 changed files with 730 additions and 2196 deletions

@ -13,7 +13,7 @@
"@types/node": "^13.7.4",
"@types/react": "^16.9.21",
"@types/react-dom": "^16.9.5",
"@uniswap/sdk": "@uniswap/sdk@2.0.0-beta.17",
"@uniswap/sdk": "@uniswap/sdk@2.0.0-beta.19",
"@web3-react/core": "^6.0.2",
"@web3-react/fortmatic-connector": "^6.0.2",
"@web3-react/injected-connector": "^6.0.3",

@ -3,6 +3,8 @@ import styled from 'styled-components'
import { useTranslation } from 'react-i18next'
import { transparentize } from 'polished'
import QR from '../../assets/svg/QR.svg'
import { isAddress } from '../../utils'
import { useWeb3React, useDebounce } from '../../hooks'
@ -13,6 +15,7 @@ const InputPanel = styled.div`
border-radius: 1.25rem;
background-color: ${({ theme }) => theme.inputBackground};
z-index: 1;
width: 100%;
`
const ContainerRow = styled.div`
@ -49,7 +52,7 @@ const LabelContainer = styled.div`
const InputRow = styled.div`
${({ theme }) => theme.flexRowNoWrap}
align-items: center;
padding: 0.25rem 0.85rem 0.75rem;
padding: 0.75rem;
`
const Input = styled.input`
@ -69,7 +72,17 @@ const Input = styled.input`
}
`
export default function AddressInputPanel({ title, initialInput = '', onChange = () => {}, onError = () => {} }) {
const QRWrapper = styled.div`
display: flex;
align-items: center;
justify-content: center;
border: 1px solid ${({ theme }) => theme.outlineGrey};
background: #fbfbfb;
padding: 4px;
border-radius: 8px;
`
export default function AddressInputPanel({ title, initialInput = '', onChange, onError}) {
const { t } = useTranslation()
const { library } = useWeb3React()
@ -166,11 +179,6 @@ export default function AddressInputPanel({ title, initialInput = '', onChange =
<InputPanel>
<ContainerRow error={input !== '' && error}>
<InputContainer>
<LabelRow>
<LabelContainer>
<span>{title || t('recipientAddress')}</span>
</LabelContainer>
</LabelRow>
<InputRow>
<Input
type="text"
@ -183,6 +191,9 @@ export default function AddressInputPanel({ title, initialInput = '', onChange =
onChange={onInput}
value={input}
/>
<QRWrapper>
<img src={QR} alt="" />
</QRWrapper>
</InputRow>
</InputContainer>
</ContainerRow>

@ -0,0 +1,150 @@
import React, { useState } from 'react'
import styled from 'styled-components'
import QuestionHelper from '../Question'
import NumericalInput from '../NumericalInput'
import { Link } from '../../theme/components'
import { TYPE } from '../../theme'
import { AutoColumn } from '../../components/Column'
import Row, { RowBetween, RowFixed } from '../../components/Row'
import { ButtonRadio } from '../Button'
const InputWrapper = styled(RowBetween)`
width: 200px;
background-color: ${({ theme }) => theme.inputBackground};
border-radius: 8px;
padding: 4px 8px;
border: 1px solid transparent;
border: ${({ active, error, theme }) =>
error ? '1px solid ' + theme.salmonRed : active ? '1px solid ' + theme.royalBlue : ''};
`
const SLIPPAGE_INDEX = {
1: 1,
2: 2,
3: 3,
4: 4
}
export default function AdvancedSettings({ setIsOpen, setDeadline, setAllowedSlippage }) {
const [deadlineInput, setDeadlineInput] = useState(15)
const [slippageInput, setSlippageInput] = useState()
const [activeIndex, setActiveIndex] = useState(SLIPPAGE_INDEX[3])
const [slippageInputError, setSlippageInputError] = useState(null) // error
function parseCustomInput(val) {
const acceptableValues = [/^$/, /^\d{1,2}$/, /^\d{0,2}\.\d{0,2}$/]
if (val > 5) {
setSlippageInputError('Your transaction may be front-run.')
} else {
setSlippageInputError(null)
}
if (acceptableValues.some(a => a.test(val))) {
setSlippageInput(val)
setAllowedSlippage(val * 100)
}
}
function parseCustomDeadline(val) {
const acceptableValues = [/^$/, /^\d+$/]
if (acceptableValues.some(re => re.test(val))) {
setDeadlineInput(val)
setDeadline(val * 60)
}
}
return (
<AutoColumn gap="20px">
<Link
onClick={() => {
setIsOpen(false)
}}
>
back
</Link>
<RowBetween>
<TYPE.main>Limit additional price impact</TYPE.main>
<QuestionHelper text="" />
</RowBetween>
<Row>
<ButtonRadio
active={SLIPPAGE_INDEX[1] === activeIndex}
padding="4px 6px"
borderRadius="8px"
style={{ marginRight: '16px' }}
width={'60px'}
onClick={() => {
setActiveIndex(SLIPPAGE_INDEX[1])
setAllowedSlippage(10)
}}
>
0.1%
</ButtonRadio>
<ButtonRadio
active={SLIPPAGE_INDEX[2] === activeIndex}
padding="4px 6px"
borderRadius="8px"
style={{ marginRight: '16px' }}
width={'60px'}
onClick={() => {
setActiveIndex(SLIPPAGE_INDEX[2])
setAllowedSlippage(100)
}}
>
1%
</ButtonRadio>
<ButtonRadio
active={SLIPPAGE_INDEX[3] === activeIndex}
padding="4px"
borderRadius="8px"
width={'140px'}
onClick={() => {
setActiveIndex(SLIPPAGE_INDEX[3])
setAllowedSlippage(200)
}}
>
2% (suggested)
</ButtonRadio>
</Row>
<RowFixed>
<InputWrapper active={SLIPPAGE_INDEX[4] === activeIndex} error={slippageInputError}>
<NumericalInput
align={slippageInput ? 'right' : 'left'}
value={slippageInput || ''}
onUserInput={val => {
parseCustomInput(val)
setActiveIndex(SLIPPAGE_INDEX[4])
}}
placeHolder="Custom"
onClick={() => {
setActiveIndex(SLIPPAGE_INDEX[4])
if (slippageInput) {
parseCustomInput(slippageInput)
}
}}
/>
%
</InputWrapper>
{slippageInputError && (
<TYPE.error error={true} fontSize={12} style={{ marginLeft: '10px' }}>
Your transaction may be front-run
</TYPE.error>
)}
</RowFixed>
<RowBetween>
<TYPE.main>Adjust deadline (minutes from now)</TYPE.main>
</RowBetween>
<RowFixed>
<InputWrapper>
<NumericalInput
value={deadlineInput}
onUserInput={val => {
parseCustomDeadline(val)
}}
/>
</InputWrapper>
</RowFixed>
</AutoColumn>
)
}

@ -1,15 +1,14 @@
import React from 'react'
import { Button as RebassButton } from 'rebass/styled-components'
import styled from 'styled-components'
import { darken, lighten } from 'polished'
import { RowBetween } from '../Row'
import { ChevronDown } from 'react-feather'
import { Button as RebassButton } from 'rebass/styled-components'
const Base = styled(RebassButton)`
padding: ${({ padding }) => (padding ? padding : '18px')};
width: ${({ width }) => (width ? width : '100%')};
font-size: 1rem;
font-weight: 500;
text-align: center;
border-radius: 20px;
@ -39,7 +38,7 @@ export const ButtonPrimary = styled(Base)`
}
&:disabled {
background-color: ${({ theme }) => theme.outlineGrey};
color: ${({ theme }) => theme.darkGrey}
color: ${({ theme }) => theme.darkGray}
cursor: auto;
box-shadow: none;
}
@ -48,6 +47,7 @@ export const ButtonPrimary = styled(Base)`
export const ButtonSecondary = styled(Base)`
background-color: #ebf4ff;
color: #2172e5;
font-size: 16px;
border-radius: 8px;
padding: 10px;
@ -69,6 +69,30 @@ export const ButtonSecondary = styled(Base)`
}
`
export const ButtonPink = styled(Base)`
background-color: ${({ theme }) => theme.darkPink};
color: white;
padding: 10px;
&:focus {
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.05, theme.darkPink)};
background-color: ${({ theme }) => darken(0.05, theme.darkPink)};
}
&:hover {
background-color: ${({ theme }) => darken(0.05, theme.darkPink)};
}
&:active {
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.1, theme.darkPink)};
background-color: ${({ theme }) => darken(0.1, theme.darkPink)};
}
&:disabled {
background-color: ${({ theme }) => theme.darkPink};
opacity: 50%;
cursor: auto;
}
`
export const ButtonEmpty = styled(Base)`
border: 1px solid #edeef2;
background-color: transparent;
@ -157,7 +181,7 @@ export function ButtonError({ children, error, ...rest }) {
}
}
export function ButtonDropwdown({ disabled, children, ...rest }) {
export function ButtonDropwdown({ disabled = false, children, ...rest }) {
return (
<ButtonPrimary {...rest}>
<RowBetween>
@ -168,7 +192,7 @@ export function ButtonDropwdown({ disabled, children, ...rest }) {
)
}
export function ButtonDropwdownLight({ disabled, children, ...rest }) {
export function ButtonDropwdownLight({ disabled = false, children, ...rest }) {
return (
<ButtonEmpty {...rest}>
<RowBetween>

@ -1,4 +1,6 @@
import React from 'react'
import styled from 'styled-components'
import { Text } from 'rebass'
import { Box } from 'rebass/styled-components'
const Card = styled(Box)`
@ -18,3 +20,21 @@ export const LightCard = styled(Card)`
export const GreyCard = styled(Card)`
background-color: rgba(255, 255, 255, 0.9);
`
const BlueCardStyled = styled(Card)`
background-color: #ebf4ff;
color: #2172e5;
border-radius: 12px;
padding: 8px;
width: fit-content;
`
export const BlueCard = ({ children }) => {
return (
<BlueCardStyled>
<Text textAlign="center" fontWeight={500} color="#2172E5">
{children}
</Text>
</BlueCardStyled>
)
}

@ -1,127 +0,0 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import ReactGA from 'react-ga'
import { ReactComponent as Dropup } from '../../assets/images/dropup-blue.svg'
import { ReactComponent as Dropdown } from '../../assets/images/dropdown-blue.svg'
const SummaryWrapper = styled.div`
color: ${({ error, theme }) => (error ? theme.salmonRed : theme.doveGray)};
font-size: 0.75rem;
text-align: center;
margin-top: 1rem;
padding-top: 1rem;
`
const Details = styled.div`
background-color: ${({ theme }) => theme.concreteGray};
padding: 1.5rem;
border-radius: 1rem;
font-size: 0.75rem;
margin-top: 1rem;
`
const SummaryWrapperContainer = styled.div`
${({ theme }) => theme.flexRowNoWrap};
color: ${({ theme }) => theme.royalBlue};
text-align: center;
margin-top: 1rem;
padding-top: 1rem;
cursor: pointer;
align-items: center;
justify-content: center;
font-size: 0.75rem;
span {
margin-right: 12px;
}
img {
height: 0.75rem;
width: 0.75rem;
}
`
const WrappedDropup = ({ isError, highSlippageWarning, ...rest }) => <Dropup {...rest} />
const ColoredDropup = styled(WrappedDropup)`
path {
stroke: ${({ theme }) => theme.royalBlue};
}
`
const WrappedDropdown = ({ isError, highSlippageWarning, ...rest }) => <Dropdown {...rest} />
const ColoredDropdown = styled(WrappedDropdown)`
path {
stroke: ${({ theme }) => theme.royalBlue};
}
`
class ContextualInfo extends Component {
static propTypes = {
openDetailsText: PropTypes.string,
renderTransactionDetails: PropTypes.func,
contextualInfo: PropTypes.string,
isError: PropTypes.bool
}
static defaultProps = {
openDetailsText: 'Advanced Details',
closeDetailsText: 'Hide Advanced',
renderTransactionDetails() {},
contextualInfo: '',
isError: false
}
state = {
showDetails: false
}
renderDetails() {
if (!this.state.showDetails) {
return null
}
return <Details>{this.props.renderTransactionDetails()}</Details>
}
render() {
const { openDetailsText, closeDetailsText, contextualInfo, isError } = this.props
if (contextualInfo) {
return <SummaryWrapper error={isError}>{contextualInfo}</SummaryWrapper>
}
return (
<>
<SummaryWrapperContainer
onClick={() => {
!this.state.showDetails &&
ReactGA.event({
category: 'Advanced Interaction',
action: 'Open Advanced Details',
label: 'Pool Page Details'
})
this.setState(prevState => {
return { showDetails: !prevState.showDetails }
})
}}
>
{!this.state.showDetails ? (
<>
<span>{openDetailsText}</span>
<ColoredDropup />
</>
) : (
<>
<span>{closeDetailsText}</span>
<ColoredDropdown />
</>
)}
</SummaryWrapperContainer>
{this.renderDetails()}
</>
)
}
}
export default ContextualInfo

@ -1,133 +0,0 @@
import React, { useState } from 'react'
import styled, { css } from 'styled-components'
import { transparentize } from 'polished'
import ReactGA from 'react-ga'
import { ReactComponent as Dropup } from '../../assets/images/dropup-blue.svg'
import { ReactComponent as Dropdown } from '../../assets/images/dropdown-blue.svg'
const SummaryWrapper = styled.div`
color: ${({ error, brokenTokenWarning, theme }) => (error || brokenTokenWarning ? theme.salmonRed : theme.doveGray)};
font-size: 0.75rem;
text-align: center;
margin-top: 1rem;
padding-top: 1rem;
`
const SummaryWrapperContainer = styled.div`
${({ theme }) => theme.flexRowNoWrap};
color: ${({ theme }) => theme.royalBlue};
text-align: center;
margin-top: 1rem;
padding-top: 1rem;
cursor: pointer;
align-items: center;
justify-content: center;
font-size: 0.75rem;
img {
height: 0.75rem;
width: 0.75rem;
}
`
const Details = styled.div`
background-color: ${({ theme }) => theme.concreteGray};
/* padding: 1.25rem 1.25rem 1rem 1.25rem; */
border-radius: 1rem;
font-size: 0.75rem;
margin: 1rem 0.5rem 0 0.5rem;
`
const ErrorSpan = styled.span`
margin-right: 12px;
font-size: 0.75rem;
line-height: 0.75rem;
color: ${({ isError, theme }) => isError && theme.salmonRed};
${({ slippageWarning, highSlippageWarning, theme }) =>
highSlippageWarning
? css`
color: ${theme.salmonRed};
font-weight: 600;
`
: slippageWarning &&
css`
background-color: ${transparentize(0.6, theme.warningYellow)};
font-weight: 600;
padding: 0.25rem;
`}
`
const WrappedDropup = ({ isError, highSlippageWarning, ...rest }) => <Dropup {...rest} />
const ColoredDropup = styled(WrappedDropup)`
path {
stroke: ${({ isError, theme }) => (isError ? theme.salmonRed : theme.royalBlue)};
${({ highSlippageWarning, theme }) =>
highSlippageWarning &&
css`
stroke: ${theme.salmonRed};
`}
}
`
const WrappedDropdown = ({ isError, highSlippageWarning, ...rest }) => <Dropdown {...rest} />
const ColoredDropdown = styled(WrappedDropdown)`
path {
stroke: ${({ isError, theme }) => (isError ? theme.salmonRed : theme.royalBlue)};
${({ highSlippageWarning, theme }) =>
highSlippageWarning &&
css`
stroke: ${theme.salmonRed};
`}
}
`
export default function ContextualInfo({
openDetailsText = 'Advanced Details',
closeDetailsText = 'Hide Advanced',
contextualInfo = '',
allowExpand = false,
isError = false,
slippageWarning,
highSlippageWarning,
brokenTokenWarning,
dropDownContent
}) {
const [showDetails, setShowDetails] = useState(false)
return !allowExpand ? (
<SummaryWrapper brokenTokenWarning={brokenTokenWarning}>{contextualInfo}</SummaryWrapper>
) : (
<>
<SummaryWrapperContainer
onClick={() => {
!showDetails &&
ReactGA.event({
category: 'Advanced Interaction',
action: 'Open Advanced Details',
label: 'Swap/Send Page Details'
})
setShowDetails(s => !s)
}}
>
<>
<ErrorSpan isError={isError} slippageWarning={slippageWarning} highSlippageWarning={highSlippageWarning}>
{(slippageWarning || highSlippageWarning) && (
<span role="img" aria-label="warning">
</span>
)}
{contextualInfo ? contextualInfo : showDetails ? closeDetailsText : openDetailsText}
</ErrorSpan>
{showDetails ? (
<ColoredDropup isError={isError} highSlippageWarning={highSlippageWarning} />
) : (
<ColoredDropdown isError={isError} highSlippageWarning={highSlippageWarning} />
)}
</>
</SummaryWrapperContainer>
{showDetails && <Details>{dropDownContent()}</Details>}
</>
)
}

@ -8,6 +8,7 @@ import { WETH } from '@uniswap/sdk'
import TokenLogo from '../TokenLogo'
import DoubleLogo from '../DoubleLogo'
import SearchModal from '../SearchModal'
import { TYPE } from '../../theme'
import { Text } from 'rebass'
import { RowBetween } from '../Row'
import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg'
@ -20,6 +21,8 @@ import { calculateGasMargin } from '../../utils'
import { useAddressBalance } from '../../contexts/Balances'
import { useTransactionAdder, usePendingApproval } from '../../contexts/Transactions'
import { ROUTER_ADDRESSES } from '../../constants'
const GAS_MARGIN = ethers.utils.bigNumberify(1000)
const SubCurrencySelect = styled.button`
@ -51,26 +54,18 @@ const CurrencySelect = styled.button`
font-size: 20px;
background-color: ${({ selected, theme }) => (selected ? theme.buttonBackgroundPlain : theme.royalBlue)};
color: ${({ selected, theme }) => (selected ? theme.textColor : theme.white)};
border: 1px solid
${({ selected, theme, disableTokenSelect }) =>
disableTokenSelect ? theme.buttonBackgroundPlain : selected ? theme.buttonOutlinePlain : theme.royalBlue};
border-radius: 8px;
outline: none;
cursor: pointer;
user-select: none;
border: 1px solid
${({ selected, theme }) => (selected ? darken(0.1, theme.outlineGrey) : darken(0.1, theme.royalBlue))};
:focus,
:hover {
border: 1px solid
${({ selected, theme }) => (selected ? darken(0.1, theme.outlineGrey) : darken(0.1, theme.royalBlue))};
}
:focus {
border: 1px solid ${({ theme }) => darken(0.1, theme.royalBlue)};
}
:active {
background-color: ${({ selected, theme }) =>
selected ? darken(0.1, theme.zumthorBlue) : darken(0.1, theme.royalBlue)};
${({ selected, theme }) => (selected ? darken(0.2, theme.outlineGrey) : darken(0.2, theme.royalBlue))};
}
`
@ -99,12 +94,8 @@ const InputPanel = styled.div`
const Container = styled.div`
border-radius: ${({ hideInput }) => (hideInput ? '8px' : '20px')};
border: 1px solid ${({ error, theme }) => (error ? theme.salmonRed : theme.mercuryGray)};
border: 1px solid ${({ error, theme }) => (error ? theme.salmonRed : theme.backgroundColor)};
background-color: ${({ theme }) => theme.inputBackground};
:focus-within {
border: 1px solid ${({ error, theme }) => (error ? theme.salmonRed : theme.malibuBlue)};
}
`
const LabelRow = styled.div`
@ -113,7 +104,7 @@ const LabelRow = styled.div`
color: ${({ theme }) => theme.doveGray};
font-size: 0.75rem;
line-height: 1rem;
padding: 0.75rem 1rem 0;
padding: 0.5rem 1rem 1rem 1rem;
span:hover {
cursor: pointer;
color: ${({ theme }) => darken(0.2, theme.doveGray)};
@ -161,12 +152,12 @@ export default function CurrencyInputPanel({
value,
field,
onUserInput,
onTokenSelection = null,
title,
onMax,
atMax,
error,
urlAddedTokens = [], // used
onTokenSelection = null,
token = null,
showUnlock = false, // used to show unlock if approval needed
disableUnlock = false,
@ -176,23 +167,19 @@ export default function CurrencyInputPanel({
exchange = null, // used for double token logo
customBalance = null, // used for LP balances instead of token balance
hideInput = false,
showSendWithSwap = false,
onTokenSelectSendWithSwap = null
showSendWithSwap = false
}) {
const { account, chainId } = useWeb3React()
const { t } = useTranslation()
const addTransaction = useTransactionAdder()
const { account, chainId } = useWeb3React()
const routerAddress = ROUTER_ADDRESSES[chainId]
const addTransaction = useTransactionAdder()
const [modalOpen, setModalOpen] = useState(false)
// this one causes the infinite loop
const userTokenBalance = useAddressBalance(account, token)
const tokenContract = useTokenContract(token?.address)
const pendingApproval = usePendingApproval(token?.address)
const routerAddress = '0xd9210Ff5A0780E083BB40e30d005d93a2DcFA4EF'
function renderUnlockButton() {
if (
disableUnlock ||
@ -240,19 +227,6 @@ export default function CurrencyInputPanel({
return (
<InputPanel>
<Container error={!!error} hideInput={hideInput}>
{!hideBalance && (
<LabelRow>
<RowBetween>
<Text>{title}</Text>
<ErrorSpan data-tip={'Enter max'} error={!!error} onClick={() => {}}></ErrorSpan>
<ClickableText onClick={onMax}>
<Text>
Balance: {customBalance ? customBalance?.toSignificant(4) : userTokenBalance?.toSignificant(4)}
</Text>
</ClickableText>
</RowBetween>
</LabelRow>
)}
<InputRow style={hideInput ? { padding: '0', borderRadius: '8px' } : {}} hideInput={hideInput}>
{!hideInput && (
<>
@ -267,7 +241,7 @@ export default function CurrencyInputPanel({
</>
)}
<CurrencySelect
selected={!!token?.address}
selected={!!token}
onClick={() => {
if (!disableTokenSelect) {
setModalOpen(true)
@ -292,6 +266,19 @@ export default function CurrencyInputPanel({
</Aligner>
</CurrencySelect>
</InputRow>
{!hideBalance && !!token && (
<LabelRow>
<RowBetween>
<Text>{'-'}</Text>
<ErrorSpan data-tip={'Enter max'} error={!!error} onClick={() => {}}></ErrorSpan>
<ClickableText onClick={onMax}>
<TYPE.body>
Balance: {customBalance ? customBalance?.toSignificant(4) : userTokenBalance?.toSignificant(4)}
</TYPE.body>
</ClickableText>
</RowBetween>
</LabelRow>
)}
</Container>
{!disableTokenSelect && (
<SearchModal
@ -304,7 +291,6 @@ export default function CurrencyInputPanel({
field={field}
onTokenSelect={onTokenSelection}
showSendWithSwap={showSendWithSwap}
onTokenSelectSendWithSwap={onTokenSelectSendWithSwap}
/>
)}
</InputPanel>

@ -2,33 +2,34 @@ import React, { useState, useReducer, useCallback, useEffect } from 'react'
import styled from 'styled-components'
import { ethers } from 'ethers'
import { parseUnits, parseEther } from '@ethersproject/units'
import { WETH, TradeType, Route, Trade, TokenAmount, JSBI } from '@uniswap/sdk'
import { WETH, TradeType, Route, Exchange, Trade, TokenAmount, JSBI, Percent } from '@uniswap/sdk'
import QR from '../../assets/svg/QR.svg'
import TokenLogo from '../TokenLogo'
import AddressInputPanel from '../AddressInputPanel'
import QuestionHelper from '../Question'
import NumericalInput from '../NumericalInput'
import AdvancedSettings from '../AdvancedSettings'
import ConfirmationModal from '../ConfirmationModal'
import CurrencyInputPanel from '../CurrencyInputPanel'
import { Link } from '../../theme/components'
import { Text } from 'rebass'
import { TYPE } from '../../theme'
import { GreyCard, LightCard } from '../../components/Card'
import { ArrowDown, ArrowUp } from 'react-feather'
import { ButtonPrimary, ButtonError, ButtonRadio } from '../Button'
import { GreyCard, BlueCard } from '../../components/Card'
import { AutoColumn, ColumnCenter } from '../../components/Column'
import Row, { RowBetween, RowFixed } from '../../components/Row'
import { ButtonPrimary, ButtonError } from '../Button'
import { RowBetween, RowFixed, AutoRow } from '../../components/Row'
import { usePopups } from '../../contexts/Application'
import { useToken } from '../../contexts/Tokens'
import { usePopups } from '../../contexts/Application'
import { useExchange } from '../../contexts/Exchanges'
import { useWeb3React, useTokenContract } from '../../hooks'
import { useAddressBalance } from '../../contexts/Balances'
import { useTransactionAdder } from '../../contexts/Transactions'
import { useAddressAllowance } from '../../contexts/Allowances'
import { useWeb3React, useTokenContract } from '../../hooks'
import { useAddressBalance, useAllBalances } from '../../contexts/Balances'
import { ROUTER_ADDRESSES } from '../../constants'
import { getRouterContract, calculateGasMargin, isAddress, getProviderOrSigner } from '../../utils'
import { getRouterContract, calculateGasMargin, getProviderOrSigner } from '../../utils'
const Wrapper = styled.div`
position: relative;
@ -59,42 +60,11 @@ const ErrorText = styled(Text)`
warningHigh ? theme.salmonRed : warningMedium ? theme.warningYellow : theme.textColor};
`
const InputWrapper = styled(RowBetween)`
width: 200px;
background-color: ${({ theme }) => theme.inputBackground};
border-radius: 8px;
padding: 4px 8px;
border: 1px solid transparent;
border: ${({ active, error, theme }) =>
error ? '1px solid ' + theme.salmonRed : active ? '1px solid ' + theme.royalBlue : ''};
`
const InputGroup = styled(AutoColumn)`
position: relative;
padding: 40px 0;
`
const QRWrapper = styled.div`
display: flex;
align-items: center;
justify-content: center;
border: 1px solid ${({ theme }) => theme.outlineGrey};
background: #fbfbfb;
padding: 4px;
border-radius: 8px;
`
const StyledInput = styled.input`
width: ${({ width }) => width};
border: none;
outline: none;
font-size: 20px;
::placeholder {
color: #edeef2;
}
`
const StyledNumerical = styled(NumericalInput)`
text-align: center;
font-size: 48px;
@ -227,13 +197,6 @@ function hex(value: JSBI) {
return ethers.utils.bigNumberify(value.toString())
}
const SLIPPAGE_INDEX = {
1: 1,
2: 2,
3: 3,
4: 4
}
const SWAP_TYPE = {
EXACT_TOKENS_FOR_TOKENS: 'EXACT_TOKENS_FOR_TOKENS',
EXACT_TOKENS_FOR_ETH: 'EXACT_TOKENS_FOR_ETH',
@ -257,52 +220,50 @@ const ALLOWED_SLIPPAGE_HIGH = 500
export default function ExchangePage({ sendingInput = false }) {
const { chainId, account, library } = useWeb3React()
const routerAddress = ROUTER_ADDRESSES[chainId]
const routerAddress: string = ROUTER_ADDRESSES[chainId]
// adding notifications on txns
const [, addPopup] = usePopups()
const addTransaction = useTransactionAdder()
// sending state
const [sending, setSending] = useState(sendingInput)
const [sendingWithSwap, setSendingWithSwap] = useState(false)
const [recipient, setRecipient] = useState('')
const [sending] = useState<boolean>(sendingInput)
const [sendingWithSwap, setSendingWithSwap] = useState<boolean>(false)
const [recipient, setRecipient] = useState<string>('')
// input details
// trade details
const [state, dispatch] = useReducer(reducer, WETH[chainId].address, initializeSwapState)
const { independentField, typedValue, ...fieldData } = state
const dependentField = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
const tradeType = independentField === Field.INPUT ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT
const [tradeError, setTradeError] = useState('') // error for things like reserve size or route
const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
const tradeType: TradeType = independentField === Field.INPUT ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT
const [tradeError, setTradeError] = useState<string>('') // error for things like reserve size or route
const tokens = {
[Field.INPUT]: useToken(fieldData[Field.INPUT].address),
[Field.OUTPUT]: useToken(fieldData[Field.OUTPUT].address)
}
const exchange = useExchange(tokens[Field.INPUT], tokens[Field.OUTPUT])
const route = !!exchange ? new Route([exchange], tokens[Field.INPUT]) : undefined
const exchange: Exchange = useExchange(tokens[Field.INPUT], tokens[Field.OUTPUT])
const route: Route = !!exchange ? new Route([exchange], tokens[Field.INPUT]) : undefined
const emptyReserves: boolean = exchange && JSBI.equal(JSBI.BigInt(0), exchange.reserve0.raw)
// modal and loading
const [showConfirm, setShowConfirm] = useState(false)
const [pendingConfirmation, setPendingConfirmation] = useState(true) // waiting for user confirmation
const [attemptingTxn, setAttemptingTxn] = useState(false) // clicked confirmed
// advanced settings
const [showAdvanced, setShowAdvanced] = useState(false)
const [activeIndex, setActiveIndex] = useState(SLIPPAGE_INDEX[3])
const [customSlippage, setCustomSlippage] = useState()
const [customDeadline, setCustomDeadline] = useState(DEFAULT_DEADLINE_FROM_NOW / 60)
const [slippageInputError, setSlippageInputError] = useState(null)
const [showConfirm, setShowConfirm] = useState<boolean>(false)
const [showAdvanced, setShowAdvanced] = useState<boolean>(false)
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // clicked confirmed
const [pendingConfirmation, setPendingConfirmation] = useState<boolean>(true) // waiting for user confirmation
// txn values
const [txHash, setTxHash] = useState()
const [deadline, setDeadline] = useState(DEFAULT_DEADLINE_FROM_NOW)
const [allowedSlippage, setAllowedSlippage] = useState(INITIAL_ALLOWED_SLIPPAGE)
const [txHash, setTxHash] = useState<string>('')
const [deadline, setDeadline] = useState<number>(DEFAULT_DEADLINE_FROM_NOW)
const [allowedSlippage, setAllowedSlippage] = useState<number>(INITIAL_ALLOWED_SLIPPAGE)
// approvals
const inputApproval = useAddressAllowance(account, tokens[Field.INPUT], routerAddress)
const outputApproval = useAddressAllowance(account, tokens[Field.OUTPUT], routerAddress)
const inputApproval: TokenAmount = useAddressAllowance(account, tokens[Field.INPUT], routerAddress)
const outputApproval: TokenAmount = useAddressAllowance(account, tokens[Field.OUTPUT], routerAddress)
// all balances for detecting a swap with send
const allBalances: TokenAmount[] = useAllBalances()
// get user- and token-specific lookup data
const userBalances = {
@ -311,18 +272,13 @@ export default function ExchangePage({ sendingInput = false }) {
}
const parsedAmounts: { [field: number]: TokenAmount } = {}
// try to parse typed value
if (typedValue !== '' && typedValue !== '.' && tokens[independentField]) {
try {
const typedValueParsed = parseUnits(typedValue, tokens[independentField].decimals).toString()
if (typedValueParsed !== '0')
parsedAmounts[independentField] = new TokenAmount(tokens[independentField], typedValueParsed)
} catch (error) {
// should only fail if the user specifies too many decimal places of precision (or maybe exceed max uint?)
/**
* @todo reserve limit error here
*/
console.error('found error here ')
console.error(error)
}
}
@ -335,12 +291,11 @@ export default function ExchangePage({ sendingInput = false }) {
: undefined
} catch (error) {}
const slippageFromTrade = trade && trade.slippage
const slippageFromTrade: Percent = trade && trade.slippage
if (trade)
parsedAmounts[dependentField] = tradeType === TradeType.EXACT_INPUT ? trade.outputAmount : trade.inputAmount
// get formatted amounts
const formattedAmounts = {
[independentField]: typedValue,
[dependentField]: parsedAmounts[dependentField] ? parsedAmounts[dependentField].toSignificant(8) : ''
@ -384,8 +339,8 @@ export default function ExchangePage({ sendingInput = false }) {
})
}, [])
const MIN_ETHER = chainId && new TokenAmount(WETH[chainId], JSBI.BigInt(parseEther('.01')))
const maxAmountInput =
const MIN_ETHER: TokenAmount = chainId && new TokenAmount(WETH[chainId], JSBI.BigInt(parseEther('.01')))
const maxAmountInput: TokenAmount =
!!userBalances[Field.INPUT] &&
JSBI.greaterThan(
userBalances[Field.INPUT].raw,
@ -395,22 +350,22 @@ export default function ExchangePage({ sendingInput = false }) {
? userBalances[Field.INPUT].subtract(MIN_ETHER)
: userBalances[Field.INPUT]
: undefined
const atMaxAmountInput =
const atMaxAmountInput: boolean =
!!maxAmountInput && !!parsedAmounts[Field.INPUT]
? JSBI.equal(maxAmountInput.raw, parsedAmounts[Field.INPUT].raw)
: undefined
const maxAmountOutput =
const maxAmountOutput: TokenAmount =
!!userBalances[Field.OUTPUT] && JSBI.greaterThan(userBalances[Field.OUTPUT].raw, JSBI.BigInt(0))
? userBalances[Field.OUTPUT]
: undefined
const atMaxAmountOutput =
const atMaxAmountOutput: boolean =
!!maxAmountOutput && !!parsedAmounts[Field.OUTPUT]
? JSBI.equal(maxAmountOutput.raw, parsedAmounts[Field.OUTPUT].raw)
: undefined
function getSwapType() {
function getSwapType(): string {
if (tradeType === TradeType.EXACT_INPUT) {
if (tokens[Field.INPUT] === WETH[chainId]) {
return SWAP_TYPE.EXACT_ETH_FOR_TOKENS
@ -438,7 +393,7 @@ export default function ExchangePage({ sendingInput = false }) {
return null
}
const slippageAdjustedAmounts = {
const slippageAdjustedAmounts: { [field: number]: TokenAmount } = {
[Field.INPUT]:
Field.INPUT === independentField
? parsedAmounts[Field.INPUT]
@ -451,37 +406,15 @@ export default function ExchangePage({ sendingInput = false }) {
new TokenAmount(tokens[Field.INPUT], calculateSlippageAmount(parsedAmounts[Field.OUTPUT])?.[0])
}
const showInputUnlock =
const showInputUnlock: boolean =
parsedAmounts[Field.INPUT] && inputApproval && JSBI.greaterThan(parsedAmounts[Field.INPUT].raw, inputApproval.raw)
const showOutputUnlock =
const showOutputUnlock: boolean =
parsedAmounts[Field.OUTPUT] &&
outputApproval &&
JSBI.greaterThan(parsedAmounts[Field.OUTPUT].raw, outputApproval.raw)
// parse the input for custom slippage
function parseCustomInput(val) {
const acceptableValues = [/^$/, /^\d{1,2}$/, /^\d{0,2}\.\d{0,2}$/]
if (val > 5) {
setSlippageInputError('Your transaction may be front-run.')
} else {
setSlippageInputError(null)
}
if (acceptableValues.some(a => a.test(val))) {
setCustomSlippage(val)
setAllowedSlippage(val * 100)
}
}
function parseCustomDeadline(val) {
const acceptableValues = [/^$/, /^\d+$/]
if (acceptableValues.some(re => re.test(val))) {
setCustomDeadline(val)
setDeadline(val * 60)
}
}
const tokenContract = useTokenContract(tokens[Field.INPUT]?.address)
const tokenContract: ethers.Contract = useTokenContract(tokens[Field.INPUT]?.address)
// function for a pure send
async function onSend() {
@ -494,7 +427,6 @@ export default function ExchangePage({ sendingInput = false }) {
signer
.sendTransaction({ to: recipient.toString(), value: hex(parsedAmounts[Field.INPUT].raw) })
.then(response => {
console.log(response)
setTxHash(response.hash)
addTransaction(response)
setPendingConfirmation(false)
@ -537,17 +469,17 @@ export default function ExchangePage({ sendingInput = false }) {
}
}
// covers swap or swap with send
async function onSwap() {
const routerContract = getRouterContract(chainId, library, account)
setAttemptingTxn(true)
const routerContract: ethers.Contract = getRouterContract(chainId, library, account)
setAttemptingTxn(true) // mark that user is attempting transaction
const path = Object.keys(route.path).map(key => {
return route.path[key].address
})
let estimate: Function, method: Function, args, value
const deadlineFromNow = Math.ceil(Date.now() / 1000) + deadline
let estimate: Function, method: Function, args: any[], value: ethers.utils.BigNumber
const deadlineFromNow: number = Math.ceil(Date.now() / 1000) + deadline
const swapType = getSwapType()
switch (swapType) {
@ -558,7 +490,7 @@ export default function ExchangePage({ sendingInput = false }) {
slippageAdjustedAmounts[Field.INPUT].raw.toString(),
slippageAdjustedAmounts[Field.OUTPUT].raw.toString(),
path,
account,
sending ? recipient : account,
deadlineFromNow
]
value = ethers.constants.Zero
@ -570,7 +502,7 @@ export default function ExchangePage({ sendingInput = false }) {
slippageAdjustedAmounts[Field.OUTPUT].raw.toString(),
slippageAdjustedAmounts[Field.INPUT].raw.toString(),
path,
account,
sending ? recipient : account,
deadlineFromNow
]
value = ethers.constants.Zero
@ -578,7 +510,12 @@ export default function ExchangePage({ sendingInput = false }) {
case SWAP_TYPE.EXACT_ETH_FOR_TOKENS:
estimate = routerContract.estimate.swapExactETHForTokens
method = routerContract.swapExactETHForTokens
args = [slippageAdjustedAmounts[Field.OUTPUT].raw.toString(), path, account, deadlineFromNow]
args = [
slippageAdjustedAmounts[Field.OUTPUT].raw.toString(),
path,
sending ? recipient : account,
deadlineFromNow
]
value = hex(slippageAdjustedAmounts[Field.INPUT].raw)
break
case SWAP_TYPE.TOKENS_FOR_EXACT_ETH:
@ -588,7 +525,7 @@ export default function ExchangePage({ sendingInput = false }) {
slippageAdjustedAmounts[Field.OUTPUT].raw.toString(),
slippageAdjustedAmounts[Field.INPUT].raw.toString(),
path,
account,
sending ? recipient : account,
deadlineFromNow
]
value = ethers.constants.Zero
@ -600,7 +537,7 @@ export default function ExchangePage({ sendingInput = false }) {
slippageAdjustedAmounts[Field.INPUT].raw.toString(),
slippageAdjustedAmounts[Field.OUTPUT].raw.toString(),
path,
account,
sending ? recipient : account,
deadlineFromNow
]
value = ethers.constants.Zero
@ -608,13 +545,18 @@ export default function ExchangePage({ sendingInput = false }) {
case SWAP_TYPE.ETH_FOR_EXACT_TOKENS:
estimate = routerContract.estimate.swapETHForExactTokens
method = routerContract.swapETHForExactTokens
args = [slippageAdjustedAmounts[Field.OUTPUT].raw.toString(), path, account, deadlineFromNow]
args = [
slippageAdjustedAmounts[Field.OUTPUT].raw.toString(),
path,
sending ? recipient : account,
deadlineFromNow
]
value = hex(slippageAdjustedAmounts[Field.INPUT].raw)
break
}
const estimatedGasLimit = await estimate(...args, { value }).catch(e => {
console.log('error getting gas limit')
console.log(e)
})
method(...args, {
@ -626,7 +568,7 @@ export default function ExchangePage({ sendingInput = false }) {
addTransaction(response)
setPendingConfirmation(false)
})
.catch(e => {
.catch(() => {
addPopup(
<AutoColumn gap="10px">
<Text>Transaction Failed: try again.</Text>
@ -638,13 +580,13 @@ export default function ExchangePage({ sendingInput = false }) {
}
// errors
const [generalError, setGeneralError] = useState('')
const [inputError, setInputError] = useState('')
const [outputError, setOutputError] = useState('')
const [recipientError, setRecipientError] = useState('')
const [isValid, setIsValid] = useState(false)
const [generalError, setGeneralError] = useState<string>('')
const [inputError, setInputError] = useState<string>('')
const [outputError, setOutputError] = useState<string>('')
const [recipientError, setRecipientError] = useState<string>('')
const [isValid, setIsValid] = useState<boolean>(false)
const ignoreOutput = sending ? !sendingWithSwap : false
const ignoreOutput: boolean = sending ? !sendingWithSwap : false
useEffect(() => {
// reset errors
@ -652,11 +594,9 @@ export default function ExchangePage({ sendingInput = false }) {
setInputError(null)
setOutputError(null)
setTradeError(null)
setRecipientError(null)
setIsValid(true)
if (!isAddress(recipient) && sending) {
setRecipientError('Invalid Recipient')
if (recipientError) {
setIsValid(false)
}
@ -675,7 +615,7 @@ export default function ExchangePage({ sendingInput = false }) {
exchange &&
JSBI.greaterThan(parsedAmounts[Field.INPUT].raw, exchange.reserveOf(tokens[Field.INPUT]).raw)
) {
setTradeError('Low Liquidity Error')
setTradeError('Insufficient Liquidity')
setIsValid(false)
}
@ -685,7 +625,7 @@ export default function ExchangePage({ sendingInput = false }) {
exchange &&
JSBI.greaterThan(parsedAmounts[Field.OUTPUT].raw, exchange.reserveOf(tokens[Field.OUTPUT]).raw)
) {
setTradeError('Low Liquidity Error')
setTradeError('Insufficient Liquidity')
setIsValid(false)
}
@ -712,6 +652,7 @@ export default function ExchangePage({ sendingInput = false }) {
ignoreOutput,
parsedAmounts,
recipient,
recipientError,
sending,
sendingWithSwap,
showInputUnlock,
@ -721,9 +662,12 @@ export default function ExchangePage({ sendingInput = false }) {
])
// warnings on slippage
const warningMedium = slippageFromTrade && parseFloat(slippageFromTrade.toFixed(4)) > ALLOWED_SLIPPAGE_MEDIUM / 100
const warningHigh = slippageFromTrade && parseFloat(slippageFromTrade.toFixed(4)) > ALLOWED_SLIPPAGE_HIGH / 100
const warningMedium: boolean =
slippageFromTrade && parseFloat(slippageFromTrade.toFixed(4)) > ALLOWED_SLIPPAGE_MEDIUM / 100
const warningHigh: boolean =
slippageFromTrade && parseFloat(slippageFromTrade.toFixed(4)) > ALLOWED_SLIPPAGE_HIGH / 100
// reset modal state when closed
function resetModal() {
setPendingConfirmation(true)
setAttemptingTxn(false)
@ -736,11 +680,11 @@ export default function ExchangePage({ sendingInput = false }) {
<AutoColumn gap="30px" style={{ marginTop: '40px' }}>
<RowBetween>
<Text fontSize={36} fontWeight={500}>
{parsedAmounts[Field.INPUT]?.toFixed(8)}
{parsedAmounts[Field.INPUT]?.toFixed(8)} {tokens[Field.INPUT]?.symbol}
</Text>
<TokenLogo address={tokens[Field.INPUT]?.address} size={'30px'} />
</RowBetween>
<ArrowDown size={24} color="#888D9B" />
<TYPE.darkGray fontSize={20}>To</TYPE.darkGray>
<TYPE.blue fontSize={36}>
{recipient?.slice(0, 6)}...{recipient?.slice(36, 42)}
</TYPE.blue>
@ -749,6 +693,27 @@ export default function ExchangePage({ sendingInput = false }) {
}
if (sending && sendingWithSwap) {
return (
<AutoColumn gap="30px" style={{ marginTop: '40px' }}>
<AutoColumn gap="10px">
<AutoRow gap="10px">
<TokenLogo address={tokens[Field.OUTPUT]?.address} size={'30px'} />
<Text fontSize={36} fontWeight={500}>
{slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(4)} {tokens[Field.OUTPUT]?.symbol}
</Text>
</AutoRow>
<BlueCard>
Via {parsedAmounts[Field.INPUT]?.toSignificant(4)} {tokens[Field.INPUT]?.symbol} swap
</BlueCard>
</AutoColumn>
<AutoColumn gap="10px">
<TYPE.darkGray fontSize={20}>To</TYPE.darkGray>
<TYPE.blue fontSize={36}>
{recipient?.slice(0, 6)}...{recipient?.slice(36, 42)}
</TYPE.blue>
</AutoColumn>
</AutoColumn>
)
}
if (!sending) {
@ -796,119 +761,29 @@ export default function ExchangePage({ sendingInput = false }) {
</AutoColumn>
)
}
if (sending && sendingWithSwap) {
}
if (showAdvanced) {
return (
<AutoColumn gap="20px">
<Link
onClick={() => {
setShowAdvanced(false)
}}
>
back
</Link>
<RowBetween>
<TYPE.main>Limit additional price impact</TYPE.main>
<QuestionHelper text="" />
</RowBetween>
<Row>
<ButtonRadio
active={SLIPPAGE_INDEX[1] === activeIndex}
padding="4px 6px"
borderRadius="8px"
style={{ marginRight: '16px' }}
width={'60px'}
onClick={() => {
setActiveIndex(SLIPPAGE_INDEX[1])
setAllowedSlippage(10)
}}
>
0.1%
</ButtonRadio>
<ButtonRadio
active={SLIPPAGE_INDEX[2] === activeIndex}
padding="4px 6px"
borderRadius="8px"
style={{ marginRight: '16px' }}
width={'60px'}
onClick={() => {
setActiveIndex(SLIPPAGE_INDEX[2])
setAllowedSlippage(100)
}}
>
1%
</ButtonRadio>
<ButtonRadio
active={SLIPPAGE_INDEX[3] === activeIndex}
padding="4px"
borderRadius="8px"
width={'140px'}
onClick={() => {
setActiveIndex(SLIPPAGE_INDEX[3])
setAllowedSlippage(200)
}}
>
2% (suggested)
</ButtonRadio>
</Row>
<RowFixed>
<InputWrapper active={SLIPPAGE_INDEX[4] === activeIndex} error={slippageInputError}>
<NumericalInput
align={customSlippage ? 'right' : 'left'}
value={customSlippage || ''}
onUserInput={val => {
parseCustomInput(val)
setActiveIndex(SLIPPAGE_INDEX[4])
}}
placeHolder="Custom"
onClick={() => {
setActiveIndex(SLIPPAGE_INDEX[4])
if (customSlippage) {
parseCustomInput(customSlippage)
}
}}
/>
%
</InputWrapper>
{slippageInputError && (
<TYPE.error error={true} fontSize={12} style={{ marginLeft: '10px' }}>
Your transaction may be front-run
</TYPE.error>
)}
</RowFixed>
<RowBetween>
<TYPE.main>Adjust deadline (minutes from now)</TYPE.main>
</RowBetween>
<RowFixed>
<InputWrapper>
<NumericalInput
value={customDeadline}
onUserInput={val => {
parseCustomDeadline(val)
}}
/>
</InputWrapper>
</RowFixed>
</AutoColumn>
<AdvancedSettings
setIsOpen={setShowAdvanced}
setDeadline={setDeadline}
setAllowedSlippage={setAllowedSlippage}
/>
)
}
if (!sending) {
if (!sending || (sending && sendingWithSwap)) {
return (
<>
<RowBetween>
<Text color="#565A69" fontWeight={500} fontSize={16}>
Price
</Text>
<Text fontWeight={500} fontSize={16}>
{`1 ${tokens[Field.INPUT]?.symbol} = ${route && route.midPrice && route.midPrice.adjusted.toFixed(8)} ${
tokens[Field.OUTPUT]?.symbol
}`}
</Text>
</RowBetween>
{route && route.midPrice && !emptyReserves && (
<RowBetween>
<Text color="#565A69" fontWeight={500} fontSize={16}>
Price
</Text>
<Text fontWeight={500} fontSize={16}>
{`1 ${tokens[Field.INPUT]?.symbol} = ${route.midPrice.toFixed(6)} ${tokens[Field.OUTPUT]?.symbol}`}
</Text>
</RowBetween>
)}
<RowBetween>
<Text color="#565A69" fontWeight={500} fontSize={16}>
Slippage <Link onClick={() => setShowAdvanced(true)}>(edit limits)</Link>
@ -919,7 +794,7 @@ export default function ExchangePage({ sendingInput = false }) {
</RowBetween>
<ButtonError onClick={onSwap} error={!!warningHigh} style={{ margin: '10px 0' }}>
<Text fontSize={20} fontWeight={500}>
{warningHigh ? 'Swap Anyway' : 'Swap'}
{warningHigh ? (sending ? 'Send Anyway' : 'Swap Anyway') : sending ? 'Confirm Send' : 'Confirm Swap'}
</Text>
</ButtonError>
<AutoColumn justify="center" gap="20px">
@ -933,7 +808,7 @@ export default function ExchangePage({ sendingInput = false }) {
setShowAdvanced(true)
}}
>
Advanced Options
Advanced
</Link>
</AutoColumn>
</>
@ -941,27 +816,48 @@ export default function ExchangePage({ sendingInput = false }) {
}
}
const pendingText = sending
? `Sending ${parsedAmounts[Field.INPUT]?.toSignificant(6)} ${tokens[Field.INPUT]?.symbol} to ${recipient}`
// text to show while loading
const pendingText: string = sending
? sendingWithSwap
? `Sending ${parsedAmounts[Field.OUTPUT]?.toSignificant(6)} ${tokens[Field.OUTPUT]?.symbol} to ${recipient}`
: `Sending ${parsedAmounts[Field.INPUT]?.toSignificant(6)} ${tokens[Field.INPUT]?.symbol} to ${recipient}`
: ` Swapped ${parsedAmounts[Field.INPUT]?.toSignificant(6)} ${tokens[Field.INPUT]?.symbol} for ${parsedAmounts[
Field.OUTPUT
]?.toSignificant(6)} ${tokens[Field.OUTPUT]?.symbol}`
function _onTokenSelect(address: string) {
const balance = allBalances?.[account]?.[address]
// if no user balance - switch view to a send with swap
const hasBalance = balance && JSBI.greaterThan(JSBI.BigInt(0), balance.raw)
if (!hasBalance && sending) {
onTokenSelection(Field.OUTPUT, address)
setSendingWithSwap(true)
} else {
onTokenSelection(Field.INPUT, address)
}
}
function _onRecipient(result) {
if (result.address) {
setRecipient(result.address)
}
}
return (
<Wrapper>
<ConfirmationModal
isOpen={showConfirm}
title={sendingWithSwap ? 'Confirm swap and send' : sending ? 'Confirm Send' : 'Confirm Swap'}
onDismiss={() => {
resetModal()
setShowConfirm(false)
}}
attemptingTxn={attemptingTxn}
pendingConfirmation={pendingConfirmation}
hash={txHash ? txHash : ''}
hash={txHash}
topContent={modalHeader}
bottomContent={modalBottom}
pendingText={pendingText}
title={sendingWithSwap ? 'Confirm swap and send' : sending ? 'Confirm Send' : 'Confirm Swap'}
/>
{sending && !sendingWithSwap && (
@ -987,11 +883,7 @@ export default function ExchangePage({ sendingInput = false }) {
}}
atMax={atMaxAmountInput}
token={tokens[Field.INPUT]}
onTokenSelection={address => onTokenSelection(Field.INPUT, address)}
onTokenSelectSendWithSwap={address => {
onTokenSelection(Field.OUTPUT, address)
setSendingWithSwap(true)
}}
onTokenSelection={address => _onTokenSelect(address)}
title={'Input'}
error={inputError}
exchange={exchange}
@ -1010,17 +902,17 @@ export default function ExchangePage({ sendingInput = false }) {
<CurrencyInputPanel
field={Field.INPUT}
value={formattedAmounts[Field.INPUT]}
onUserInput={onUserInput}
onMax={() => {
maxAmountInput && onMaxInput(maxAmountInput.toExact())
}}
atMax={atMaxAmountInput}
token={tokens[Field.INPUT]}
onTokenSelection={address => onTokenSelection(Field.INPUT, address)}
title={'Input'}
error={inputError}
exchange={exchange}
showUnlock={showInputUnlock}
onUserInput={onUserInput}
onMax={() => {
maxAmountInput && onMaxInput(maxAmountInput.toExact())
}}
onTokenSelection={address => onTokenSelection(Field.INPUT, address)}
/>
<ColumnCenter>
<ArrowWrapper onClick={onSwapTokens}>
@ -1043,18 +935,20 @@ export default function ExchangePage({ sendingInput = false }) {
exchange={exchange}
showUnlock={showOutputUnlock}
/>
<RowBetween>
<Text fontWeight={500} color="#565A69">
Price
</Text>
<Text fontWeight={500} color="#565A69">
{exchange
? `1 ${tokens[Field.INPUT].symbol} = ${route?.midPrice.toSignificant(6)} ${
tokens[Field.OUTPUT].symbol
}`
: '-'}
</Text>
</RowBetween>
{!emptyReserves && ( // hide price if new exchange
<RowBetween>
<Text fontWeight={500} color="#565A69">
Price
</Text>
<Text fontWeight={500} color="#565A69">
{exchange
? `1 ${tokens[Field.INPUT].symbol} = ${route?.midPrice.toSignificant(6)} ${
tokens[Field.OUTPUT].symbol
}`
: '-'}
</Text>
</RowBetween>
)}
{warningMedium && (
<RowBetween>
<Text fontWeight={500} color="#565A69">
@ -1070,39 +964,54 @@ export default function ExchangePage({ sendingInput = false }) {
{sending && (
<AutoColumn gap="10px">
<LightCard borderRadius={'20px'}>
<RowBetween>
<StyledInput placeholder="Recipient Address" onChange={e => setRecipient(e.target.value)} />
<QRWrapper>
<img src={QR} alt="" />
</QRWrapper>
</RowBetween>
</LightCard>
<AddressInputPanel
title={''}
onChange={_onRecipient}
onError={error => {
if (error) {
setRecipientError('Inavlid Recipient')
} else {
setRecipientError(null)
}
}}
/>
</AutoColumn>
)}
<ButtonError
onClick={() => {
setShowConfirm(true)
}}
disabled={!isValid}
error={!!warningHigh}
>
<Text fontSize={20} fontWeight={500}>
{generalError
? generalError
: inputError
? inputError
: outputError
? outputError
: recipientError
? recipientError
: tradeError
? tradeError
: warningHigh
? 'Swap Anyway'
: 'Swap'}
</Text>
</ButtonError>
{emptyReserves ? (
<RowBetween style={{ margin: '10px 0' }}>
<TYPE.main>No exchange for this pair.</TYPE.main>
<TYPE.blue> Create one now</TYPE.blue>
</RowBetween>
) : (
<ButtonError
onClick={() => {
setShowConfirm(true)
}}
disabled={!isValid}
error={!!warningHigh}
>
<Text fontSize={20} fontWeight={500}>
{generalError
? generalError
: inputError
? inputError
: outputError
? outputError
: recipientError
? recipientError
: tradeError
? tradeError
: warningHigh
? sendingWithSwap
? 'Send Anyway'
: 'Swap Anyway'
: sending
? 'Send'
: 'Swap'}
</Text>
</ButtonError>
)}
</AutoColumn>
{warningHigh && (

@ -1,15 +1,14 @@
import React from 'react'
import styled from 'styled-components'
import Row from '../Row'
import Menu from '../Menu'
import Logo from '../../assets/svg/logo.svg'
import Row from '../Row'
import Card from '../Card'
import Web3Status from '../Web3Status'
import { CloseIcon } from '../../theme/components'
import { X } from 'react-feather'
import { Link } from '../../theme'
import { Text } from 'rebass'
import Card from '../Card'
import { X } from 'react-feather'
import { WETH } from '@uniswap/sdk'
import { isMobile } from 'react-device-detect'
@ -46,9 +45,7 @@ const Title = styled.div`
const TitleText = styled.div`
font-size: 24px;
font-weight: 700;
background: linear-gradient(119.64deg, #fb1868 -5.55%, #ff00f3 154.46%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
color: ${({ theme }) => theme.black};
margin-left: 12px;
`
@ -58,22 +55,21 @@ const AccountElement = styled.div`
display: flex;
flex-direction: row;
align-items: center;
background-color: ${({ theme }) => theme.outlineGrey};
background-color: ${({ theme, active }) => (!active ? theme.white : theme.outlineGrey)};
border: 1px solid ${({ theme }) => theme.outlineGrey};
border-radius: 8px;
padding-left: 8px;
padding-left: ${({ active }) => (active ? '8px' : 0)};
:focus {
border: 1px solid blue;
}
/* width: 100%; */
`
const FixedPopupColumn = styled(AutoColumn)`
position: absolute;
top: 80px;
right: 20px
width: 340px;
width: 380px;
`
const StyledClose = styled(X)`
@ -86,12 +82,10 @@ const StyledClose = styled(X)`
`
const Popup = styled(Card)`
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.04), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04),
0px 24px 32px rgba(0, 0, 0, 0.04);
z-index: 9999;
border-radius: 8px;
padding: 1rem;
background: ${theme => theme.white};
background-color: white;
`
export default function Header() {
@ -115,8 +109,8 @@ export default function Header() {
</Title>
</HeaderElement>
<HeaderElement>
<AccountElement>
{!isMobile ? (
<AccountElement active={!!account}>
{!isMobile && account ? (
<Row style={{ marginRight: '-1.25rem', paddingRight: '1.75rem' }}>
<Text fontWeight={500}> {userEthBalance && userEthBalance?.toFixed(4) + ' ETH'}</Text>
</Row>

@ -1,24 +1,24 @@
import React, { useState } from 'react'
import { JSBI } from '@uniswap/sdk'
import { withRouter } from 'react-router-dom'
import { useToken } from '../../contexts/Tokens'
import { useExchange } from '../../contexts/Exchanges'
import { useWeb3React } from '@web3-react/core'
import { useAddressBalance } from '../../contexts/Balances'
import { usePopups } from '../../contexts/Application'
import { TokenAmount, JSBI, Token, Exchange } from '@uniswap/sdk'
import Row from '../Row'
import TokenLogo from '../TokenLogo'
import SearchModal from '../SearchModal'
import PositionCard from '../PositionCard'
import DoubleTokenLogo from '../DoubleLogo'
import { Link } from '../../theme'
import { Text } from 'rebass'
import { Plus } from 'react-feather'
import { LightCard } from '../Card'
import Column, { AutoColumn, ColumnCenter } from '../Column'
import { AutoColumn, ColumnCenter } from '../Column'
import { ButtonPrimary, ButtonDropwdown, ButtonDropwdownLight } from '../Button'
import DoubleTokenLogo from '../DoubleLogo'
import { useToken } from '../../contexts/Tokens'
import { usePopups } from '../../contexts/Application'
import { useExchange } from '../../contexts/Exchanges'
import { useWeb3React } from '@web3-react/core'
import { useAddressBalance } from '../../contexts/Balances'
function PoolFinder({ history }) {
const Fields = {
@ -27,27 +27,25 @@ function PoolFinder({ history }) {
}
const { account } = useWeb3React()
const [showSearch, setShowSearch] = useState(false)
const [activeField, setActiveField] = useState(Fields.TOKEN0)
const [showSearch, setShowSearch] = useState<boolean>(false)
const [activeField, setActiveField] = useState<number>(Fields.TOKEN0)
const [, addPopup] = usePopups()
const [token0Address, setToken0Address] = useState()
const [token1Address, setToken1Address] = useState()
const [token0Address, setToken0Address] = useState<string>()
const [token1Address, setToken1Address] = useState<string>()
const token0 = useToken(token0Address)
const token1 = useToken(token1Address)
const token0: Token = useToken(token0Address)
const token1: Token = useToken(token1Address)
const exchange = useExchange(token0, token1)
const exchange: Exchange = useExchange(token0, token1)
const position: TokenAmount = useAddressBalance(account, exchange?.liquidityToken)
const position = useAddressBalance(account, exchange?.liquidityToken)
const newExchange = exchange && JSBI.equal(exchange.reserve0.raw, JSBI.BigInt(0))
const allowImport = position && JSBI.greaterThan(position.raw, JSBI.BigInt(0))
const newExchange: boolean = exchange && JSBI.equal(exchange.reserve0.raw, JSBI.BigInt(0))
const allowImport: boolean = position && JSBI.greaterThan(position.raw, JSBI.BigInt(0))
function endSearch() {
history.goBack()
history.goBack() // return to previous page
addPopup(
<Row>
<DoubleTokenLogo a0={token0Address || ''} a1={token1Address || ''} margin={true} />

@ -2,12 +2,11 @@ import React, { useState, useRef, useMemo, useEffect } from 'react'
import '@reach/tooltip/styles.css'
import styled from 'styled-components'
import escapeStringRegex from 'escape-string-regexp'
import { JSBI } from '@uniswap/sdk'
import { Link } from 'react-router-dom'
import { ethers } from 'ethers'
import { isMobile } from 'react-device-detect'
import { withRouter } from 'react-router-dom'
import { JSBI } from '@uniswap/sdk'
import { Link as StyledLink } from '../../theme/components'
import Modal from '../Modal'
@ -107,11 +106,17 @@ const PaddedItem = styled(RowBetween)`
const MenuItem = styled(PaddedItem)`
cursor: pointer;
:hover {
background-color: ${({ theme }) => theme.tokenRowHover};
}
`
// filters on results
const FILTERS = {
VOLUME: 'VOLUME',
LIQUIDITY: 'LIQUIDITY',
BALANCES: 'BALANCES'
}
function SearchModal({
history,
isOpen,
@ -120,28 +125,26 @@ function SearchModal({
urlAddedTokens,
filterType,
hiddenToken,
showSendWithSwap,
onTokenSelectSendWithSwap
showSendWithSwap
}) {
const { t } = useTranslation()
const { account, chainId } = useWeb3React()
const [searchQuery, setSearchQuery] = useState('')
// get all exchanges
const allTokens = useAllTokens()
const allExchanges = useAllExchanges()
const token = useToken(searchQuery)
const allBalances = useAllBalances()
const [searchQuery, setSearchQuery] = useState('')
const [sortDirection, setSortDirection] = useState(true)
const token = useToken(searchQuery)
const tokenAddress = token && token.address
// get all tokens
const allTokens = useAllTokens()
// amount of tokens to display at once
const [, setTokensShown] = useState(0)
const [, setPairsShown] = useState(0)
// all balances for both account and exchanges
let allBalances = useAllBalances()
const [sortDirection, setSortDirection] = useState(true)
const [activeFilter, setActiveFilter] = useState(FILTERS.BALANCES)
const tokenList = useMemo(() => {
return Object.keys(allTokens)
@ -149,12 +152,10 @@ function SearchModal({
if (allTokens[a].symbol && allTokens[b].symbol) {
const aSymbol = allTokens[a].symbol.toLowerCase()
const bSymbol = allTokens[b].symbol.toLowerCase()
// pin ETH to top
if (aSymbol === 'ETH'.toLowerCase() || bSymbol === 'ETH'.toLowerCase()) {
return aSymbol === bSymbol ? 0 : aSymbol === 'ETH'.toLowerCase() ? -1 : 1
}
// sort by balance
const balanceA = allBalances?.[account]?.[a]
const balanceB = allBalances?.[account]?.[b]
@ -162,16 +163,12 @@ function SearchModal({
if (balanceA && !balanceB) {
return sortDirection
}
if (!balanceA && balanceB) {
return sortDirection * -1
}
if (balanceA && balanceB) {
return sortDirection * parseFloat(balanceA.toExact()) > parseFloat(balanceB.toExact()) ? -1 : 1
}
// sort alphabetically
return aSymbol < bSymbol ? -1 : aSymbol > bSymbol ? 1 : 0
} else {
return 0
@ -181,16 +178,11 @@ function SearchModal({
if (k === hiddenToken) {
return false
}
let balance
// only update if we have data
balance = allBalances?.[account]?.[k]
return {
name: allTokens[k].name,
symbol: allTokens[k].symbol,
address: k,
balance: balance
balance: allBalances?.[account]?.[k]
}
})
}, [allTokens, allBalances, account, sortDirection, hiddenToken])
@ -198,10 +190,7 @@ function SearchModal({
const filteredTokenList = useMemo(() => {
return tokenList.filter(tokenEntry => {
const inputIsAddress = searchQuery.slice(0, 2) === '0x'
// check the regex for each field
const regexMatches = Object.keys(tokenEntry).map(tokenEntryKey => {
// if address field only search if input starts with 0x
if (tokenEntryKey === 'address') {
return (
inputIsAddress &&
@ -218,21 +207,14 @@ function SearchModal({
})
}, [tokenList, searchQuery])
function _onTokenSelect(address, sendWithSwap = false) {
if (sendWithSwap) {
setSearchQuery('')
onTokenSelectSendWithSwap(address)
onDismiss()
} else {
setSearchQuery('')
onTokenSelect(address)
onDismiss()
}
function _onTokenSelect(address) {
setSearchQuery('')
onTokenSelect(address)
onDismiss()
}
// manage focus on modal show
const inputRef = useRef()
function onInput(event) {
const input = event.target.value
const checksummedInput = isAddress(input)
@ -244,34 +226,39 @@ function SearchModal({
onDismiss()
}
// amount of tokens to display at once
const [, setTokensShown] = useState(0)
const [, setPairsShown] = useState(0)
// filters on results
const FILTERS = {
VOLUME: 'VOLUME',
LIQUIDITY: 'LIQUIDITY',
BALANCES: 'BALANCES'
}
const [activeFilter, setActiveFilter] = useState(FILTERS.BALANCES)
// sort tokens
const escapeStringRegexp = string => string
// sort pairs
const filteredPairList = useMemo(() => {
// check if the search is an address
const isAddress = searchQuery.slice(0, 2) === '0x'
return Object.keys(allExchanges).filter(exchangeAddress => {
const exchange = allExchanges[exchangeAddress]
const sortedPairList = useMemo(() => {
return Object.keys(allExchanges).sort((a, b) => {
// sort by balance
const balanceA = allBalances?.[account]?.[a]
const balanceB = allBalances?.[account]?.[b]
if (balanceA && !balanceB) {
return sortDirection
}
if (!balanceA && balanceB) {
return sortDirection * -1
}
if (balanceA && balanceB) {
const order = sortDirection * (parseFloat(balanceA.toExact()) > parseFloat(balanceB.toExact()) ? -1 : 1)
return order ? 1 : -1
} else {
return 0
}
})
}, [account, allBalances, allExchanges, sortDirection])
const filteredPairList = useMemo(() => {
const isAddress = searchQuery.slice(0, 2) === '0x'
return sortedPairList.filter(exchangeAddress => {
const exchange = allExchanges[exchangeAddress]
if (searchQuery === '') {
return true
}
const token0 = allTokens[exchange.token0]
const token1 = allTokens[exchange.token1]
const regexMatches = Object.keys(token0).map(field => {
if (
(field === 'address' && isAddress) ||
@ -288,7 +275,7 @@ function SearchModal({
return regexMatches.some(m => m)
})
}, [allExchanges, allTokens, searchQuery])
}, [account, allBalances, allExchanges, allTokens, searchQuery, sortDirection])
// update the amount shown as filtered list changes
useEffect(() => {
@ -312,9 +299,7 @@ function SearchModal({
filteredPairList.map((exchangeAddress, i) => {
const token0 = allTokens[allExchanges[exchangeAddress].token0]
const token1 = allTokens[allExchanges[exchangeAddress].token1]
const balance = allBalances?.[account]?.[exchangeAddress]?.toSignificant(6)
return (
<MenuItem
key={i}

@ -2,23 +2,23 @@ import React from 'react'
import Slider from '@material-ui/core/Slider'
import { withStyles } from '@material-ui/core/styles'
const marks = [
{
value: 0
},
{
value: 25
},
{
value: 50
},
{
value: 75
},
{
value: 100
}
]
// const marks = [
// {
// value: 0
// },
// {
// value: 25
// },
// {
// value: 50
// },
// {
// value: 75
// },
// {
// value: 100
// }
// ]
const StyledSlider = withStyles({
root: {

@ -1,765 +0,0 @@
import React, { useState, useEffect, useRef, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import styled, { css, keyframes } from 'styled-components'
import { darken, lighten } from 'polished'
import { isAddress, amountFormatter } from '../../utils'
import { useDebounce } from '../../hooks'
import question from '../../assets/images/question.svg'
import NewContextualInfo from '../../components/ContextualInfoNew'
const WARNING_TYPE = Object.freeze({
none: 'none',
emptyInput: 'emptyInput',
invalidEntryBound: 'invalidEntryBound',
riskyEntryHigh: 'riskyEntryHigh',
riskyEntryLow: 'riskyEntryLow'
})
const Flex = styled.div`
display: flex;
justify-content: center;
`
const FlexBetween = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
height: 100%;
`
const WrappedSlippageRow = ({ wrap, ...rest }) => <Flex {...rest} />
const SlippageRow = styled(WrappedSlippageRow)`
position: relative;
flex-wrap: ${({ wrap }) => wrap && 'wrap'};
flex-direction: row;
justify-content: flex-start;
align-items: center;
width: 100%;
padding: 0;
padding-top: ${({ wrap }) => wrap && '0.25rem'};
`
const QuestionWrapper = styled.button`
display: flex;
align-items: center;
justify-content: center;
margin: 0;
padding: 0;
margin-left: 0.4rem;
padding: 0.2rem;
border: none;
background: none;
outline: none;
cursor: default;
border-radius: 36px;
:hover,
:focus {
opacity: 0.7;
}
`
const HelpCircleStyled = styled.img`
height: 18px;
width: 18px;
`
const fadeIn = keyframes`
from {
opacity : 0;
}
to {
opacity : 1;
}
`
const Popup = styled(Flex)`
position: absolute;
width: 228px;
left: -78px;
top: -94px;
flex-direction: column;
align-items: center;
padding: 0.6rem 1rem;
line-height: 150%;
background: ${({ theme }) => theme.inputBackground};
border: 1px solid ${({ theme }) => theme.mercuryGray};
border-radius: 8px;
animation: ${fadeIn} 0.15s linear;
color: ${({ theme }) => theme.textColor};
font-style: italic;
${({ theme }) => theme.mediaWidth.upToSmall`
left: -20px;
`}
`
const FancyButton = styled.button`
color: ${({ theme }) => theme.textColor};
align-items: center;
min-width: 55px;
border-radius: 36px;
font-size: 12px;
border: 1px solid ${({ theme }) => theme.mercuryGray};
outline: none;
background: ${({ theme }) => theme.inputBackground};
:hover {
cursor: inherit;
border: 1px solid ${({ theme }) => theme.chaliceGray};
}
:focus {
border: 1px solid ${({ theme }) => theme.royalBlue};
}
`
const Option = styled(FancyButton)`
margin-right: 8px;
margin-top: 6px;
:hover {
cursor: pointer;
}
${({ active, theme }) =>
active &&
css`
background-color: ${({ theme }) => theme.royalBlue};
color: ${({ theme }) => theme.white};
border: none;
:hover {
border: none;
box-shadow: none;
background-color: ${({ theme }) => darken(0.05, theme.royalBlue)};
}
:focus {
border: none;
box-shadow: none;
background-color: ${({ theme }) => lighten(0.05, theme.royalBlue)};
}
:active {
background-color: ${({ theme }) => darken(0.05, theme.royalBlue)};
}
:hover:focus {
background-color: ${({ theme }) => theme.royalBlue};
}
:hover:focus:active {
background-color: ${({ theme }) => darken(0.05, theme.royalBlue)};
}
`}
`
const OptionLarge = styled(Option)`
width: 120px;
`
const Input = styled.input`
background: ${({ theme }) => theme.inputBackground};
flex-grow: 1;
font-size: 12px;
min-width: 20px;
outline: none;
box-sizing: border-box;
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
-webkit-appearance: none;
}
cursor: inherit;
color: ${({ theme }) => theme.doveGray};
text-align: left;
${({ active }) =>
active &&
css`
color: initial;
cursor: initial;
text-align: right;
`}
${({ placeholder }) =>
placeholder !== 'Custom' &&
css`
text-align: right;
color: ${({ theme }) => theme.textColor};
`}
${({ color }) =>
color === 'red' &&
css`
color: ${({ theme }) => theme.salmonRed};
`}
`
const BottomError = styled.div`
${({ show }) =>
show &&
css`
padding-top: 12px;
`}
color: ${({ theme }) => theme.doveGray};
${({ color }) =>
color === 'red' &&
css`
color: ${({ theme }) => theme.salmonRed};
`}
`
const OptionCustom = styled(FancyButton)`
position: relative;
width: 120px;
margin-top: 6px;
padding: 0 0.75rem;
${({ active }) =>
active &&
css`
border: 1px solid ${({ theme }) => theme.royalBlue};
:hover {
border: 1px solid ${({ theme }) => darken(0.1, theme.royalBlue)};
}
`}
${({ color }) =>
color === 'red' &&
css`
border: 1px solid ${({ theme }) => theme.salmonRed};
`}
input {
width: 100%;
height: 100%;
border: 0px;
border-radius: 2rem;
}
`
const Bold = styled.span`
font-weight: 500;
`
const LastSummaryText = styled.div`
padding-top: 0.5rem;
`
const SlippageSelector = styled.div`
background-color: ${({ theme }) => darken(0.04, theme.concreteGray)};
padding: 1rem 1.25rem 1rem 1.25rem;
border-radius: 12px 12px 0 0;
`
const Percent = styled.div`
color: inherit;
font-size: 0, 8rem;
flex-grow: 0;
${({ color, theme }) =>
(color === 'faded' &&
css`
color: ${theme.doveGray};
`) ||
(color === 'red' &&
css`
color: ${theme.salmonRed};
`)};
`
const Faded = styled.span`
opacity: 0.7;
`
const TransactionInfo = styled.div`
padding: 1.25rem 1.25rem 1rem 1.25rem;
`
const ValueWrapper = styled.span`
padding: 0.125rem 0.3rem 0.1rem 0.3rem;
background-color: ${({ theme }) => darken(0.04, theme.concreteGray)};
border-radius: 12px;
font-variant: tabular-nums;
`
const DeadlineSelector = styled.div`
background-color: ${({ theme }) => darken(0.04, theme.concreteGray)};
padding: 1rem 1.25rem 1rem 1.25rem;
border-radius: 0 0 12px 12px;
`
const DeadlineRow = SlippageRow
const DeadlineInput = OptionCustom
export default function TransactionDetails(props) {
const { t } = useTranslation()
const [activeIndex, setActiveIndex] = useState(3)
const [warningType, setWarningType] = useState(WARNING_TYPE.none)
const inputRef = useRef()
const [showPopup, setPopup] = useState(false)
const [userInput, setUserInput] = useState('')
const debouncedInput = useDebounce(userInput, 150)
useEffect(() => {
if (activeIndex === 4) {
checkBounds(debouncedInput)
}
})
const [deadlineInput, setDeadlineInput] = useState('')
function renderSummary() {
let contextualInfo = ''
let isError = false
if (props.brokenTokenWarning) {
contextualInfo = t('brokenToken')
isError = true
} else if (props.inputError || props.independentError) {
contextualInfo = props.inputError || props.independentError
isError = true
} else if (!props.inputCurrency || !props.outputCurrency) {
contextualInfo = t('selectTokenCont')
} else if (!props.independentValue) {
contextualInfo = t('enterValueCont')
} else if (props.sending && !props.recipientAddress) {
contextualInfo = t('noRecipient')
} else if (props.sending && !isAddress(props.recipientAddress)) {
contextualInfo = t('invalidRecipient')
} else if (!props.account) {
contextualInfo = t('noWallet')
isError = true
}
const slippageWarningText = props.highSlippageWarning
? t('highSlippageWarning')
: props.slippageWarning
? t('slippageWarning')
: ''
return (
<NewContextualInfo
openDetailsText={t('transactionDetails')}
closeDetailsText={t('hideDetails')}
contextualInfo={contextualInfo ? contextualInfo : slippageWarningText}
allowExpand={
!!(
!props.brokenTokenWarning &&
props.inputCurrency &&
props.outputCurrency &&
props.inputValueParsed &&
props.outputValueParsed &&
(props.sending ? props.recipientAddress : true)
)
}
isError={isError}
slippageWarning={props.slippageWarning && !contextualInfo}
highSlippageWarning={props.highSlippageWarning && !contextualInfo}
brokenTokenWarning={props.brokenTokenWarning}
renderTransactionDetails={renderTransactionDetails}
dropDownContent={dropDownContent}
/>
)
}
const dropDownContent = () => {
return (
<>
{renderTransactionDetails()}
<SlippageSelector>
<SlippageRow>
Limit additional price slippage
<QuestionWrapper
onClick={() => {
setPopup(!showPopup)
}}
onMouseEnter={() => {
setPopup(true)
}}
onMouseLeave={() => {
setPopup(false)
}}
>
<HelpCircleStyled src={question} alt="popup" />
</QuestionWrapper>
{showPopup ? (
<Popup>
Lowering this limit decreases your risk of frontrunning. However, this makes it more likely that your
transaction will fail due to normal price movements.
</Popup>
) : (
''
)}
</SlippageRow>
<SlippageRow wrap>
<Option
onClick={() => {
setFromFixed(1, 0.1)
}}
active={activeIndex === 1}
>
0.1%
</Option>
<OptionLarge
onClick={() => {
setFromFixed(2, 0.5)
}}
active={activeIndex === 2}
>
0.5% <Faded>(suggested)</Faded>
</OptionLarge>
<Option
onClick={() => {
setFromFixed(3, 1)
}}
active={activeIndex === 3}
>
1%
</Option>
<OptionCustom
active={activeIndex === 4}
color={
warningType === WARNING_TYPE.emptyInput
? ''
: warningType !== WARNING_TYPE.none && warningType !== WARNING_TYPE.riskyEntryLow
? 'red'
: ''
}
onClick={() => {
setFromCustom()
}}
>
<FlexBetween>
{!(warningType === WARNING_TYPE.none || warningType === WARNING_TYPE.emptyInput) && (
<span role="img" aria-label="warning">
</span>
)}
<Input
tabIndex={-1}
ref={inputRef}
active={activeIndex === 4}
placeholder={
activeIndex === 4
? !!userInput
? ''
: '0'
: activeIndex !== 4 && userInput !== ''
? userInput
: 'Custom'
}
value={activeIndex === 4 ? userInput : ''}
onChange={parseInput}
color={
warningType === WARNING_TYPE.emptyInput
? ''
: warningType !== WARNING_TYPE.none && warningType !== WARNING_TYPE.riskyEntryLow
? 'red'
: ''
}
/>
<Percent
color={
activeIndex !== 4
? 'faded'
: warningType === WARNING_TYPE.riskyEntryHigh || warningType === WARNING_TYPE.invalidEntryBound
? 'red'
: ''
}
>
%
</Percent>
</FlexBetween>
</OptionCustom>
</SlippageRow>
<SlippageRow>
<BottomError
show={activeIndex === 4}
color={
warningType === WARNING_TYPE.emptyInput
? ''
: warningType !== WARNING_TYPE.none && warningType !== WARNING_TYPE.riskyEntryLow
? 'red'
: ''
}
>
{activeIndex === 4 && warningType.toString() === 'none' && 'Custom slippage value'}
{warningType === WARNING_TYPE.emptyInput && 'Enter a slippage percentage'}
{warningType === WARNING_TYPE.invalidEntryBound && 'Please select a value no greater than 50%'}
{warningType === WARNING_TYPE.riskyEntryHigh && 'Your transaction may be frontrun'}
{warningType === WARNING_TYPE.riskyEntryLow && 'Your transaction may fail'}
</BottomError>
</SlippageRow>
</SlippageSelector>
<DeadlineSelector>
Set swap deadline (minutes from now)
<DeadlineRow wrap>
<DeadlineInput>
<Input placeholder={'Deadline'} value={deadlineInput} onChange={parseDeadlineInput} />
</DeadlineInput>
</DeadlineRow>
</DeadlineSelector>
</>
)
}
const setFromCustom = () => {
setActiveIndex(4)
inputRef.current.focus()
// if there's a value, evaluate the bounds
checkBounds(debouncedInput)
}
// destructure props for to limit effect callbacks
const setRawSlippage = props.setRawSlippage
const setRawTokenSlippage = props.setRawTokenSlippage
const setcustomSlippageError = props.setcustomSlippageError
const setDeadline = props.setDeadline
const updateSlippage = useCallback(
newSlippage => {
// round to 2 decimals to prevent ethers error
let numParsed = parseInt(newSlippage * 100)
// set both slippage values in parents
setRawSlippage(numParsed)
setRawTokenSlippage(numParsed)
},
[setRawSlippage, setRawTokenSlippage]
)
// used for slippage presets
const setFromFixed = useCallback(
(index, slippage) => {
// update slippage in parent, reset errors and input state
updateSlippage(slippage)
setWarningType(WARNING_TYPE.none)
setActiveIndex(index)
setcustomSlippageError('valid`')
},
[setcustomSlippageError, updateSlippage]
)
/**
* @todo
* Breaks without useState here, able to
* break input parsing if typing is faster than
* debounce time
*/
const [initialSlippage] = useState(props.rawSlippage)
useEffect(() => {
switch (Number.parseInt(initialSlippage)) {
case 10:
setFromFixed(1, 0.1)
break
case 50:
setFromFixed(2, 0.5)
break
case 100:
setFromFixed(3, 1)
break
default:
// restrict to 2 decimal places
let acceptableValues = [/^$/, /^\d{1,2}$/, /^\d{0,2}\.\d{0,2}$/]
// if its within accepted decimal limit, update the input state
if (acceptableValues.some(val => val.test(initialSlippage / 100))) {
setUserInput(initialSlippage / 100)
setActiveIndex(4)
}
}
}, [initialSlippage, setFromFixed])
const checkBounds = useCallback(
slippageValue => {
setWarningType(WARNING_TYPE.none)
setcustomSlippageError('valid')
if (slippageValue === '' || slippageValue === '.') {
setcustomSlippageError('invalid')
return setWarningType(WARNING_TYPE.emptyInput)
}
// check bounds and set errors
if (Number(slippageValue) < 0 || Number(slippageValue) > 50) {
setcustomSlippageError('invalid')
return setWarningType(WARNING_TYPE.invalidEntryBound)
}
if (Number(slippageValue) >= 0 && Number(slippageValue) < 0.1) {
setcustomSlippageError('valid')
setWarningType(WARNING_TYPE.riskyEntryLow)
}
if (Number(slippageValue) > 5) {
setcustomSlippageError('warning')
setWarningType(WARNING_TYPE.riskyEntryHigh)
}
//update the actual slippage value in parent
updateSlippage(Number(slippageValue))
},
[setcustomSlippageError, updateSlippage]
)
// check that the theyve entered number and correct decimal
const parseInput = e => {
let input = e.target.value
// restrict to 2 decimal places
let acceptableValues = [/^$/, /^\d{1,2}$/, /^\d{0,2}\.\d{0,2}$/]
// if its within accepted decimal limit, update the input state
if (acceptableValues.some(a => a.test(input))) {
setUserInput(input)
}
}
const [initialDeadline] = useState(props.deadline)
useEffect(() => {
setDeadlineInput(initialDeadline / 60)
}, [initialDeadline])
const parseDeadlineInput = e => {
const input = e.target.value
const acceptableValues = [/^$/, /^\d+$/]
if (acceptableValues.some(re => re.test(input))) {
setDeadlineInput(input)
setDeadline(parseInt(input) * 60)
}
}
const b = text => <Bold>{text}</Bold>
const renderTransactionDetails = () => {
if (props.independentField === props.INPUT) {
return props.sending ? (
<TransactionInfo>
<div>
{t('youAreSelling')}{' '}
<ValueWrapper>
{b(
`${amountFormatter(
props.independentValueParsed,
props.independentDecimals,
Math.min(4, props.independentDecimals)
)} ${props.inputSymbol}`
)}
</ValueWrapper>
</div>
<LastSummaryText>
{b(props.recipientAddress)} {t('willReceive')}{' '}
<ValueWrapper>
{b(
`${amountFormatter(
props.dependentValueMinumum,
props.dependentDecimals,
Math.min(4, props.dependentDecimals)
)} ${props.outputSymbol}`
)}
</ValueWrapper>{' '}
</LastSummaryText>
<LastSummaryText>
{t('priceChange')} <ValueWrapper>{b(`${props.percentSlippageFormatted}%`)}</ValueWrapper>
</LastSummaryText>
</TransactionInfo>
) : (
<TransactionInfo>
<div>
{t('youAreSelling')}{' '}
<ValueWrapper>
{b(
`${amountFormatter(
props.independentValueParsed,
props.independentDecimals,
Math.min(4, props.independentDecimals)
)} ${props.inputSymbol}`
)}
</ValueWrapper>{' '}
{t('forAtLeast')}
<ValueWrapper>
{b(
`${amountFormatter(
props.dependentValueMinumum,
props.dependentDecimals,
Math.min(4, props.dependentDecimals)
)} ${props.outputSymbol}`
)}
</ValueWrapper>
</div>
<LastSummaryText>
{t('priceChange')} <ValueWrapper>{b(`${props.percentSlippageFormatted}%`)}</ValueWrapper>
</LastSummaryText>
</TransactionInfo>
)
} else {
return props.sending ? (
<TransactionInfo>
<div>
{t('youAreSending')}{' '}
<ValueWrapper>
{b(
`${amountFormatter(
props.independentValueParsed,
props.independentDecimals,
Math.min(4, props.independentDecimals)
)} ${props.outputSymbol}`
)}
</ValueWrapper>{' '}
{t('to')} {b(props.recipientAddress)} {t('forAtMost')}{' '}
<ValueWrapper>
{b(
`${amountFormatter(
props.dependentValueMaximum,
props.dependentDecimals,
Math.min(4, props.dependentDecimals)
)} ${props.inputSymbol}`
)}
</ValueWrapper>{' '}
</div>
<LastSummaryText>
{t('priceChange')} <ValueWrapper>{b(`${props.percentSlippageFormatted}%`)}</ValueWrapper>
</LastSummaryText>
</TransactionInfo>
) : (
<TransactionInfo>
{t('youAreBuying')}{' '}
<ValueWrapper>
{b(
`${amountFormatter(
props.independentValueParsed,
props.independentDecimals,
Math.min(4, props.independentDecimals)
)} ${props.outputSymbol}`
)}
</ValueWrapper>{' '}
{t('forAtMost')}{' '}
<ValueWrapper>
{b(
`${amountFormatter(
props.dependentValueMaximum,
props.dependentDecimals,
Math.min(4, props.dependentDecimals)
)} ${props.inputSymbol}`
)}
</ValueWrapper>{' '}
<LastSummaryText>
{t('priceChange')} <ValueWrapper>{b(`${props.percentSlippageFormatted}%`)}</ValueWrapper>
</LastSummaryText>
</TransactionInfo>
)
}
}
return <>{renderSummary()}</>
}

@ -39,7 +39,6 @@ export default function Web3ReactManager({ children }) {
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)

@ -1,21 +1,4 @@
[
{
"inputs": [
{
"internalType": "address",
"name": "_WETH",
"type": "address"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"payable": true,
"stateMutability": "payable",
"type": "fallback"
},
{
"constant": true,
"inputs": [],
@ -153,32 +136,6 @@
"stateMutability": "payable",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"internalType": "address",
"name": "tokenA",
"type": "address"
},
{
"internalType": "address",
"name": "tokenB",
"type": "address"
}
],
"name": "exchangeFor",
"outputs": [
{
"internalType": "address",
"name": "exchange",
"type": "address"
}
],
"payable": false,
"stateMutability": "pure",
"type": "function"
},
{
"constant": true,
"inputs": [],
@ -191,7 +148,7 @@
}
],
"payable": false,
"stateMutability": "view",
"stateMutability": "pure",
"type": "function"
},
{
@ -308,52 +265,6 @@
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"internalType": "address",
"name": "tokenA",
"type": "address"
},
{
"internalType": "address",
"name": "tokenB",
"type": "address"
}
],
"name": "getReserves",
"outputs": [
{
"internalType": "uint256",
"name": "reserveA",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "reserveB",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "initCodeHash",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
@ -525,6 +436,11 @@
"name": "deadline",
"type": "uint256"
},
{
"internalType": "bool",
"name": "approveMax",
"type": "bool"
},
{
"internalType": "uint8",
"name": "v",
@ -596,6 +512,11 @@
"name": "deadline",
"type": "uint256"
},
{
"internalType": "bool",
"name": "approveMax",
"type": "bool"
},
{
"internalType": "uint8",
"name": "v",
@ -629,37 +550,6 @@
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"internalType": "address",
"name": "tokenA",
"type": "address"
},
{
"internalType": "address",
"name": "tokenB",
"type": "address"
}
],
"name": "sortTokens",
"outputs": [
{
"internalType": "address",
"name": "token0",
"type": "address"
},
{
"internalType": "address",
"name": "token1",
"type": "address"
}
],
"payable": false,
"stateMutability": "pure",
"type": "function"
},
{
"constant": false,
"inputs": [
@ -895,35 +785,5 @@
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "transferFromSelector",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "transferSelector",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
}
]

@ -1,16 +1,16 @@
import { injected, walletconnect, walletlink, fortmatic, portis } from '../connectors'
export const FACTORY_ADDRESSES = {
1: '0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95',
3: '0x9c83dCE8CA20E9aAF9D3efc003b2ea62aBC08351',
4: '0xf5D915570BC477f9B8D6C0E980aA81757A3AaC36',
42: '0xD3E51Ef092B2845f10401a0159B2B96e8B6c3D30'
1: '',
3: '',
4: '0xe2f197885abe8ec7c866cFf76605FD06d4576218',
42: ''
}
export const ROUTER_ADDRESSES = {
1: '',
3: '',
4: '0xd9210Ff5A0780E083BB40e30d005d93a2DcFA4EF',
4: '0xcDbE04934d89e97a24BCc07c3562DC8CF17d8167',
42: ''
}
@ -100,12 +100,4 @@ export const SUPPORTED_WALLETS =
}
}
// list of tokens that lock fund on adding liquidity - used to disable button
export const brokenTokens = [
'0xB8c77482e45F1F44dE1745F52C74426C631bDD52',
'0x95dAaaB98046846bF4B2853e23cba236fa394A31',
'0x55296f69f40Ea6d20E478533C15A6B08B654E758',
'0xc3761EB917CD790B30dAD99f6Cc5b4Ff93C4F9eA'
]
export const NetworkContextName = 'NETWORK'

@ -162,6 +162,7 @@ export function usePopups() {
if (key === item.key) {
item.show = false
}
return true
})
setPopups(currentPopups)
}

@ -427,6 +427,7 @@ export function useAllBalances(): Array<TokenAmount> {
}
}
}
return true
})
})
return newBalances
@ -443,6 +444,9 @@ export function useAddressBalance(address: string, token: Token): TokenAmount |
const { chainId } = useWeb3React()
const [state, { startListening, stopListening }] = useBalancesContext()
const value = typeof chainId === 'number' ? state?.[chainId]?.[address]?.[token?.address]?.value : undefined
const formattedValue = value && token && new TokenAmount(token, value)
/**
* @todo
* when catching for token, causes infinite rerender
@ -457,9 +461,6 @@ export function useAddressBalance(address: string, token: Token): TokenAmount |
}
}, [chainId, address, startListening, stopListening])
const value = typeof chainId === 'number' ? state?.[chainId]?.[address]?.[token?.address]?.value : undefined
const formattedValue = value && token && new TokenAmount(token, value)
return useMemo(() => formattedValue, [formattedValue])
}
@ -476,6 +477,7 @@ export function useAccountLPBalances(account: string) {
stopListening(chainId, account, exchangeAddress)
}
}
return true
})
}, [account, allExchanges, chainId, startListening, stopListening])
}

@ -11,7 +11,7 @@ const UPDATE = 'UPDATE'
const ALL_EXCHANGES: [Token, Token][] = [
[
INITIAL_TOKENS_CONTEXT[ChainId.RINKEBY][WETH[ChainId.RINKEBY].address],
INITIAL_TOKENS_CONTEXT[ChainId.RINKEBY]['0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735']
INITIAL_TOKENS_CONTEXT[ChainId.RINKEBY]['0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735'] //dai
],
[
INITIAL_TOKENS_CONTEXT[ChainId.RINKEBY]['0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735'],

@ -8,7 +8,7 @@ const UPDATE = 'UPDATE'
export const ALL_TOKENS = [
WETH[ChainId.RINKEBY],
new Token(ChainId.RINKEBY, '0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735', 18, 'DAI', 'Dai Stablecoin'),
new Token(ChainId.RINKEBY, '0x8ab15C890E5C03B5F240f2D146e3DF54bEf3Df44', 18, 'IANV2', 'IAn V2 Coin')
new Token(ChainId.RINKEBY, '0x8ab15C890E5C03B5F240f2D146e3DF54bEf3Df44', 18, 'IANV2', 'IAn V2 /Coin')
]
// only meant to be used in exchanges.ts!
@ -112,6 +112,11 @@ export function useAllTokens(): string[] {
const [state] = useTokensContext()
return useMemo(() => {
// hardcode overide weth as ETH
if (state && state[chainId]) {
state[chainId][WETH[chainId].address].symbol = 'ETH'
state[chainId][WETH[chainId].address].name = 'ETH'
}
return state?.[chainId] || {}
}, [state, chainId])
}

@ -1,23 +1,21 @@
import { useState, useMemo, useCallback, useEffect, useRef } from 'react'
import { useWeb3React as useWeb3ReactCore } from '@web3-react/core'
import copy from 'copy-to-clipboard'
import { isMobile } from 'react-device-detect'
import copy from 'copy-to-clipboard'
import { NetworkContextName } from '../constants'
import ERC20_ABI from '../constants/abis/erc20'
import { getContract, getFactoryContract, getExchangeContract, isAddress } from '../utils'
import { injected } from '../connectors'
import { NetworkContextName } from '../constants'
import { getContract, getFactoryContract, getExchangeContract, isAddress } from '../utils'
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(() => {
@ -265,6 +263,5 @@ export function usePrevious(value) {
export function useToggle(initialState = false) {
const [state, setState] = useState(initialState)
const toggle = useCallback(() => setState(state => !state), [])
return [state, toggle]
}

@ -2,10 +2,9 @@ import React, { Suspense, lazy } from 'react'
import styled from 'styled-components'
import { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom'
import Web3ReactManager from '../components/Web3ReactManager'
import Header from '../components/Header'
import NavigationTabs from '../components/NavigationTabs'
import Web3ReactManager from '../components/Web3ReactManager'
import { isAddress, getAllQueryParams } from '../utils'
const Swap = lazy(() => import('./Swap'))
@ -65,6 +64,7 @@ export default function App() {
{/* this Suspense is for route code-splitting */}
<Suspense fallback={null}>
<Switch>
<Route exact strict path="/" render={() => <Redirect to={{ pathname: '/swap' }} />} />
<Route exact strict path="/find" component={() => <Find params={params} />} />
<Route exact strict path="/swap" component={() => <Swap params={params} />} />
<Route

@ -1,251 +1,7 @@
import React, { useState, useEffect } from 'react'
import styled from 'styled-components'
import { darken } from 'polished'
import { TokenAmount, JSBI } from '@uniswap/sdk'
import React from 'react'
import QR from '../../assets/svg/QR.svg'
import TokenLogo from '../../components/TokenLogo'
import SearchModal from '../../components/SearchModal'
import ExchangePage from '../../components/ExchangePage'
import NumericalInput from '../../components/NumericalInput'
import ConfirmationModal from '../../components/ConfirmationModal'
import { Text } from 'rebass'
import { TYPE } from '../../theme'
import { LightCard } from '../../components/Card'
import { ArrowDown } from 'react-feather'
import { AutoColumn } from '../../components/Column'
import { ButtonPrimary } from '../../components/Button'
import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg'
import { useToken } from '../../contexts/Tokens'
import { RowBetween } from '../../components/Row'
import { useENSName } from '../../hooks'
import { useWeb3React } from '@web3-react/core'
import { useAddressBalance } from '../../contexts/Balances'
import { parseUnits } from '@ethersproject/units'
import { isAddress } from '../../utils'
const CurrencySelect = styled.button`
display: flex;
align-items: center;
justify-content: space-between;
font-size: 20px;
width: ${({ selected }) => (selected ? '128px' : '180px')}
padding: 8px 12px;
background-color: ${({ selected, theme }) => (selected ? theme.buttonBackgroundPlain : theme.royalBlue)};
color: ${({ selected, theme }) => (selected ? theme.textColor : theme.white)};
border: 1px solid
${({ selected, theme }) => (selected ? theme.outlineGrey : theme.royalBlue)};
border-radius: 8px;
outline: none;
cursor: pointer;
user-select: none;
:hover {
border: 1px solid
${({ selected, theme }) => (selected ? darken(0.1, theme.outlineGrey) : darken(0.1, theme.royalBlue))};
}
:focus {
border: 1px solid ${({ selected, theme }) =>
selected ? darken(0.1, theme.outlineGrey) : darken(0.1, theme.royalBlue)};
}
:active {
background-color: ${({ selected, theme }) => (selected ? theme.buttonBackgroundPlain : theme.royalBlue)};
}
`
const StyledDropDown = styled(DropDown)`
height: 35%;
path {
stroke: ${({ selected, theme }) => (selected ? theme.textColor : theme.white)};
}
`
const InputGroup = styled(AutoColumn)`
position: relative;
padding: 40px 0;
`
const QRWrapper = styled.div`
display: flex;
align-items: center;
justify-content: center;
border: 1px solid ${({ theme }) => theme.outlineGrey};
background: #fbfbfb;
padding: 4px;
border-radius: 8px;
`
const StyledInput = styled.input`
width: ${({ width }) => width};
border: none;
outline: none;
font-size: 20px;
::placeholder {
color: #edeef2;
}
`
const StyledNumerical = styled(NumericalInput)`
text-align: center;
font-size: 48px;
font-weight: 500px;
width: 100%;
::placeholder {
color: #edeef2;
}
`
const MaxButton = styled.button`
position: absolute;
right: 70px;
padding: 0.5rem 1rem;
background-color: ${({ theme }) => theme.zumthorBlue};
border: 1px solid ${({ theme }) => theme.zumthorBlue};
border-radius: 0.5rem;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
margin-right: 0.5rem;
color: ${({ theme }) => theme.royalBlue};
:hover {
border: 1px solid ${({ theme }) => theme.royalBlue};
}
:focus {
border: 1px solid ${({ theme }) => theme.royalBlue};
outline: none;
}
`
export default function Send() {
const { account } = useWeb3React()
// setting for send with swap or regular swap
const [withSwap, setWithSwap] = useState(true)
// modals
const [modalOpen, setModalOpen] = useState(false)
const [showConfirm, setShowConfirm] = useState(false)
// token selected
const [activeTokenAddress, setActiveTokenAddress] = useState()
const token = useToken(activeTokenAddress)
// user inputs
const [typedValue, setTypedValue] = useState('')
const [amount, setAmount] = useState(null)
const [recipient, setRecipient] = useState('0x74Aa01d162E6dC6A657caC857418C403D48E2D77')
//ENS
const recipientENS = useENSName(recipient)
// balances
const userBalance = useAddressBalance(account, token)
//errors
const [generalError, setGeneralError] = useState('')
const [amountError, setAmountError] = useState('')
const [recipientError, setRecipientError] = useState('')
function parseInputAmount(newtypedValue) {
setTypedValue(newtypedValue)
if (!!token && newtypedValue !== '' && newtypedValue !== '.') {
const typedValueParsed = parseUnits(newtypedValue, token.decimals).toString()
setAmount(new TokenAmount(token, typedValueParsed))
}
}
function onMax() {
if (userBalance) {
setTypedValue(userBalance.toExact())
setAmount(userBalance)
}
}
const atMax = amount && userBalance && JSBI.equal(amount.raw, userBalance.raw) ? true : false
//error detection
useEffect(() => {
setGeneralError('')
setRecipientError('')
setAmountError('')
if (!amount) {
setGeneralError('Enter an amount')
}
if (!isAddress(recipient)) {
setRecipientError('Enter a valid address')
}
if (!!!token) {
setGeneralError('Select a token')
}
if (amount && userBalance && JSBI.greaterThan(amount.raw, userBalance.raw)) {
setAmountError('Insufficient Balance')
}
}, [recipient, token, amount, userBalance])
const TopContent = () => {
return (
<AutoColumn gap="30px" style={{ marginTop: '40px' }}>
<RowBetween>
<Text fontSize={36} fontWeight={500}>
{amount?.toFixed(8)}
</Text>
<TokenLogo address={activeTokenAddress} size={'30px'} />
</RowBetween>
<ArrowDown size={24} color="#888D9B" />
<TYPE.blue fontSize={36}>
{recipient?.slice(0, 6)}...{recipient?.slice(36, 42)}
</TYPE.blue>
</AutoColumn>
)
}
const BottomContent = () => {
return (
<AutoColumn>
<ButtonPrimary>
<Text color="white" fontSize={20}>
Confirm send
</Text>
</ButtonPrimary>
</AutoColumn>
)
}
const [attemptedSend, setAttemptedSend] = useState(false) // clicke confirm
const [pendingConfirmation, setPendingConfirmation] = useState(true) // waiting for
return withSwap ? (
<ExchangePage sendingInput={true} />
) : (
<>
<SearchModal
isOpen={modalOpen}
onDismiss={() => {
setModalOpen(false)
}}
filterType="tokens"
onTokenSelect={tokenAddress => setActiveTokenAddress(tokenAddress)}
/>
<ConfirmationModal
isOpen={showConfirm}
onDismiss={() => setShowConfirm(false)}
hash=""
title="Confirm Send"
topContent={TopContent}
bottomContent={BottomContent}
attemptingTxn={attemptedSend}
pendingConfirmation={pendingConfirmation}
pendingText=""
/>
</>
)
return <ExchangePage sendingInput={true} />
}

@ -2,10 +2,10 @@ import React, { useReducer, useState, useCallback, useEffect } from 'react'
import styled from 'styled-components'
import { ethers } from 'ethers'
import { parseUnits, parseEther } from '@ethersproject/units'
import { WETH, TokenAmount, JSBI, Percent, Route } from '@uniswap/sdk'
import { WETH, TokenAmount, JSBI, Percent, Route, Token, Exchange } from '@uniswap/sdk'
import DoubleLogo from '../../components/DoubleLogo'
import TokenLogo from '../../components/TokenLogo'
import DoubleLogo from '../../components/DoubleLogo'
import SearchModal from '../../components/SearchModal'
import PositionCard from '../../components/PositionCard'
import ConfirmationModal from '../../components/ConfirmationModal'
@ -17,8 +17,8 @@ import { AutoColumn, ColumnCenter } from '../../components/Column'
import Row, { RowBetween, RowFlat, RowFixed } from '../../components/Row'
import { useToken } from '../../contexts/Tokens'
import { useWeb3React } from '../../hooks'
import { usePopups } from '../../contexts/Application'
import { useWeb3React } from '../../hooks'
import { useAddressBalance } from '../../contexts/Balances'
import { useAddressAllowance } from '../../contexts/Allowances'
import { useTransactionAdder } from '../../contexts/Transactions'
@ -27,6 +27,7 @@ import { useExchange, useTotalSupply } from '../../contexts/Exchanges'
import { BigNumber } from 'ethers/utils'
import { ROUTER_ADDRESSES } from '../../constants'
import { getRouterContract, calculateGasMargin } from '../../utils'
import { TYPE } from '../../theme'
// denominated in bips
const ALLOWED_SLIPPAGE = 200
@ -134,12 +135,9 @@ function reducer(
}
}
/**
* @todo should we ever not have prepopulated tokens?
*
*/
export default function AddLiquidity({ token0, token1 }) {
const { account, chainId, library } = useWeb3React()
const [, addPopup] = usePopups()
const routerAddress: string = ROUTER_ADDRESSES[chainId]
@ -152,28 +150,28 @@ export default function AddLiquidity({ token0, token1 }) {
// input state
const [state, dispatch] = useReducer(reducer, initializeAddState(token0, token1))
const { independentField, typedValue, ...fieldData } = state
const dependentField = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
// get basic SDK entities
const tokens = {
const tokens: { [field: number]: Token } = {
[Field.INPUT]: useToken(fieldData[Field.INPUT].address),
[Field.OUTPUT]: useToken(fieldData[Field.OUTPUT].address)
}
// exhchange data
const exchange = useExchange(tokens[Field.INPUT], tokens[Field.OUTPUT])
const route = exchange ? new Route([exchange], tokens[independentField]) : undefined
const totalSupply = useTotalSupply(exchange)
const [noLiquidity, setNoLiquidity] = useState<boolean>(false)
const exchange: Exchange = useExchange(tokens[Field.INPUT], tokens[Field.OUTPUT])
const route: Route = exchange ? new Route([exchange], tokens[independentField]) : undefined
const totalSupply: TokenAmount = useTotalSupply(exchange)
const [noLiquidity, setNoLiquidity] = useState<boolean>(false) // used to detect new exchange
// state for amount approvals
const inputApproval = useAddressAllowance(account, tokens[Field.INPUT], routerAddress)
const outputApproval = useAddressAllowance(account, tokens[Field.OUTPUT], routerAddress)
const inputApproval: TokenAmount = useAddressAllowance(account, tokens[Field.INPUT], routerAddress)
const outputApproval: TokenAmount = useAddressAllowance(account, tokens[Field.OUTPUT], routerAddress)
const [showInputUnlock, setShowInputUnlock] = useState<boolean>(false)
const [showOutputUnlock, setShowOutputUnlock] = useState<boolean>(false)
// get user-pecific and token-specific lookup data
const userBalances = {
const userBalances: { [field: number]: TokenAmount } = {
[Field.INPUT]: useAddressBalance(account, tokens[Field.INPUT]),
[Field.OUTPUT]: useAddressBalance(account, tokens[Field.OUTPUT])
}
@ -219,7 +217,6 @@ export default function AddLiquidity({ token0, token1 }) {
if (typedValueParsed !== '0')
parsedAmounts[independentField] = new TokenAmount(tokens[independentField], typedValueParsed)
} catch (error) {
// should only fail if the user specifies too many decimal places of precision (or maybe exceed max uint?)
console.error(error)
}
}
@ -240,12 +237,16 @@ export default function AddLiquidity({ token0, token1 }) {
}
// check for estimated liquidity minted
const liquidityMinted =
!!exchange && !!parsedAmounts[Field.INPUT] && !!parsedAmounts[Field.OUTPUT] && !!totalSupply
? exchange.getLiquidityMinted(totalSupply, parsedAmounts[Field.INPUT], parsedAmounts[Field.OUTPUT])
const liquidityMinted: TokenAmount =
!!exchange && !!parsedAmounts[Field.INPUT] && !!parsedAmounts[Field.OUTPUT]
? exchange.getLiquidityMinted(
totalSupply ? totalSupply : new TokenAmount(exchange?.liquidityToken, JSBI.BigInt(0)),
parsedAmounts[Field.INPUT],
parsedAmounts[Field.OUTPUT]
)
: undefined
const poolTokenPercentage =
const poolTokenPercentage: Percent =
!!liquidityMinted && !!totalSupply
? new Percent(liquidityMinted.raw, totalSupply.add(liquidityMinted).raw)
: undefined
@ -271,10 +272,10 @@ export default function AddLiquidity({ token0, token1 }) {
})
}, [])
const MIN_ETHER = new TokenAmount(WETH[chainId], JSBI.BigInt(parseEther('.01')))
const MIN_ETHER: TokenAmount = new TokenAmount(WETH[chainId], JSBI.BigInt(parseEther('.01')))
// get the max amounts user can add
const [maxAmountInput, maxAmountOutput] = [Field.INPUT, Field.OUTPUT].map(index => {
const [maxAmountInput, maxAmountOutput]: TokenAmount[] = [Field.INPUT, Field.OUTPUT].map(index => {
const field = Field[index]
return !!userBalances[Field[field]] &&
JSBI.greaterThan(
@ -287,7 +288,7 @@ export default function AddLiquidity({ token0, token1 }) {
: undefined
})
const [atMaxAmountInput, atMaxAmountOutput] = [Field.INPUT, Field.OUTPUT].map(index => {
const [atMaxAmountInput, atMaxAmountOutput]: boolean[] = [Field.INPUT, Field.OUTPUT].map(index => {
const field = Field[index]
const maxAmount = index === Field.INPUT ? maxAmountInput : maxAmountOutput
return !!maxAmount && !!parsedAmounts[Field[field]]
@ -357,22 +358,23 @@ export default function AddLiquidity({ token0, token1 }) {
// state for txn
const addTransaction = useTransactionAdder()
const [txHash, setTxHash] = useState()
const [txHash, setTxHash] = useState<string>('')
// format ETH value for transaction
function hex(value: JSBI) {
return ethers.utils.bigNumberify(value.toString())
}
// calculate slippage bounds based on current reserves
function calculateSlippageAmount(value: TokenAmount): JSBI[] {
if (value && value.raw) {
const offset = JSBI.divide(JSBI.multiply(JSBI.BigInt(ALLOWED_SLIPPAGE), value.raw), JSBI.BigInt(10000))
return [JSBI.subtract(value.raw, offset), JSBI.add(value.raw, offset)]
} else {
return null
}
return null
}
const [, addPopup] = usePopups()
async function onAdd() {
setAttemptingTxn(true)
const router = getRouterContract(chainId, library, account)
@ -387,6 +389,7 @@ export default function AddLiquidity({ token0, token1 }) {
if (tokens[Field.INPUT] === WETH[chainId] || tokens[Field.OUTPUT] === WETH[chainId]) {
method = router.addLiquidityETH
estimate = router.estimate.addLiquidityETH
args = [
tokens[Field.OUTPUT] === WETH[chainId] ? tokens[Field.INPUT].address : tokens[Field.OUTPUT].address, // token
tokens[Field.OUTPUT] === WETH[chainId] // token desired
@ -466,60 +469,48 @@ export default function AddLiquidity({ token0, token1 }) {
return (
<>
<RowBetween>
<Text color="#565A69" fontWeight={500} fontSize={16}>
{tokens[Field.INPUT]?.symbol} Deposited
</Text>
<TYPE.body>{tokens[Field.INPUT]?.symbol} Deposited</TYPE.body>
<RowFixed>
<TokenLogo address={tokens[Field.INPUT]?.address || ''} style={{ marginRight: '8px' }} />
<Text fontWeight={500} fontSize={16}>
{!!parsedAmounts[Field.INPUT] && parsedAmounts[Field.INPUT].toSignificant(6)}
</Text>
<TYPE.body>{!!parsedAmounts[Field.INPUT] && parsedAmounts[Field.INPUT].toSignificant(6)}</TYPE.body>
</RowFixed>
</RowBetween>
<RowBetween>
<Text color="#565A69" fontWeight={500} fontSize={16}>
{tokens[Field.OUTPUT]?.symbol} Deposited
</Text>
<TYPE.body>{tokens[Field.OUTPUT]?.symbol} Deposited</TYPE.body>
<RowFixed>
<TokenLogo address={tokens[Field.OUTPUT]?.address || ''} style={{ marginRight: '8px' }} />
<Text fontWeight={500} fontSize={16}>
{!!parsedAmounts[Field.OUTPUT] && parsedAmounts[Field.OUTPUT].toSignificant(6)}
</Text>
<TYPE.body>{!!parsedAmounts[Field.OUTPUT] && parsedAmounts[Field.OUTPUT].toSignificant(6)}</TYPE.body>
</RowFixed>
</RowBetween>
{route && !JSBI.equal(route?.midPrice?.raw?.denominator, JSBI.BigInt(0)) && (
<RowBetween>
<TYPE.body>Rate</TYPE.body>
<TYPE.body>
{`1 ${tokens[Field.INPUT]?.symbol} = ${route?.midPrice &&
route?.midPrice?.raw?.denominator &&
route?.midPrice?.adjusted?.toFixed(8)} ${tokens[Field.OUTPUT]?.symbol}`}
</TYPE.body>
</RowBetween>
)}
<RowBetween>
<Text color="#565A69" fontWeight={500} fontSize={16}>
Rate
</Text>
<Text fontWeight={500} fontSize={16}>
{`1 ${tokens[Field.INPUT]?.symbol} = ${route?.midPrice &&
route?.midPrice?.raw?.denominator &&
route.midPrice.adjusted.toFixed(8)} ${tokens[Field.OUTPUT]?.symbol}`}
</Text>
</RowBetween>
<RowBetween>
<Text color="#565A69" fontWeight={500} fontSize={16}>
Minted Pool Share:
</Text>
<Text fontWeight={500} fontSize={16}>
{poolTokenPercentage?.toFixed(6) + '%'}
</Text>
<TYPE.body>Minted Pool Share:</TYPE.body>
<TYPE.body>{noLiquidity ? '100%' : poolTokenPercentage?.toFixed(6) + '%'}</TYPE.body>
</RowBetween>
<ButtonPrimary style={{ margin: '20px 0' }} onClick={onAdd}>
<Text fontWeight={500} fontSize={20}>
Confirm Supply
</Text>
</ButtonPrimary>
<Text fontSize={12} color="#565A69" textAlign="center">
<TYPE.italic fontSize={12} color="#565A69" textAlign="center">
{`Output is estimated. You will receive at least ${liquidityMinted?.toFixed(6)} UNI ${
tokens[Field.INPUT]?.symbol
}/${tokens[Field.OUTPUT]?.symbol} or the transaction will revert.`}
</Text>
</TYPE.italic>
</>
)
}
const pendingText = `Supplied ${parsedAmounts[Field.INPUT]?.toSignificant(6)} ${
const pendingText: string = `Supplied ${parsedAmounts[Field.INPUT]?.toSignificant(6)} ${
tokens[Field.INPUT]?.symbol
} ${'and'} ${parsedAmounts[Field.OUTPUT]?.toSignificant(6)} ${tokens[Field.OUTPUT]?.symbol}`
@ -549,12 +540,12 @@ export default function AddLiquidity({ token0, token1 }) {
<AutoColumn gap="20px">
{noLiquidity && (
<ColumnCenter>
<Text fontWeight={500} style={{ textAlign: 'center' }}>
<TYPE.main textAlign="center">
<span role="img" aria-label="Thinking">
🥇
</span>{' '}
You are the first to add liquidity. Make sure you're setting rates correctly.
</Text>
</TYPE.main>
</ColumnCenter>
)}
<CurrencyInputPanel
@ -592,13 +583,15 @@ export default function AddLiquidity({ token0, token1 }) {
showUnlock={showOutputUnlock}
disableTokenSelect
/>
<RowBetween>
Rate:
<div>
1 {tokens[independentField].symbol} = {route?.midPrice?.toSignificant(6)}
{tokens[dependentField].symbol}
</div>
</RowBetween>
{!noLiquidity && (
<RowBetween>
Rate:
<div>
1 {tokens[independentField].symbol} = {route?.midPrice?.toSignificant(6)}
{tokens[dependentField].symbol}
</div>
</RowBetween>
)}
<ButtonPrimary
onClick={() => {
setShowConfirm(true)

@ -1,157 +0,0 @@
import React, { useState, useEffect } from 'react'
import { withRouter } from 'react-router'
import { createBrowserHistory } from 'history'
import { ethers } from 'ethers'
import styled from 'styled-components'
import { useTranslation } from 'react-i18next'
import ReactGA from 'react-ga'
import { useWeb3React, useFactoryContract } from '../../hooks'
import { Button } from '../../theme'
import AddressInputPanel from '../../components/AddressInputPanel'
import OversizedPanel from '../../components/OversizedPanel'
import { useTokenDetails } from '../../contexts/Tokens'
import { useTransactionAdder } from '../../contexts/Transactions'
const SummaryPanel = styled.div`
${({ theme }) => theme.flexColumnNoWrap};
padding: 1rem 0;
`
const ExchangeRateWrapper = styled.div`
${({ theme }) => theme.flexRowNoWrap};
align-items: center;
color: ${({ theme }) => theme.doveGray};
font-size: 0.75rem;
padding: 0.25rem 1rem 0;
`
const ExchangeRate = styled.span`
flex: 1 1 auto;
width: 0;
color: ${({ theme }) => theme.doveGray};
`
const CreateExchangeWrapper = styled.div`
color: ${({ theme }) => theme.doveGray};
text-align: center;
margin-top: 1rem;
padding-top: 1rem;
`
const SummaryText = styled.div`
font-size: 0.75rem;
color: ${({ error, theme }) => error && theme.salmonRed};
`
const Flex = styled.div`
display: flex;
justify-content: center;
padding: 2rem;
button {
max-width: 20rem;
}
`
function CreateExchange({ location, params }) {
const { t } = useTranslation()
const { account } = useWeb3React()
const factory = useFactoryContract()
const [tokenAddress, setTokenAddress] = useState({
address: params.tokenAddress ? params.tokenAddress : '',
name: ''
})
const [tokenAddressError, setTokenAddressError] = useState()
const { name, symbol, decimals, exchangeAddress } = useTokenDetails(tokenAddress.address)
const addTransaction = useTransactionAdder()
// clear url of query
useEffect(() => {
const history = createBrowserHistory()
history.push(window.location.pathname + '')
}, [])
// validate everything
const [errorMessage, setErrorMessage] = useState(!account && t('noWallet'))
useEffect(() => {
if (tokenAddressError) {
setErrorMessage(t('invalidTokenAddress'))
} else if (symbol === undefined || decimals === undefined || exchangeAddress === undefined) {
setErrorMessage()
} else if (symbol === null) {
setErrorMessage(t('invalidSymbol'))
} else if (decimals === null) {
setErrorMessage(t('invalidDecimals'))
} else if (exchangeAddress !== ethers.constants.AddressZero) {
setErrorMessage(t('exchangeExists'))
} else if (!account) {
setErrorMessage(t('noWallet'))
} else {
setErrorMessage(null)
}
return () => {
setErrorMessage()
}
}, [tokenAddress.address, symbol, decimals, exchangeAddress, account, t, tokenAddressError])
async function createExchange() {
const estimatedGasLimit = await factory.estimate.createExchange(tokenAddress.address)
factory.createExchange(tokenAddress.address, { gasLimit: estimatedGasLimit }).then(response => {
ReactGA.event({
category: 'Transaction',
action: 'Create Exchange'
})
addTransaction(response)
})
}
const isValid = errorMessage === null
return (
<>
<AddressInputPanel
title={t('tokenAddress')}
initialInput={
params.tokenAddress
? { address: params.tokenAddress }
: { address: (location.state && location.state.tokenAddress) || '' }
}
onChange={setTokenAddress}
onError={setTokenAddressError}
/>
<OversizedPanel hideBottom>
<SummaryPanel>
<ExchangeRateWrapper>
<ExchangeRate>{t('name')}</ExchangeRate>
<span>{name ? name : ' - '}</span>
</ExchangeRateWrapper>
<ExchangeRateWrapper>
<ExchangeRate>{t('symbol')}</ExchangeRate>
<span>{symbol ? symbol : ' - '}</span>
</ExchangeRateWrapper>
<ExchangeRateWrapper>
<ExchangeRate>{t('decimals')}</ExchangeRate>
<span>{decimals || decimals === 0 ? decimals : ' - '}</span>
</ExchangeRateWrapper>
</SummaryPanel>
</OversizedPanel>
<CreateExchangeWrapper>
<SummaryText>{errorMessage ? errorMessage : t('enterTokenCont')}</SummaryText>
</CreateExchangeWrapper>
<Flex>
<Button disabled={!isValid} onClick={createExchange}>
{t('createExchange')}
</Button>
</Flex>
</>
)
}
export default withRouter(CreateExchange)

@ -2,7 +2,7 @@ import React, { useReducer, useState, useCallback, useEffect } from 'react'
import styled from 'styled-components'
import { ethers } from 'ethers'
import { parseUnits } from '@ethersproject/units'
import { TokenAmount, JSBI, Route, WETH, Percent } from '@uniswap/sdk'
import { TokenAmount, JSBI, Route, WETH, Percent, Token, Exchange } from '@uniswap/sdk'
import Slider from '../../components/Slider'
import TokenLogo from '../../components/TokenLogo'
@ -21,8 +21,8 @@ import Row, { RowBetween, RowFixed } from '../../components/Row'
import { useToken } from '../../contexts/Tokens'
import { useWeb3React } from '../../hooks'
import { useAllBalances } from '../../contexts/Balances'
import { useTransactionAdder } from '../../contexts/Transactions'
import { useExchangeContract } from '../../hooks'
import { useTransactionAdder } from '../../contexts/Transactions'
import { useExchange, useTotalSupply } from '../../contexts/Exchanges'
import { BigNumber } from 'ethers/utils'
@ -145,34 +145,34 @@ const ConfirmedText = styled(Text)`
export default function RemoveLiquidity({ token0, token1 }) {
const { account, chainId, library } = useWeb3React()
const routerAddress = ROUTER_ADDRESSES[chainId]
const routerAddress: string = ROUTER_ADDRESSES[chainId]
const [showConfirm, setShowConfirm] = useState(false)
const [showAdvanced, setShowAdvanced] = useState(false)
const [showConfirm, setShowConfirm] = useState<boolean>(false)
const [showAdvanced, setShowAdvanced] = useState<boolean>(false)
const inputToken = useToken(token0)
const outputToken = useToken(token1)
const inputToken: Token = useToken(token0)
const outputToken: Token = useToken(token1)
// get basic SDK entities
const tokens = {
const tokens: { [field: number]: Token } = {
[Field.TOKEN0]: inputToken,
[Field.TOKEN1]: outputToken
}
const exchange = useExchange(inputToken, outputToken)
const exchangeContract = useExchangeContract(exchange?.liquidityToken.address)
const exchange: Exchange = useExchange(inputToken, outputToken)
const exchangeContract: ethers.Contract = useExchangeContract(exchange?.liquidityToken.address)
// pool token data
const totalPoolTokens = useTotalSupply(exchange)
const totalPoolTokens: TokenAmount = useTotalSupply(exchange)
const allBalances = useAllBalances()
const userLiquidity = allBalances?.[account]?.[exchange?.liquidityToken?.address]
const allBalances: TokenAmount[] = useAllBalances()
const userLiquidity: TokenAmount = allBalances?.[account]?.[exchange?.liquidityToken?.address]
// input state
const [state, dispatch] = useReducer(reducer, initializeRemoveState(userLiquidity?.toExact(), token0, token1))
const { independentField, typedValue } = state
const TokensDeposited = {
const TokensDeposited: { [field: number]: TokenAmount } = {
[Field.TOKEN0]:
exchange &&
totalPoolTokens &&
@ -185,7 +185,7 @@ export default function RemoveLiquidity({ token0, token1 }) {
exchange.getLiquidityValue(tokens[Field.TOKEN1], totalPoolTokens, userLiquidity, false)
}
const route = exchange
const route: Route = exchange
? new Route([exchange], independentField !== Field.LIQUIDITY ? tokens[independentField] : tokens[Field.TOKEN1])
: undefined
@ -307,11 +307,11 @@ export default function RemoveLiquidity({ token0, token1 }) {
: false
// errors
const [generalError, setGeneralError] = useState('')
const [inputError, setInputError] = useState('')
const [outputError, setOutputError] = useState('')
const [poolTokenError, setPoolTokenError] = useState('')
const [isValid, setIsValid] = useState(false)
const [generalError, setGeneralError] = useState<string>('')
const [inputError, setInputError] = useState<string>('')
const [outputError, setOutputError] = useState<string>('')
const [poolTokenError, setPoolTokenError] = useState<string>('')
const [isValid, setIsValid] = useState<boolean>(false)
// update errors live
useEffect(() => {
@ -351,7 +351,7 @@ export default function RemoveLiquidity({ token0, token1 }) {
const addTransaction = useTransactionAdder()
const [txHash, setTxHash] = useState()
const [sigInputs, setSigInputs] = useState([])
const [deadline, setDeadline] = useState()
const [deadline, setDeadline] = useState(null)
const [signed, setSigned] = useState(false) // waiting for signature sign
const [attemptedRemoval, setAttemptedRemoval] = useState(false) // clicke confirm
const [pendingConfirmation, setPendingConfirmation] = useState(true) // waiting for
@ -359,7 +359,7 @@ export default function RemoveLiquidity({ token0, token1 }) {
async function onSign() {
const nonce = await exchangeContract.nonces(account)
const newDeadline = Math.ceil(Date.now() / 1000) + DEADLINE_FROM_NOW
const newDeadline: number = Math.ceil(Date.now() / 1000) + DEADLINE_FROM_NOW
setDeadline(newDeadline)
const EIP712Domain = [
@ -428,6 +428,7 @@ export default function RemoveLiquidity({ token0, token1 }) {
: parsedAmounts[Field.TOKEN0].raw.toString(),
account,
deadline,
false,
sigInputs[0],
sigInputs[1],
sigInputs[2]
@ -445,6 +446,7 @@ export default function RemoveLiquidity({ token0, token1 }) {
parsedAmounts[Field.TOKEN1].raw.toString(),
account,
deadline,
false,
sigInputs[0],
sigInputs[1],
sigInputs[2]
@ -466,18 +468,13 @@ export default function RemoveLiquidity({ token0, token1 }) {
setTxHash(response.hash)
addTransaction(response)
})
.catch(() => {
.catch(e => {
console.log(e)
resetModalState()
setShowConfirm(false)
})
}
/**
* @todo
* if the input values stay the same,
* we should probably not reset the signature values,
* move to an effect
*/
function resetModalState() {
setSigned(false)
setSigInputs(null)
@ -562,7 +559,7 @@ export default function RemoveLiquidity({ token0, token1 }) {
</>
)
}
const pendingText = `Removed ${parsedAmounts[Field.TOKEN0]?.toSignificant(6)} ${
const pendingText: string = `Removed ${parsedAmounts[Field.TOKEN0]?.toSignificant(6)} ${
tokens[Field.TOKEN0]?.symbol
} and ${parsedAmounts[Field.TOKEN1]?.toSignificant(6)} ${tokens[Field.TOKEN1]?.symbol}`

@ -72,7 +72,7 @@ const theme = darkMode => ({
tokenRowHover: darkMode ? '#404040' : '#F2F2F2',
outlineGrey: darkMode ? '#292C2F' : '#EDEEF2',
darkGrey: darkMode ? '#888D9B' : '#888D9B',
darkGray: darkMode ? '#888D9B' : '#888D9B',
//blacks
charcoalBlack: darkMode ? '#F2F2F2' : '#404040',
@ -85,6 +85,7 @@ const theme = darkMode => ({
// purples
wisteriaPurple: '#DC6BE5',
// reds
salmonRed: '#FF6871',
// orange
@ -93,6 +94,7 @@ const theme = darkMode => ({
warningYellow: '#FFE270',
// pink
uniswapPink: '#DC6BE5',
darkPink: '#ff007a',
//green
connectedGreen: '#27AE60',
@ -124,6 +126,16 @@ export const TYPE = {
{children}
</Text>
),
largeHeader: ({ children, ...rest }) => (
<Text fontWeight={600} fontSize={24} color={theme().black} {...rest}>
{children}
</Text>
),
body: ({ children, ...rest }) => (
<Text fontWeight={500} fontSize={16} color={'#565A69'} {...rest}>
{children}
</Text>
),
blue: ({ children, ...rest }) => (
<Text fontWeight={500} color={theme().royalBlue} {...rest}>
{children}
@ -134,6 +146,11 @@ export const TYPE = {
{children}
</Text>
),
darkGray: ({ children, ...rest }) => (
<Text fontWeight={500} color={theme().darkGray} {...rest}>
{children}
</Text>
),
italic: ({ children, ...rest }) => (
<Text fontWeight={500} fontSize={12} fontStyle={'italic'} color={theme().mineshaftGray} {...rest}>
{children}

@ -5,7 +5,7 @@ import EXCHANGE_ABI from '../constants/abis/exchange'
import ROUTER_ABI from '../constants/abis/router'
import ERC20_ABI from '../constants/abis/erc20'
import ERC20_BYTES32_ABI from '../constants/abis/erc20_bytes32'
import { FACTORY_ADDRESSES, SUPPORTED_THEMES } from '../constants'
import { FACTORY_ADDRESSES, SUPPORTED_THEMES, ROUTER_ADDRESSES } from '../constants'
import { bigNumberify, keccak256, defaultAbiCoder, toUtf8Bytes, solidityPack } from 'ethers/utils'
import UncheckedJsonRpcSigner from './signer'
@ -131,8 +131,8 @@ export function getContract(address, ABI, library, account) {
}
// account is optional
export function getRouterContract(networkId, library, account) {
const router = getContract('0xd9210Ff5A0780E083BB40e30d005d93a2DcFA4EF', ROUTER_ABI, library, account)
export function getRouterContract(chainId, library, account) {
const router = getContract(ROUTER_ADDRESSES[chainId], ROUTER_ABI, library, account)
return router
}

@ -1319,7 +1319,7 @@
"@ethersproject/logger" ">=5.0.0-beta.129"
"@ethersproject/strings" ">=5.0.0-beta.130"
"@ethersproject/keccak256@>=5.0.0-beta.127", "@ethersproject/keccak256@^5.0.0-beta.130", "@ethersproject/keccak256@^5.0.0-beta.131":
"@ethersproject/keccak256@>=5.0.0-beta.127", "@ethersproject/keccak256@^5.0.0-beta.130":
version "5.0.0-beta.131"
resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.0.0-beta.131.tgz#b5778723ee75208065b9b9ad30c71d480f41bb31"
integrity sha512-KQnqMwGV0IMOjAr/UTFO8DuLrmN1uaMvcV3zh9hiXhh3rCuY+WXdeUh49w1VQ94kBKmaP0qfGb7z4SdhUWUHjw==
@ -1382,6 +1382,15 @@
dependencies:
"@ethersproject/bytes" ">=5.0.0-beta.129"
"@ethersproject/sha2@>=5.0.0-beta.129":
version "5.0.0-beta.135"
resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.0.0-beta.135.tgz#e597572ba991fe044d50f8d75704bb4a2b2c64b4"
integrity sha512-DK/cUT5ilCVLtf1xk7XDPB9xGHsXiU3TsULKsEg823cTBIhFl2l0IiHAGqu9uiMlSJRpb2BwrWQuMgmFe/vMwQ==
dependencies:
"@ethersproject/bytes" ">=5.0.0-beta.129"
"@ethersproject/logger" ">=5.0.0-beta.129"
hash.js "1.1.3"
"@ethersproject/signing-key@>=5.0.0-beta.129":
version "5.0.0-beta.135"
resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.0.0-beta.135.tgz#f739e800aad9e01b77a8ec2c353b9b66ce5738fa"
@ -1392,6 +1401,17 @@
"@ethersproject/properties" ">=5.0.0-beta.131"
elliptic "6.5.2"
"@ethersproject/solidity@^5.0.0-beta.131":
version "5.0.0-beta.131"
resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.0.0-beta.131.tgz#7d826e98cc0a29e25f0ff52ae17c07483f5d93d4"
integrity sha512-i5vuj2CXGMkVPo08bmElC2cvhjRDNRZZ8nzvx2WCi75Zh42xD0XNV77E9ZLYgS0WoZSiAi/F71nXSBnM7FAqJg==
dependencies:
"@ethersproject/bignumber" ">=5.0.0-beta.130"
"@ethersproject/bytes" ">=5.0.0-beta.129"
"@ethersproject/keccak256" ">=5.0.0-beta.127"
"@ethersproject/sha2" ">=5.0.0-beta.129"
"@ethersproject/strings" ">=5.0.0-beta.130"
"@ethersproject/strings@>=5.0.0-beta.130":
version "5.0.0-beta.136"
resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.0.0-beta.136.tgz#053cbf4f9f96a7537cbc50300597f2d707907f51"
@ -2365,16 +2385,16 @@
semver "^6.3.0"
tsutils "^3.17.1"
"@uniswap/sdk@@uniswap/sdk@2.0.0-beta.17":
version "2.0.0-beta.17"
resolved "https://registry.yarnpkg.com/@uniswap/sdk/-/sdk-2.0.0-beta.17.tgz#8f24be0375d5f8137eae75afe75b2356c75bb793"
integrity sha512-Nd3S/VE51z4jsNs9G9hkslUkS862dpslnU86lXEJi7mbbbPIagh31iR0s/uBPnrBFGiktucgvzRn6WJJIvojWA==
"@uniswap/sdk@@uniswap/sdk@2.0.0-beta.19":
version "2.0.0-beta.19"
resolved "https://registry.yarnpkg.com/@uniswap/sdk/-/sdk-2.0.0-beta.19.tgz#1f0228a1d5451d62f209e09c48cd1d6bea5ffe01"
integrity sha512-mqDZkeX2TU7e3yOOKHSeUryv94//mJ6dsU6dCv6FExrfOY9yi6+O5ZWpe43rVi0XFVdQGRIRhJapdWKWaGsung==
dependencies:
"@ethersproject/address" "^5.0.0-beta.134"
"@ethersproject/contracts" "^5.0.0-beta.143"
"@ethersproject/keccak256" "^5.0.0-beta.131"
"@ethersproject/networks" "^5.0.0-beta.135"
"@ethersproject/providers" "^5.0.0-beta.153"
"@ethersproject/solidity" "^5.0.0-beta.131"
big.js "^5.2.2"
decimal.js-light "^2.5.0"
jsbi "^3.1.1"