Commit 6211dff0 authored by ianlapham's avatar ianlapham

typed pages

parent 655b7956
...@@ -3,6 +3,8 @@ import styled from 'styled-components' ...@@ -3,6 +3,8 @@ import styled from 'styled-components'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { transparentize } from 'polished' import { transparentize } from 'polished'
import QR from '../../assets/svg/QR.svg'
import { isAddress } from '../../utils' import { isAddress } from '../../utils'
import { useWeb3React, useDebounce } from '../../hooks' import { useWeb3React, useDebounce } from '../../hooks'
...@@ -13,6 +15,7 @@ const InputPanel = styled.div` ...@@ -13,6 +15,7 @@ const InputPanel = styled.div`
border-radius: 1.25rem; border-radius: 1.25rem;
background-color: ${({ theme }) => theme.inputBackground}; background-color: ${({ theme }) => theme.inputBackground};
z-index: 1; z-index: 1;
width: 100%;
` `
const ContainerRow = styled.div` const ContainerRow = styled.div`
...@@ -49,7 +52,7 @@ const LabelContainer = styled.div` ...@@ -49,7 +52,7 @@ const LabelContainer = styled.div`
const InputRow = styled.div` const InputRow = styled.div`
${({ theme }) => theme.flexRowNoWrap} ${({ theme }) => theme.flexRowNoWrap}
align-items: center; align-items: center;
padding: 0.25rem 0.85rem 0.75rem; padding: 0.75rem;
` `
const Input = styled.input` const Input = styled.input`
...@@ -69,7 +72,17 @@ 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 { t } = useTranslation()
const { library } = useWeb3React() const { library } = useWeb3React()
...@@ -166,11 +179,6 @@ export default function AddressInputPanel({ title, initialInput = '', onChange = ...@@ -166,11 +179,6 @@ export default function AddressInputPanel({ title, initialInput = '', onChange =
<InputPanel> <InputPanel>
<ContainerRow error={input !== '' && error}> <ContainerRow error={input !== '' && error}>
<InputContainer> <InputContainer>
<LabelRow>
<LabelContainer>
<span>{title || t('recipientAddress')}</span>
</LabelContainer>
</LabelRow>
<InputRow> <InputRow>
<Input <Input
type="text" type="text"
...@@ -183,6 +191,9 @@ export default function AddressInputPanel({ title, initialInput = '', onChange = ...@@ -183,6 +191,9 @@ export default function AddressInputPanel({ title, initialInput = '', onChange =
onChange={onInput} onChange={onInput}
value={input} value={input}
/> />
<QRWrapper>
<img src={QR} alt="" />
</QRWrapper>
</InputRow> </InputRow>
</InputContainer> </InputContainer>
</ContainerRow> </ContainerRow>
......
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>
)
}
import React from 'react' import React from 'react'
import { Button as RebassButton } from 'rebass/styled-components'
import styled from 'styled-components' import styled from 'styled-components'
import { darken, lighten } from 'polished' import { darken, lighten } from 'polished'
import { RowBetween } from '../Row' import { RowBetween } from '../Row'
import { ChevronDown } from 'react-feather' import { ChevronDown } from 'react-feather'
import { Button as RebassButton } from 'rebass/styled-components'
const Base = styled(RebassButton)` const Base = styled(RebassButton)`
padding: ${({ padding }) => (padding ? padding : '18px')}; padding: ${({ padding }) => (padding ? padding : '18px')};
width: ${({ width }) => (width ? width : '100%')}; width: ${({ width }) => (width ? width : '100%')};
font-size: 1rem;
font-weight: 500; font-weight: 500;
text-align: center; text-align: center;
border-radius: 20px; border-radius: 20px;
...@@ -39,7 +38,7 @@ export const ButtonPrimary = styled(Base)` ...@@ -39,7 +38,7 @@ export const ButtonPrimary = styled(Base)`
} }
&:disabled { &:disabled {
background-color: ${({ theme }) => theme.outlineGrey}; background-color: ${({ theme }) => theme.outlineGrey};
color: ${({ theme }) => theme.darkGrey} color: ${({ theme }) => theme.darkGray}
cursor: auto; cursor: auto;
box-shadow: none; box-shadow: none;
} }
...@@ -48,6 +47,7 @@ export const ButtonPrimary = styled(Base)` ...@@ -48,6 +47,7 @@ export const ButtonPrimary = styled(Base)`
export const ButtonSecondary = styled(Base)` export const ButtonSecondary = styled(Base)`
background-color: #ebf4ff; background-color: #ebf4ff;
color: #2172e5; color: #2172e5;
font-size: 16px;
border-radius: 8px; border-radius: 8px;
padding: 10px; padding: 10px;
...@@ -69,6 +69,30 @@ export const ButtonSecondary = styled(Base)` ...@@ -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)` export const ButtonEmpty = styled(Base)`
border: 1px solid #edeef2; border: 1px solid #edeef2;
background-color: transparent; background-color: transparent;
...@@ -157,7 +181,7 @@ export function ButtonError({ children, error, ...rest }) { ...@@ -157,7 +181,7 @@ export function ButtonError({ children, error, ...rest }) {
} }
} }
export function ButtonDropwdown({ disabled, children, ...rest }) { export function ButtonDropwdown({ disabled = false, children, ...rest }) {
return ( return (
<ButtonPrimary {...rest}> <ButtonPrimary {...rest}>
<RowBetween> <RowBetween>
...@@ -168,7 +192,7 @@ export function ButtonDropwdown({ disabled, children, ...rest }) { ...@@ -168,7 +192,7 @@ export function ButtonDropwdown({ disabled, children, ...rest }) {
) )
} }
export function ButtonDropwdownLight({ disabled, children, ...rest }) { export function ButtonDropwdownLight({ disabled = false, children, ...rest }) {
return ( return (
<ButtonEmpty {...rest}> <ButtonEmpty {...rest}>
<RowBetween> <RowBetween>
......
import React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { Text } from 'rebass'
import { Box } from 'rebass/styled-components' import { Box } from 'rebass/styled-components'
const Card = styled(Box)` const Card = styled(Box)`
...@@ -18,3 +20,21 @@ export const LightCard = styled(Card)` ...@@ -18,3 +20,21 @@ export const LightCard = styled(Card)`
export const GreyCard = styled(Card)` export const GreyCard = styled(Card)`
background-color: rgba(255, 255, 255, 0.9); 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>
)
}
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
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' ...@@ -8,6 +8,7 @@ import { WETH } from '@uniswap/sdk'
import TokenLogo from '../TokenLogo' import TokenLogo from '../TokenLogo'
import DoubleLogo from '../DoubleLogo' import DoubleLogo from '../DoubleLogo'
import SearchModal from '../SearchModal' import SearchModal from '../SearchModal'
import { TYPE } from '../../theme'
import { Text } from 'rebass' import { Text } from 'rebass'
import { RowBetween } from '../Row' import { RowBetween } from '../Row'
import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg' import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg'
...@@ -20,6 +21,8 @@ import { calculateGasMargin } from '../../utils' ...@@ -20,6 +21,8 @@ import { calculateGasMargin } from '../../utils'
import { useAddressBalance } from '../../contexts/Balances' import { useAddressBalance } from '../../contexts/Balances'
import { useTransactionAdder, usePendingApproval } from '../../contexts/Transactions' import { useTransactionAdder, usePendingApproval } from '../../contexts/Transactions'
import { ROUTER_ADDRESSES } from '../../constants'
const GAS_MARGIN = ethers.utils.bigNumberify(1000) const GAS_MARGIN = ethers.utils.bigNumberify(1000)
const SubCurrencySelect = styled.button` const SubCurrencySelect = styled.button`
...@@ -51,26 +54,18 @@ const CurrencySelect = styled.button` ...@@ -51,26 +54,18 @@ const CurrencySelect = styled.button`
font-size: 20px; font-size: 20px;
background-color: ${({ selected, theme }) => (selected ? theme.buttonBackgroundPlain : theme.royalBlue)}; background-color: ${({ selected, theme }) => (selected ? theme.buttonBackgroundPlain : theme.royalBlue)};
color: ${({ selected, theme }) => (selected ? theme.textColor : theme.white)}; 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; border-radius: 8px;
outline: none; outline: none;
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
border: 1px solid
${({ selected, theme }) => (selected ? darken(0.1, theme.outlineGrey) : darken(0.1, theme.royalBlue))};
:focus,
:hover { :hover {
border: 1px solid border: 1px solid
${({ selected, theme }) => (selected ? darken(0.1, theme.outlineGrey) : darken(0.1, theme.royalBlue))}; ${({ selected, theme }) => (selected ? darken(0.2, theme.outlineGrey) : darken(0.2, 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)};
} }
` `
...@@ -99,12 +94,8 @@ const InputPanel = styled.div` ...@@ -99,12 +94,8 @@ const InputPanel = styled.div`
const Container = styled.div` const Container = styled.div`
border-radius: ${({ hideInput }) => (hideInput ? '8px' : '20px')}; 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}; background-color: ${({ theme }) => theme.inputBackground};
:focus-within {
border: 1px solid ${({ error, theme }) => (error ? theme.salmonRed : theme.malibuBlue)};
}
` `
const LabelRow = styled.div` const LabelRow = styled.div`
...@@ -113,7 +104,7 @@ const LabelRow = styled.div` ...@@ -113,7 +104,7 @@ const LabelRow = styled.div`
color: ${({ theme }) => theme.doveGray}; color: ${({ theme }) => theme.doveGray};
font-size: 0.75rem; font-size: 0.75rem;
line-height: 1rem; line-height: 1rem;
padding: 0.75rem 1rem 0; padding: 0.5rem 1rem 1rem 1rem;
span:hover { span:hover {
cursor: pointer; cursor: pointer;
color: ${({ theme }) => darken(0.2, theme.doveGray)}; color: ${({ theme }) => darken(0.2, theme.doveGray)};
...@@ -161,12 +152,12 @@ export default function CurrencyInputPanel({ ...@@ -161,12 +152,12 @@ export default function CurrencyInputPanel({
value, value,
field, field,
onUserInput, onUserInput,
onTokenSelection = null,
title, title,
onMax, onMax,
atMax, atMax,
error, error,
urlAddedTokens = [], // used urlAddedTokens = [], // used
onTokenSelection = null,
token = null, token = null,
showUnlock = false, // used to show unlock if approval needed showUnlock = false, // used to show unlock if approval needed
disableUnlock = false, disableUnlock = false,
...@@ -176,23 +167,19 @@ export default function CurrencyInputPanel({ ...@@ -176,23 +167,19 @@ export default function CurrencyInputPanel({
exchange = null, // used for double token logo exchange = null, // used for double token logo
customBalance = null, // used for LP balances instead of token balance customBalance = null, // used for LP balances instead of token balance
hideInput = false, hideInput = false,
showSendWithSwap = false, showSendWithSwap = false
onTokenSelectSendWithSwap = null
}) { }) {
const { account, chainId } = useWeb3React()
const { t } = useTranslation() const { t } = useTranslation()
const addTransaction = useTransactionAdder() const { account, chainId } = useWeb3React()
const routerAddress = ROUTER_ADDRESSES[chainId]
const addTransaction = useTransactionAdder()
const [modalOpen, setModalOpen] = useState(false) const [modalOpen, setModalOpen] = useState(false)
// this one causes the infinite loop
const userTokenBalance = useAddressBalance(account, token) const userTokenBalance = useAddressBalance(account, token)
const tokenContract = useTokenContract(token?.address) const tokenContract = useTokenContract(token?.address)
const pendingApproval = usePendingApproval(token?.address) const pendingApproval = usePendingApproval(token?.address)
const routerAddress = '0xd9210Ff5A0780E083BB40e30d005d93a2DcFA4EF'
function renderUnlockButton() { function renderUnlockButton() {
if ( if (
disableUnlock || disableUnlock ||
...@@ -240,19 +227,6 @@ export default function CurrencyInputPanel({ ...@@ -240,19 +227,6 @@ export default function CurrencyInputPanel({
return ( return (
<InputPanel> <InputPanel>
<Container error={!!error} hideInput={hideInput}> <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}> <InputRow style={hideInput ? { padding: '0', borderRadius: '8px' } : {}} hideInput={hideInput}>
{!hideInput && ( {!hideInput && (
<> <>
...@@ -267,7 +241,7 @@ export default function CurrencyInputPanel({ ...@@ -267,7 +241,7 @@ export default function CurrencyInputPanel({
</> </>
)} )}
<CurrencySelect <CurrencySelect
selected={!!token?.address} selected={!!token}
onClick={() => { onClick={() => {
if (!disableTokenSelect) { if (!disableTokenSelect) {
setModalOpen(true) setModalOpen(true)
...@@ -292,6 +266,19 @@ export default function CurrencyInputPanel({ ...@@ -292,6 +266,19 @@ export default function CurrencyInputPanel({
</Aligner> </Aligner>
</CurrencySelect> </CurrencySelect>
</InputRow> </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> </Container>
{!disableTokenSelect && ( {!disableTokenSelect && (
<SearchModal <SearchModal
...@@ -304,7 +291,6 @@ export default function CurrencyInputPanel({ ...@@ -304,7 +291,6 @@ export default function CurrencyInputPanel({
field={field} field={field}
onTokenSelect={onTokenSelection} onTokenSelect={onTokenSelection}
showSendWithSwap={showSendWithSwap} showSendWithSwap={showSendWithSwap}
onTokenSelectSendWithSwap={onTokenSelectSendWithSwap}
/> />
)} )}
</InputPanel> </InputPanel>
......
...@@ -2,33 +2,34 @@ import React, { useState, useReducer, useCallback, useEffect } from 'react' ...@@ -2,33 +2,34 @@ import React, { useState, useReducer, useCallback, useEffect } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { ethers } from 'ethers' import { ethers } from 'ethers'
import { parseUnits, parseEther } from '@ethersproject/units' 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 TokenLogo from '../TokenLogo'
import AddressInputPanel from '../AddressInputPanel'
import QuestionHelper from '../Question' import QuestionHelper from '../Question'
import NumericalInput from '../NumericalInput' import NumericalInput from '../NumericalInput'
import AdvancedSettings from '../AdvancedSettings'
import ConfirmationModal from '../ConfirmationModal' import ConfirmationModal from '../ConfirmationModal'
import CurrencyInputPanel from '../CurrencyInputPanel' import CurrencyInputPanel from '../CurrencyInputPanel'
import { Link } from '../../theme/components' import { Link } from '../../theme/components'
import { Text } from 'rebass' import { Text } from 'rebass'
import { TYPE } from '../../theme' import { TYPE } from '../../theme'
import { GreyCard, LightCard } from '../../components/Card'
import { ArrowDown, ArrowUp } from 'react-feather' 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 { 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 { useToken } from '../../contexts/Tokens'
import { usePopups } from '../../contexts/Application'
import { useExchange } from '../../contexts/Exchanges' import { useExchange } from '../../contexts/Exchanges'
import { useWeb3React, useTokenContract } from '../../hooks'
import { useAddressBalance } from '../../contexts/Balances'
import { useTransactionAdder } from '../../contexts/Transactions' import { useTransactionAdder } from '../../contexts/Transactions'
import { useAddressAllowance } from '../../contexts/Allowances' import { useAddressAllowance } from '../../contexts/Allowances'
import { useWeb3React, useTokenContract } from '../../hooks'
import { useAddressBalance, useAllBalances } from '../../contexts/Balances'
import { ROUTER_ADDRESSES } from '../../constants' import { ROUTER_ADDRESSES } from '../../constants'
import { getRouterContract, calculateGasMargin, isAddress, getProviderOrSigner } from '../../utils' import { getRouterContract, calculateGasMargin, getProviderOrSigner } from '../../utils'
const Wrapper = styled.div` const Wrapper = styled.div`
position: relative; position: relative;
...@@ -59,42 +60,11 @@ const ErrorText = styled(Text)` ...@@ -59,42 +60,11 @@ const ErrorText = styled(Text)`
warningHigh ? theme.salmonRed : warningMedium ? theme.warningYellow : theme.textColor}; 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)` const InputGroup = styled(AutoColumn)`
position: relative; position: relative;
padding: 40px 0; 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)` const StyledNumerical = styled(NumericalInput)`
text-align: center; text-align: center;
font-size: 48px; font-size: 48px;
...@@ -227,13 +197,6 @@ function hex(value: JSBI) { ...@@ -227,13 +197,6 @@ function hex(value: JSBI) {
return ethers.utils.bigNumberify(value.toString()) return ethers.utils.bigNumberify(value.toString())
} }
const SLIPPAGE_INDEX = {
1: 1,
2: 2,
3: 3,
4: 4
}
const SWAP_TYPE = { const SWAP_TYPE = {
EXACT_TOKENS_FOR_TOKENS: 'EXACT_TOKENS_FOR_TOKENS', EXACT_TOKENS_FOR_TOKENS: 'EXACT_TOKENS_FOR_TOKENS',
EXACT_TOKENS_FOR_ETH: 'EXACT_TOKENS_FOR_ETH', EXACT_TOKENS_FOR_ETH: 'EXACT_TOKENS_FOR_ETH',
...@@ -257,52 +220,50 @@ const ALLOWED_SLIPPAGE_HIGH = 500 ...@@ -257,52 +220,50 @@ const ALLOWED_SLIPPAGE_HIGH = 500
export default function ExchangePage({ sendingInput = false }) { export default function ExchangePage({ sendingInput = false }) {
const { chainId, account, library } = useWeb3React() const { chainId, account, library } = useWeb3React()
const routerAddress = ROUTER_ADDRESSES[chainId] const routerAddress: string = ROUTER_ADDRESSES[chainId]
// adding notifications on txns // adding notifications on txns
const [, addPopup] = usePopups() const [, addPopup] = usePopups()
const addTransaction = useTransactionAdder() const addTransaction = useTransactionAdder()
// sending state // sending state
const [sending, setSending] = useState(sendingInput) const [sending] = useState<boolean>(sendingInput)
const [sendingWithSwap, setSendingWithSwap] = useState(false) const [sendingWithSwap, setSendingWithSwap] = useState<boolean>(false)
const [recipient, setRecipient] = useState('') const [recipient, setRecipient] = useState<string>('')
// input details // trade details
const [state, dispatch] = useReducer(reducer, WETH[chainId].address, initializeSwapState) const [state, dispatch] = useReducer(reducer, WETH[chainId].address, initializeSwapState)
const { independentField, typedValue, ...fieldData } = state const { independentField, typedValue, ...fieldData } = state
const dependentField = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
const tradeType = independentField === Field.INPUT ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT const tradeType: TradeType = independentField === Field.INPUT ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT
const [tradeError, setTradeError] = useState('') // error for things like reserve size or route const [tradeError, setTradeError] = useState<string>('') // error for things like reserve size or route
const tokens = { const tokens = {
[Field.INPUT]: useToken(fieldData[Field.INPUT].address), [Field.INPUT]: useToken(fieldData[Field.INPUT].address),
[Field.OUTPUT]: useToken(fieldData[Field.OUTPUT].address) [Field.OUTPUT]: useToken(fieldData[Field.OUTPUT].address)
} }
const exchange = useExchange(tokens[Field.INPUT], tokens[Field.OUTPUT]) const exchange: Exchange = useExchange(tokens[Field.INPUT], tokens[Field.OUTPUT])
const route = !!exchange ? new Route([exchange], tokens[Field.INPUT]) : undefined 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 // modal and loading
const [showConfirm, setShowConfirm] = useState(false) const [showConfirm, setShowConfirm] = useState<boolean>(false)
const [pendingConfirmation, setPendingConfirmation] = useState(true) // waiting for user confirmation const [showAdvanced, setShowAdvanced] = useState<boolean>(false)
const [attemptingTxn, setAttemptingTxn] = useState(false) // clicked confirmed const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // clicked confirmed
const [pendingConfirmation, setPendingConfirmation] = useState<boolean>(true) // waiting for user confirmation
// 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)
// txn values // txn values
const [txHash, setTxHash] = useState() const [txHash, setTxHash] = useState<string>('')
const [deadline, setDeadline] = useState(DEFAULT_DEADLINE_FROM_NOW) const [deadline, setDeadline] = useState<number>(DEFAULT_DEADLINE_FROM_NOW)
const [allowedSlippage, setAllowedSlippage] = useState(INITIAL_ALLOWED_SLIPPAGE) const [allowedSlippage, setAllowedSlippage] = useState<number>(INITIAL_ALLOWED_SLIPPAGE)
// approvals // approvals
const inputApproval = useAddressAllowance(account, tokens[Field.INPUT], routerAddress) const inputApproval: TokenAmount = useAddressAllowance(account, tokens[Field.INPUT], routerAddress)
const outputApproval = useAddressAllowance(account, tokens[Field.OUTPUT], 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 // get user- and token-specific lookup data
const userBalances = { const userBalances = {
...@@ -311,18 +272,13 @@ export default function ExchangePage({ sendingInput = false }) { ...@@ -311,18 +272,13 @@ export default function ExchangePage({ sendingInput = false }) {
} }
const parsedAmounts: { [field: number]: TokenAmount } = {} const parsedAmounts: { [field: number]: TokenAmount } = {}
// try to parse typed value
if (typedValue !== '' && typedValue !== '.' && tokens[independentField]) { if (typedValue !== '' && typedValue !== '.' && tokens[independentField]) {
try { try {
const typedValueParsed = parseUnits(typedValue, tokens[independentField].decimals).toString() const typedValueParsed = parseUnits(typedValue, tokens[independentField].decimals).toString()
if (typedValueParsed !== '0') if (typedValueParsed !== '0')
parsedAmounts[independentField] = new TokenAmount(tokens[independentField], typedValueParsed) parsedAmounts[independentField] = new TokenAmount(tokens[independentField], typedValueParsed)
} catch (error) { } catch (error) {
// should only fail if the user specifies too many decimal places of precision (or maybe exceed max uint?) console.error(error)
/**
* @todo reserve limit error here
*/
console.error('found error here ')
} }
} }
...@@ -335,12 +291,11 @@ export default function ExchangePage({ sendingInput = false }) { ...@@ -335,12 +291,11 @@ export default function ExchangePage({ sendingInput = false }) {
: undefined : undefined
} catch (error) {} } catch (error) {}
const slippageFromTrade = trade && trade.slippage const slippageFromTrade: Percent = trade && trade.slippage
if (trade) if (trade)
parsedAmounts[dependentField] = tradeType === TradeType.EXACT_INPUT ? trade.outputAmount : trade.inputAmount parsedAmounts[dependentField] = tradeType === TradeType.EXACT_INPUT ? trade.outputAmount : trade.inputAmount
// get formatted amounts
const formattedAmounts = { const formattedAmounts = {
[independentField]: typedValue, [independentField]: typedValue,
[dependentField]: parsedAmounts[dependentField] ? parsedAmounts[dependentField].toSignificant(8) : '' [dependentField]: parsedAmounts[dependentField] ? parsedAmounts[dependentField].toSignificant(8) : ''
...@@ -384,8 +339,8 @@ export default function ExchangePage({ sendingInput = false }) { ...@@ -384,8 +339,8 @@ export default function ExchangePage({ sendingInput = false }) {
}) })
}, []) }, [])
const MIN_ETHER = chainId && new TokenAmount(WETH[chainId], JSBI.BigInt(parseEther('.01'))) const MIN_ETHER: TokenAmount = chainId && new TokenAmount(WETH[chainId], JSBI.BigInt(parseEther('.01')))
const maxAmountInput = const maxAmountInput: TokenAmount =
!!userBalances[Field.INPUT] && !!userBalances[Field.INPUT] &&
JSBI.greaterThan( JSBI.greaterThan(
userBalances[Field.INPUT].raw, userBalances[Field.INPUT].raw,
...@@ -395,22 +350,22 @@ export default function ExchangePage({ sendingInput = false }) { ...@@ -395,22 +350,22 @@ export default function ExchangePage({ sendingInput = false }) {
? userBalances[Field.INPUT].subtract(MIN_ETHER) ? userBalances[Field.INPUT].subtract(MIN_ETHER)
: userBalances[Field.INPUT] : userBalances[Field.INPUT]
: undefined : undefined
const atMaxAmountInput = const atMaxAmountInput: boolean =
!!maxAmountInput && !!parsedAmounts[Field.INPUT] !!maxAmountInput && !!parsedAmounts[Field.INPUT]
? JSBI.equal(maxAmountInput.raw, parsedAmounts[Field.INPUT].raw) ? JSBI.equal(maxAmountInput.raw, parsedAmounts[Field.INPUT].raw)
: undefined : undefined
const maxAmountOutput = const maxAmountOutput: TokenAmount =
!!userBalances[Field.OUTPUT] && JSBI.greaterThan(userBalances[Field.OUTPUT].raw, JSBI.BigInt(0)) !!userBalances[Field.OUTPUT] && JSBI.greaterThan(userBalances[Field.OUTPUT].raw, JSBI.BigInt(0))
? userBalances[Field.OUTPUT] ? userBalances[Field.OUTPUT]
: undefined : undefined
const atMaxAmountOutput = const atMaxAmountOutput: boolean =
!!maxAmountOutput && !!parsedAmounts[Field.OUTPUT] !!maxAmountOutput && !!parsedAmounts[Field.OUTPUT]
? JSBI.equal(maxAmountOutput.raw, parsedAmounts[Field.OUTPUT].raw) ? JSBI.equal(maxAmountOutput.raw, parsedAmounts[Field.OUTPUT].raw)
: undefined : undefined
function getSwapType() { function getSwapType(): string {
if (tradeType === TradeType.EXACT_INPUT) { if (tradeType === TradeType.EXACT_INPUT) {
if (tokens[Field.INPUT] === WETH[chainId]) { if (tokens[Field.INPUT] === WETH[chainId]) {
return SWAP_TYPE.EXACT_ETH_FOR_TOKENS return SWAP_TYPE.EXACT_ETH_FOR_TOKENS
...@@ -438,7 +393,7 @@ export default function ExchangePage({ sendingInput = false }) { ...@@ -438,7 +393,7 @@ export default function ExchangePage({ sendingInput = false }) {
return null return null
} }
const slippageAdjustedAmounts = { const slippageAdjustedAmounts: { [field: number]: TokenAmount } = {
[Field.INPUT]: [Field.INPUT]:
Field.INPUT === independentField Field.INPUT === independentField
? parsedAmounts[Field.INPUT] ? parsedAmounts[Field.INPUT]
...@@ -451,37 +406,15 @@ export default function ExchangePage({ sendingInput = false }) { ...@@ -451,37 +406,15 @@ export default function ExchangePage({ sendingInput = false }) {
new TokenAmount(tokens[Field.INPUT], calculateSlippageAmount(parsedAmounts[Field.OUTPUT])?.[0]) 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) parsedAmounts[Field.INPUT] && inputApproval && JSBI.greaterThan(parsedAmounts[Field.INPUT].raw, inputApproval.raw)
const showOutputUnlock = const showOutputUnlock: boolean =
parsedAmounts[Field.OUTPUT] && parsedAmounts[Field.OUTPUT] &&
outputApproval && outputApproval &&
JSBI.greaterThan(parsedAmounts[Field.OUTPUT].raw, outputApproval.raw) JSBI.greaterThan(parsedAmounts[Field.OUTPUT].raw, outputApproval.raw)
// parse the input for custom slippage const tokenContract: ethers.Contract = useTokenContract(tokens[Field.INPUT]?.address)
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)
// function for a pure send // function for a pure send
async function onSend() { async function onSend() {
...@@ -494,7 +427,6 @@ export default function ExchangePage({ sendingInput = false }) { ...@@ -494,7 +427,6 @@ export default function ExchangePage({ sendingInput = false }) {
signer signer
.sendTransaction({ to: recipient.toString(), value: hex(parsedAmounts[Field.INPUT].raw) }) .sendTransaction({ to: recipient.toString(), value: hex(parsedAmounts[Field.INPUT].raw) })
.then(response => { .then(response => {
console.log(response)
setTxHash(response.hash) setTxHash(response.hash)
addTransaction(response) addTransaction(response)
setPendingConfirmation(false) setPendingConfirmation(false)
...@@ -537,17 +469,17 @@ export default function ExchangePage({ sendingInput = false }) { ...@@ -537,17 +469,17 @@ export default function ExchangePage({ sendingInput = false }) {
} }
} }
// covers swap or swap with send
async function onSwap() { async function onSwap() {
const routerContract = getRouterContract(chainId, library, account) const routerContract: ethers.Contract = getRouterContract(chainId, library, account)
setAttemptingTxn(true)
setAttemptingTxn(true) // mark that user is attempting transaction
const path = Object.keys(route.path).map(key => { const path = Object.keys(route.path).map(key => {
return route.path[key].address return route.path[key].address
}) })
let estimate: Function, method: Function, args: any[], value: ethers.utils.BigNumber
let estimate: Function, method: Function, args, value const deadlineFromNow: number = Math.ceil(Date.now() / 1000) + deadline
const deadlineFromNow = Math.ceil(Date.now() / 1000) + deadline
const swapType = getSwapType() const swapType = getSwapType()
switch (swapType) { switch (swapType) {
...@@ -558,7 +490,7 @@ export default function ExchangePage({ sendingInput = false }) { ...@@ -558,7 +490,7 @@ export default function ExchangePage({ sendingInput = false }) {
slippageAdjustedAmounts[Field.INPUT].raw.toString(), slippageAdjustedAmounts[Field.INPUT].raw.toString(),
slippageAdjustedAmounts[Field.OUTPUT].raw.toString(), slippageAdjustedAmounts[Field.OUTPUT].raw.toString(),
path, path,
account, sending ? recipient : account,
deadlineFromNow deadlineFromNow
] ]
value = ethers.constants.Zero value = ethers.constants.Zero
...@@ -570,7 +502,7 @@ export default function ExchangePage({ sendingInput = false }) { ...@@ -570,7 +502,7 @@ export default function ExchangePage({ sendingInput = false }) {
slippageAdjustedAmounts[Field.OUTPUT].raw.toString(), slippageAdjustedAmounts[Field.OUTPUT].raw.toString(),
slippageAdjustedAmounts[Field.INPUT].raw.toString(), slippageAdjustedAmounts[Field.INPUT].raw.toString(),
path, path,
account, sending ? recipient : account,
deadlineFromNow deadlineFromNow
] ]
value = ethers.constants.Zero value = ethers.constants.Zero
...@@ -578,7 +510,12 @@ export default function ExchangePage({ sendingInput = false }) { ...@@ -578,7 +510,12 @@ export default function ExchangePage({ sendingInput = false }) {
case SWAP_TYPE.EXACT_ETH_FOR_TOKENS: case SWAP_TYPE.EXACT_ETH_FOR_TOKENS:
estimate = routerContract.estimate.swapExactETHForTokens estimate = routerContract.estimate.swapExactETHForTokens
method = routerContract.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) value = hex(slippageAdjustedAmounts[Field.INPUT].raw)
break break
case SWAP_TYPE.TOKENS_FOR_EXACT_ETH: case SWAP_TYPE.TOKENS_FOR_EXACT_ETH:
...@@ -588,7 +525,7 @@ export default function ExchangePage({ sendingInput = false }) { ...@@ -588,7 +525,7 @@ export default function ExchangePage({ sendingInput = false }) {
slippageAdjustedAmounts[Field.OUTPUT].raw.toString(), slippageAdjustedAmounts[Field.OUTPUT].raw.toString(),
slippageAdjustedAmounts[Field.INPUT].raw.toString(), slippageAdjustedAmounts[Field.INPUT].raw.toString(),
path, path,
account, sending ? recipient : account,
deadlineFromNow deadlineFromNow
] ]
value = ethers.constants.Zero value = ethers.constants.Zero
...@@ -600,7 +537,7 @@ export default function ExchangePage({ sendingInput = false }) { ...@@ -600,7 +537,7 @@ export default function ExchangePage({ sendingInput = false }) {
slippageAdjustedAmounts[Field.INPUT].raw.toString(), slippageAdjustedAmounts[Field.INPUT].raw.toString(),
slippageAdjustedAmounts[Field.OUTPUT].raw.toString(), slippageAdjustedAmounts[Field.OUTPUT].raw.toString(),
path, path,
account, sending ? recipient : account,
deadlineFromNow deadlineFromNow
] ]
value = ethers.constants.Zero value = ethers.constants.Zero
...@@ -608,13 +545,18 @@ export default function ExchangePage({ sendingInput = false }) { ...@@ -608,13 +545,18 @@ export default function ExchangePage({ sendingInput = false }) {
case SWAP_TYPE.ETH_FOR_EXACT_TOKENS: case SWAP_TYPE.ETH_FOR_EXACT_TOKENS:
estimate = routerContract.estimate.swapETHForExactTokens estimate = routerContract.estimate.swapETHForExactTokens
method = routerContract.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) value = hex(slippageAdjustedAmounts[Field.INPUT].raw)
break break
} }
const estimatedGasLimit = await estimate(...args, { value }).catch(e => { const estimatedGasLimit = await estimate(...args, { value }).catch(e => {
console.log('error getting gas limit') console.log(e)
}) })
method(...args, { method(...args, {
...@@ -626,7 +568,7 @@ export default function ExchangePage({ sendingInput = false }) { ...@@ -626,7 +568,7 @@ export default function ExchangePage({ sendingInput = false }) {
addTransaction(response) addTransaction(response)
setPendingConfirmation(false) setPendingConfirmation(false)
}) })
.catch(e => { .catch(() => {
addPopup( addPopup(
<AutoColumn gap="10px"> <AutoColumn gap="10px">
<Text>Transaction Failed: try again.</Text> <Text>Transaction Failed: try again.</Text>
...@@ -638,13 +580,13 @@ export default function ExchangePage({ sendingInput = false }) { ...@@ -638,13 +580,13 @@ export default function ExchangePage({ sendingInput = false }) {
} }
// errors // errors
const [generalError, setGeneralError] = useState('') const [generalError, setGeneralError] = useState<string>('')
const [inputError, setInputError] = useState('') const [inputError, setInputError] = useState<string>('')
const [outputError, setOutputError] = useState('') const [outputError, setOutputError] = useState<string>('')
const [recipientError, setRecipientError] = useState('') const [recipientError, setRecipientError] = useState<string>('')
const [isValid, setIsValid] = useState(false) const [isValid, setIsValid] = useState<boolean>(false)
const ignoreOutput = sending ? !sendingWithSwap : false const ignoreOutput: boolean = sending ? !sendingWithSwap : false
useEffect(() => { useEffect(() => {
// reset errors // reset errors
...@@ -652,11 +594,9 @@ export default function ExchangePage({ sendingInput = false }) { ...@@ -652,11 +594,9 @@ export default function ExchangePage({ sendingInput = false }) {
setInputError(null) setInputError(null)
setOutputError(null) setOutputError(null)
setTradeError(null) setTradeError(null)
setRecipientError(null)
setIsValid(true) setIsValid(true)
if (!isAddress(recipient) && sending) { if (recipientError) {
setRecipientError('Invalid Recipient')
setIsValid(false) setIsValid(false)
} }
...@@ -675,7 +615,7 @@ export default function ExchangePage({ sendingInput = false }) { ...@@ -675,7 +615,7 @@ export default function ExchangePage({ sendingInput = false }) {
exchange && exchange &&
JSBI.greaterThan(parsedAmounts[Field.INPUT].raw, exchange.reserveOf(tokens[Field.INPUT]).raw) JSBI.greaterThan(parsedAmounts[Field.INPUT].raw, exchange.reserveOf(tokens[Field.INPUT]).raw)
) { ) {
setTradeError('Low Liquidity Error') setTradeError('Insufficient Liquidity')
setIsValid(false) setIsValid(false)
} }
...@@ -685,7 +625,7 @@ export default function ExchangePage({ sendingInput = false }) { ...@@ -685,7 +625,7 @@ export default function ExchangePage({ sendingInput = false }) {
exchange && exchange &&
JSBI.greaterThan(parsedAmounts[Field.OUTPUT].raw, exchange.reserveOf(tokens[Field.OUTPUT]).raw) JSBI.greaterThan(parsedAmounts[Field.OUTPUT].raw, exchange.reserveOf(tokens[Field.OUTPUT]).raw)
) { ) {
setTradeError('Low Liquidity Error') setTradeError('Insufficient Liquidity')
setIsValid(false) setIsValid(false)
} }
...@@ -712,6 +652,7 @@ export default function ExchangePage({ sendingInput = false }) { ...@@ -712,6 +652,7 @@ export default function ExchangePage({ sendingInput = false }) {
ignoreOutput, ignoreOutput,
parsedAmounts, parsedAmounts,
recipient, recipient,
recipientError,
sending, sending,
sendingWithSwap, sendingWithSwap,
showInputUnlock, showInputUnlock,
...@@ -721,9 +662,12 @@ export default function ExchangePage({ sendingInput = false }) { ...@@ -721,9 +662,12 @@ export default function ExchangePage({ sendingInput = false }) {
]) ])
// warnings on slippage // warnings on slippage
const warningMedium = slippageFromTrade && parseFloat(slippageFromTrade.toFixed(4)) > ALLOWED_SLIPPAGE_MEDIUM / 100 const warningMedium: boolean =
const warningHigh = slippageFromTrade && parseFloat(slippageFromTrade.toFixed(4)) > ALLOWED_SLIPPAGE_HIGH / 100 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() { function resetModal() {
setPendingConfirmation(true) setPendingConfirmation(true)
setAttemptingTxn(false) setAttemptingTxn(false)
...@@ -736,11 +680,11 @@ export default function ExchangePage({ sendingInput = false }) { ...@@ -736,11 +680,11 @@ export default function ExchangePage({ sendingInput = false }) {
<AutoColumn gap="30px" style={{ marginTop: '40px' }}> <AutoColumn gap="30px" style={{ marginTop: '40px' }}>
<RowBetween> <RowBetween>
<Text fontSize={36} fontWeight={500}> <Text fontSize={36} fontWeight={500}>
{parsedAmounts[Field.INPUT]?.toFixed(8)} {parsedAmounts[Field.INPUT]?.toFixed(8)} {tokens[Field.INPUT]?.symbol}
</Text> </Text>
<TokenLogo address={tokens[Field.INPUT]?.address} size={'30px'} /> <TokenLogo address={tokens[Field.INPUT]?.address} size={'30px'} />
</RowBetween> </RowBetween>
<ArrowDown size={24} color="#888D9B" /> <TYPE.darkGray fontSize={20}>To</TYPE.darkGray>
<TYPE.blue fontSize={36}> <TYPE.blue fontSize={36}>
{recipient?.slice(0, 6)}...{recipient?.slice(36, 42)} {recipient?.slice(0, 6)}...{recipient?.slice(36, 42)}
</TYPE.blue> </TYPE.blue>
...@@ -749,6 +693,27 @@ export default function ExchangePage({ sendingInput = false }) { ...@@ -749,6 +693,27 @@ export default function ExchangePage({ sendingInput = false }) {
} }
if (sending && sendingWithSwap) { 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) { if (!sending) {
...@@ -796,119 +761,29 @@ export default function ExchangePage({ sendingInput = false }) { ...@@ -796,119 +761,29 @@ export default function ExchangePage({ sendingInput = false }) {
</AutoColumn> </AutoColumn>
) )
} }
if (sending && sendingWithSwap) {
}
if (showAdvanced) { if (showAdvanced) {
return ( return (
<AutoColumn gap="20px"> <AdvancedSettings
<Link setIsOpen={setShowAdvanced}
onClick={() => { setDeadline={setDeadline}
setShowAdvanced(false) setAllowedSlippage={setAllowedSlippage}
}} />
>
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>
) )
} }
if (!sending) { if (!sending || (sending && sendingWithSwap)) {
return ( return (
<> <>
<RowBetween> {route && route.midPrice && !emptyReserves && (
<Text color="#565A69" fontWeight={500} fontSize={16}> <RowBetween>
Price <Text color="#565A69" fontWeight={500} fontSize={16}>
</Text> Price
<Text fontWeight={500} fontSize={16}> </Text>
{`1 ${tokens[Field.INPUT]?.symbol} = ${route && route.midPrice && route.midPrice.adjusted.toFixed(8)} ${ <Text fontWeight={500} fontSize={16}>
tokens[Field.OUTPUT]?.symbol {`1 ${tokens[Field.INPUT]?.symbol} = ${route.midPrice.toFixed(6)} ${tokens[Field.OUTPUT]?.symbol}`}
}`} </Text>
</Text> </RowBetween>
</RowBetween> )}
<RowBetween> <RowBetween>
<Text color="#565A69" fontWeight={500} fontSize={16}> <Text color="#565A69" fontWeight={500} fontSize={16}>
Slippage <Link onClick={() => setShowAdvanced(true)}>(edit limits)</Link> Slippage <Link onClick={() => setShowAdvanced(true)}>(edit limits)</Link>
...@@ -919,7 +794,7 @@ export default function ExchangePage({ sendingInput = false }) { ...@@ -919,7 +794,7 @@ export default function ExchangePage({ sendingInput = false }) {
</RowBetween> </RowBetween>
<ButtonError onClick={onSwap} error={!!warningHigh} style={{ margin: '10px 0' }}> <ButtonError onClick={onSwap} error={!!warningHigh} style={{ margin: '10px 0' }}>
<Text fontSize={20} fontWeight={500}> <Text fontSize={20} fontWeight={500}>
{warningHigh ? 'Swap Anyway' : 'Swap'} {warningHigh ? (sending ? 'Send Anyway' : 'Swap Anyway') : sending ? 'Confirm Send' : 'Confirm Swap'}
</Text> </Text>
</ButtonError> </ButtonError>
<AutoColumn justify="center" gap="20px"> <AutoColumn justify="center" gap="20px">
...@@ -933,7 +808,7 @@ export default function ExchangePage({ sendingInput = false }) { ...@@ -933,7 +808,7 @@ export default function ExchangePage({ sendingInput = false }) {
setShowAdvanced(true) setShowAdvanced(true)
}} }}
> >
Advanced Options Advanced
</Link> </Link>
</AutoColumn> </AutoColumn>
</> </>
...@@ -941,27 +816,48 @@ export default function ExchangePage({ sendingInput = false }) { ...@@ -941,27 +816,48 @@ export default function ExchangePage({ sendingInput = false }) {
} }
} }
const pendingText = sending // text to show while loading
? `Sending ${parsedAmounts[Field.INPUT]?.toSignificant(6)} ${tokens[Field.INPUT]?.symbol} to ${recipient}` 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[ : ` Swapped ${parsedAmounts[Field.INPUT]?.toSignificant(6)} ${tokens[Field.INPUT]?.symbol} for ${parsedAmounts[
Field.OUTPUT Field.OUTPUT
]?.toSignificant(6)} ${tokens[Field.OUTPUT]?.symbol}` ]?.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 ( return (
<Wrapper> <Wrapper>
<ConfirmationModal <ConfirmationModal
isOpen={showConfirm} isOpen={showConfirm}
title={sendingWithSwap ? 'Confirm swap and send' : sending ? 'Confirm Send' : 'Confirm Swap'}
onDismiss={() => { onDismiss={() => {
resetModal() resetModal()
setShowConfirm(false) setShowConfirm(false)
}} }}
attemptingTxn={attemptingTxn} attemptingTxn={attemptingTxn}
pendingConfirmation={pendingConfirmation} pendingConfirmation={pendingConfirmation}
hash={txHash ? txHash : ''} hash={txHash}
topContent={modalHeader} topContent={modalHeader}
bottomContent={modalBottom} bottomContent={modalBottom}
pendingText={pendingText} pendingText={pendingText}
title={sendingWithSwap ? 'Confirm swap and send' : sending ? 'Confirm Send' : 'Confirm Swap'}
/> />
{sending && !sendingWithSwap && ( {sending && !sendingWithSwap && (
...@@ -987,11 +883,7 @@ export default function ExchangePage({ sendingInput = false }) { ...@@ -987,11 +883,7 @@ export default function ExchangePage({ sendingInput = false }) {
}} }}
atMax={atMaxAmountInput} atMax={atMaxAmountInput}
token={tokens[Field.INPUT]} token={tokens[Field.INPUT]}
onTokenSelection={address => onTokenSelection(Field.INPUT, address)} onTokenSelection={address => _onTokenSelect(address)}
onTokenSelectSendWithSwap={address => {
onTokenSelection(Field.OUTPUT, address)
setSendingWithSwap(true)
}}
title={'Input'} title={'Input'}
error={inputError} error={inputError}
exchange={exchange} exchange={exchange}
...@@ -1010,17 +902,17 @@ export default function ExchangePage({ sendingInput = false }) { ...@@ -1010,17 +902,17 @@ export default function ExchangePage({ sendingInput = false }) {
<CurrencyInputPanel <CurrencyInputPanel
field={Field.INPUT} field={Field.INPUT}
value={formattedAmounts[Field.INPUT]} value={formattedAmounts[Field.INPUT]}
onUserInput={onUserInput}
onMax={() => {
maxAmountInput && onMaxInput(maxAmountInput.toExact())
}}
atMax={atMaxAmountInput} atMax={atMaxAmountInput}
token={tokens[Field.INPUT]} token={tokens[Field.INPUT]}
onTokenSelection={address => onTokenSelection(Field.INPUT, address)}
title={'Input'} title={'Input'}
error={inputError} error={inputError}
exchange={exchange} exchange={exchange}
showUnlock={showInputUnlock} showUnlock={showInputUnlock}
onUserInput={onUserInput}
onMax={() => {
maxAmountInput && onMaxInput(maxAmountInput.toExact())
}}
onTokenSelection={address => onTokenSelection(Field.INPUT, address)}
/> />
<ColumnCenter> <ColumnCenter>
<ArrowWrapper onClick={onSwapTokens}> <ArrowWrapper onClick={onSwapTokens}>
...@@ -1043,18 +935,20 @@ export default function ExchangePage({ sendingInput = false }) { ...@@ -1043,18 +935,20 @@ export default function ExchangePage({ sendingInput = false }) {
exchange={exchange} exchange={exchange}
showUnlock={showOutputUnlock} showUnlock={showOutputUnlock}
/> />
<RowBetween> {!emptyReserves && ( // hide price if new exchange
<Text fontWeight={500} color="#565A69"> <RowBetween>
Price <Text fontWeight={500} color="#565A69">
</Text> Price
<Text fontWeight={500} color="#565A69"> </Text>
{exchange <Text fontWeight={500} color="#565A69">
? `1 ${tokens[Field.INPUT].symbol} = ${route?.midPrice.toSignificant(6)} ${ {exchange
tokens[Field.OUTPUT].symbol ? `1 ${tokens[Field.INPUT].symbol} = ${route?.midPrice.toSignificant(6)} ${
}` tokens[Field.OUTPUT].symbol
: '-'} }`
</Text> : '-'}
</RowBetween> </Text>
</RowBetween>
)}
{warningMedium && ( {warningMedium && (
<RowBetween> <RowBetween>
<Text fontWeight={500} color="#565A69"> <Text fontWeight={500} color="#565A69">
...@@ -1070,39 +964,54 @@ export default function ExchangePage({ sendingInput = false }) { ...@@ -1070,39 +964,54 @@ export default function ExchangePage({ sendingInput = false }) {
{sending && ( {sending && (
<AutoColumn gap="10px"> <AutoColumn gap="10px">
<LightCard borderRadius={'20px'}> <AddressInputPanel
<RowBetween> title={''}
<StyledInput placeholder="Recipient Address" onChange={e => setRecipient(e.target.value)} /> onChange={_onRecipient}
<QRWrapper> onError={error => {
<img src={QR} alt="" /> if (error) {
</QRWrapper> setRecipientError('Inavlid Recipient')
</RowBetween> } else {
</LightCard> setRecipientError(null)
}
}}
/>
</AutoColumn> </AutoColumn>
)} )}
<ButtonError
onClick={() => { {emptyReserves ? (
setShowConfirm(true) <RowBetween style={{ margin: '10px 0' }}>
}} <TYPE.main>No exchange for this pair.</TYPE.main>
disabled={!isValid} <TYPE.blue> Create one now</TYPE.blue>
error={!!warningHigh} </RowBetween>
> ) : (
<Text fontSize={20} fontWeight={500}> <ButtonError
{generalError onClick={() => {
? generalError setShowConfirm(true)
: inputError }}
? inputError disabled={!isValid}
: outputError error={!!warningHigh}
? outputError >
: recipientError <Text fontSize={20} fontWeight={500}>
? recipientError {generalError
: tradeError ? generalError
? tradeError : inputError
: warningHigh ? inputError
? 'Swap Anyway' : outputError
: 'Swap'} ? outputError
</Text> : recipientError
</ButtonError> ? recipientError
: tradeError
? tradeError
: warningHigh
? sendingWithSwap
? 'Send Anyway'
: 'Swap Anyway'
: sending
? 'Send'
: 'Swap'}
</Text>
</ButtonError>
)}
</AutoColumn> </AutoColumn>
{warningHigh && ( {warningHigh && (
......
import React from 'react' import React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import Row from '../Row'
import Menu from '../Menu' import Menu from '../Menu'
import Logo from '../../assets/svg/logo.svg' import Logo from '../../assets/svg/logo.svg'
import Row from '../Row' import Card from '../Card'
import Web3Status from '../Web3Status' import Web3Status from '../Web3Status'
import { CloseIcon } from '../../theme/components' import { X } from 'react-feather'
import { Link } from '../../theme' import { Link } from '../../theme'
import { Text } from 'rebass' import { Text } from 'rebass'
import Card from '../Card'
import { X } from 'react-feather'
import { WETH } from '@uniswap/sdk' import { WETH } from '@uniswap/sdk'
import { isMobile } from 'react-device-detect' import { isMobile } from 'react-device-detect'
...@@ -46,9 +45,7 @@ const Title = styled.div` ...@@ -46,9 +45,7 @@ const Title = styled.div`
const TitleText = styled.div` const TitleText = styled.div`
font-size: 24px; font-size: 24px;
font-weight: 700; font-weight: 700;
background: linear-gradient(119.64deg, #fb1868 -5.55%, #ff00f3 154.46%); color: ${({ theme }) => theme.black};
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-left: 12px; margin-left: 12px;
` `
...@@ -58,22 +55,21 @@ const AccountElement = styled.div` ...@@ -58,22 +55,21 @@ const AccountElement = styled.div`
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
background-color: ${({ theme }) => theme.outlineGrey}; background-color: ${({ theme, active }) => (!active ? theme.white : theme.outlineGrey)};
border: 1px solid ${({ theme }) => theme.outlineGrey}; border: 1px solid ${({ theme }) => theme.outlineGrey};
border-radius: 8px; border-radius: 8px;
padding-left: 8px; padding-left: ${({ active }) => (active ? '8px' : 0)};
:focus { :focus {
border: 1px solid blue; border: 1px solid blue;
} }
/* width: 100%; */
` `
const FixedPopupColumn = styled(AutoColumn)` const FixedPopupColumn = styled(AutoColumn)`
position: absolute; position: absolute;
top: 80px; top: 80px;
right: 20px right: 20px
width: 340px; width: 380px;
` `
const StyledClose = styled(X)` const StyledClose = styled(X)`
...@@ -86,12 +82,10 @@ const StyledClose = styled(X)` ...@@ -86,12 +82,10 @@ const StyledClose = styled(X)`
` `
const Popup = styled(Card)` 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; z-index: 9999;
border-radius: 8px; border-radius: 8px;
padding: 1rem; padding: 1rem;
background: ${theme => theme.white}; background-color: white;
` `
export default function Header() { export default function Header() {
...@@ -115,8 +109,8 @@ export default function Header() { ...@@ -115,8 +109,8 @@ export default function Header() {
</Title> </Title>
</HeaderElement> </HeaderElement>
<HeaderElement> <HeaderElement>
<AccountElement> <AccountElement active={!!account}>
{!isMobile ? ( {!isMobile && account ? (
<Row style={{ marginRight: '-1.25rem', paddingRight: '1.75rem' }}> <Row style={{ marginRight: '-1.25rem', paddingRight: '1.75rem' }}>
<Text fontWeight={500}> {userEthBalance && userEthBalance?.toFixed(4) + ' ETH'}</Text> <Text fontWeight={500}> {userEthBalance && userEthBalance?.toFixed(4) + ' ETH'}</Text>
</Row> </Row>
......
import React, { useState } from 'react' import React, { useState } from 'react'
import { JSBI } from '@uniswap/sdk'
import { withRouter } from 'react-router-dom' import { withRouter } from 'react-router-dom'
import { TokenAmount, JSBI, Token, Exchange } from '@uniswap/sdk'
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 Row from '../Row' import Row from '../Row'
import TokenLogo from '../TokenLogo' import TokenLogo from '../TokenLogo'
import SearchModal from '../SearchModal' import SearchModal from '../SearchModal'
import PositionCard from '../PositionCard' import PositionCard from '../PositionCard'
import DoubleTokenLogo from '../DoubleLogo'
import { Link } from '../../theme' import { Link } from '../../theme'
import { Text } from 'rebass' import { Text } from 'rebass'
import { Plus } from 'react-feather' import { Plus } from 'react-feather'
import { LightCard } from '../Card' import { LightCard } from '../Card'
import Column, { AutoColumn, ColumnCenter } from '../Column' import { AutoColumn, ColumnCenter } from '../Column'
import { ButtonPrimary, ButtonDropwdown, ButtonDropwdownLight } from '../Button' 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 }) { function PoolFinder({ history }) {
const Fields = { const Fields = {
...@@ -27,27 +27,25 @@ function PoolFinder({ history }) { ...@@ -27,27 +27,25 @@ function PoolFinder({ history }) {
} }
const { account } = useWeb3React() const { account } = useWeb3React()
const [showSearch, setShowSearch] = useState(false) const [showSearch, setShowSearch] = useState<boolean>(false)
const [activeField, setActiveField] = useState(Fields.TOKEN0) const [activeField, setActiveField] = useState<number>(Fields.TOKEN0)
const [, addPopup] = usePopups() const [, addPopup] = usePopups()
const [token0Address, setToken0Address] = useState() const [token0Address, setToken0Address] = useState<string>()
const [token1Address, setToken1Address] = useState() const [token1Address, setToken1Address] = useState<string>()
const token0 = useToken(token0Address)
const token1 = useToken(token1Address)
const exchange = useExchange(token0, token1)
const position = useAddressBalance(account, exchange?.liquidityToken) const token0: Token = useToken(token0Address)
const token1: Token = useToken(token1Address)
const newExchange = exchange && JSBI.equal(exchange.reserve0.raw, JSBI.BigInt(0)) const exchange: Exchange = useExchange(token0, token1)
const position: TokenAmount = useAddressBalance(account, exchange?.liquidityToken)
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() { function endSearch() {
history.goBack() history.goBack() // return to previous page
addPopup( addPopup(
<Row> <Row>
<DoubleTokenLogo a0={token0Address || ''} a1={token1Address || ''} margin={true} /> <DoubleTokenLogo a0={token0Address || ''} a1={token1Address || ''} margin={true} />
......
...@@ -2,12 +2,11 @@ import React, { useState, useRef, useMemo, useEffect } from 'react' ...@@ -2,12 +2,11 @@ import React, { useState, useRef, useMemo, useEffect } from 'react'
import '@reach/tooltip/styles.css' import '@reach/tooltip/styles.css'
import styled from 'styled-components' import styled from 'styled-components'
import escapeStringRegex from 'escape-string-regexp' import escapeStringRegex from 'escape-string-regexp'
import { JSBI } from '@uniswap/sdk'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { ethers } from 'ethers' import { ethers } from 'ethers'
import { isMobile } from 'react-device-detect' import { isMobile } from 'react-device-detect'
import { withRouter } from 'react-router-dom' import { withRouter } from 'react-router-dom'
import { JSBI } from '@uniswap/sdk'
import { Link as StyledLink } from '../../theme/components' import { Link as StyledLink } from '../../theme/components'
import Modal from '../Modal' import Modal from '../Modal'
...@@ -107,11 +106,17 @@ const PaddedItem = styled(RowBetween)` ...@@ -107,11 +106,17 @@ const PaddedItem = styled(RowBetween)`
const MenuItem = styled(PaddedItem)` const MenuItem = styled(PaddedItem)`
cursor: pointer; cursor: pointer;
:hover { :hover {
background-color: ${({ theme }) => theme.tokenRowHover}; background-color: ${({ theme }) => theme.tokenRowHover};
} }
` `
// filters on results
const FILTERS = {
VOLUME: 'VOLUME',
LIQUIDITY: 'LIQUIDITY',
BALANCES: 'BALANCES'
}
function SearchModal({ function SearchModal({
history, history,
isOpen, isOpen,
...@@ -120,28 +125,26 @@ function SearchModal({ ...@@ -120,28 +125,26 @@ function SearchModal({
urlAddedTokens, urlAddedTokens,
filterType, filterType,
hiddenToken, hiddenToken,
showSendWithSwap, showSendWithSwap
onTokenSelectSendWithSwap
}) { }) {
const { t } = useTranslation() const { t } = useTranslation()
const { account, chainId } = useWeb3React() const { account, chainId } = useWeb3React()
const allTokens = useAllTokens()
const allExchanges = useAllExchanges()
const allBalances = useAllBalances()
const [searchQuery, setSearchQuery] = useState('') const [searchQuery, setSearchQuery] = useState('')
const [sortDirection, setSortDirection] = useState(true)
// get all exchanges
const allExchanges = useAllExchanges()
const token = useToken(searchQuery) const token = useToken(searchQuery)
const tokenAddress = token && token.address const tokenAddress = token && token.address
// get all tokens // amount of tokens to display at once
const allTokens = useAllTokens() 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(() => { const tokenList = useMemo(() => {
return Object.keys(allTokens) return Object.keys(allTokens)
...@@ -149,12 +152,10 @@ function SearchModal({ ...@@ -149,12 +152,10 @@ function SearchModal({
if (allTokens[a].symbol && allTokens[b].symbol) { if (allTokens[a].symbol && allTokens[b].symbol) {
const aSymbol = allTokens[a].symbol.toLowerCase() const aSymbol = allTokens[a].symbol.toLowerCase()
const bSymbol = allTokens[b].symbol.toLowerCase() const bSymbol = allTokens[b].symbol.toLowerCase()
// pin ETH to top // pin ETH to top
if (aSymbol === 'ETH'.toLowerCase() || bSymbol === 'ETH'.toLowerCase()) { if (aSymbol === 'ETH'.toLowerCase() || bSymbol === 'ETH'.toLowerCase()) {
return aSymbol === bSymbol ? 0 : aSymbol === 'ETH'.toLowerCase() ? -1 : 1 return aSymbol === bSymbol ? 0 : aSymbol === 'ETH'.toLowerCase() ? -1 : 1
} }
// sort by balance // sort by balance
const balanceA = allBalances?.[account]?.[a] const balanceA = allBalances?.[account]?.[a]
const balanceB = allBalances?.[account]?.[b] const balanceB = allBalances?.[account]?.[b]
...@@ -162,16 +163,12 @@ function SearchModal({ ...@@ -162,16 +163,12 @@ function SearchModal({
if (balanceA && !balanceB) { if (balanceA && !balanceB) {
return sortDirection return sortDirection
} }
if (!balanceA && balanceB) { if (!balanceA && balanceB) {
return sortDirection * -1 return sortDirection * -1
} }
if (balanceA && balanceB) { if (balanceA && balanceB) {
return sortDirection * parseFloat(balanceA.toExact()) > parseFloat(balanceB.toExact()) ? -1 : 1 return sortDirection * parseFloat(balanceA.toExact()) > parseFloat(balanceB.toExact()) ? -1 : 1
} }
// sort alphabetically
return aSymbol < bSymbol ? -1 : aSymbol > bSymbol ? 1 : 0 return aSymbol < bSymbol ? -1 : aSymbol > bSymbol ? 1 : 0
} else { } else {
return 0 return 0
...@@ -181,16 +178,11 @@ function SearchModal({ ...@@ -181,16 +178,11 @@ function SearchModal({
if (k === hiddenToken) { if (k === hiddenToken) {
return false return false
} }
let balance
// only update if we have data
balance = allBalances?.[account]?.[k]
return { return {
name: allTokens[k].name, name: allTokens[k].name,
symbol: allTokens[k].symbol, symbol: allTokens[k].symbol,
address: k, address: k,
balance: balance balance: allBalances?.[account]?.[k]
} }
}) })
}, [allTokens, allBalances, account, sortDirection, hiddenToken]) }, [allTokens, allBalances, account, sortDirection, hiddenToken])
...@@ -198,10 +190,7 @@ function SearchModal({ ...@@ -198,10 +190,7 @@ function SearchModal({
const filteredTokenList = useMemo(() => { const filteredTokenList = useMemo(() => {
return tokenList.filter(tokenEntry => { return tokenList.filter(tokenEntry => {
const inputIsAddress = searchQuery.slice(0, 2) === '0x' const inputIsAddress = searchQuery.slice(0, 2) === '0x'
// check the regex for each field
const regexMatches = Object.keys(tokenEntry).map(tokenEntryKey => { const regexMatches = Object.keys(tokenEntry).map(tokenEntryKey => {
// if address field only search if input starts with 0x
if (tokenEntryKey === 'address') { if (tokenEntryKey === 'address') {
return ( return (
inputIsAddress && inputIsAddress &&
...@@ -218,21 +207,14 @@ function SearchModal({ ...@@ -218,21 +207,14 @@ function SearchModal({
}) })
}, [tokenList, searchQuery]) }, [tokenList, searchQuery])
function _onTokenSelect(address, sendWithSwap = false) { function _onTokenSelect(address) {
if (sendWithSwap) { setSearchQuery('')
setSearchQuery('') onTokenSelect(address)
onTokenSelectSendWithSwap(address) onDismiss()
onDismiss()
} else {
setSearchQuery('')
onTokenSelect(address)
onDismiss()
}
} }
// manage focus on modal show // manage focus on modal show
const inputRef = useRef() const inputRef = useRef()
function onInput(event) { function onInput(event) {
const input = event.target.value const input = event.target.value
const checksummedInput = isAddress(input) const checksummedInput = isAddress(input)
...@@ -244,34 +226,39 @@ function SearchModal({ ...@@ -244,34 +226,39 @@ function SearchModal({
onDismiss() 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 // sort tokens
const escapeStringRegexp = string => string const escapeStringRegexp = string => string
// sort pairs const sortedPairList = useMemo(() => {
const filteredPairList = useMemo(() => { return Object.keys(allExchanges).sort((a, b) => {
// check if the search is an address // 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' const isAddress = searchQuery.slice(0, 2) === '0x'
return Object.keys(allExchanges).filter(exchangeAddress => { return sortedPairList.filter(exchangeAddress => {
const exchange = allExchanges[exchangeAddress] const exchange = allExchanges[exchangeAddress]
if (searchQuery === '') { if (searchQuery === '') {
return true return true
} }
const token0 = allTokens[exchange.token0] const token0 = allTokens[exchange.token0]
const token1 = allTokens[exchange.token1] const token1 = allTokens[exchange.token1]
const regexMatches = Object.keys(token0).map(field => { const regexMatches = Object.keys(token0).map(field => {
if ( if (
(field === 'address' && isAddress) || (field === 'address' && isAddress) ||
...@@ -288,7 +275,7 @@ function SearchModal({ ...@@ -288,7 +275,7 @@ function SearchModal({
return regexMatches.some(m => m) return regexMatches.some(m => m)
}) })
}, [allExchanges, allTokens, searchQuery]) }, [account, allBalances, allExchanges, allTokens, searchQuery, sortDirection])
// update the amount shown as filtered list changes // update the amount shown as filtered list changes
useEffect(() => { useEffect(() => {
...@@ -312,9 +299,7 @@ function SearchModal({ ...@@ -312,9 +299,7 @@ function SearchModal({
filteredPairList.map((exchangeAddress, i) => { filteredPairList.map((exchangeAddress, i) => {
const token0 = allTokens[allExchanges[exchangeAddress].token0] const token0 = allTokens[allExchanges[exchangeAddress].token0]
const token1 = allTokens[allExchanges[exchangeAddress].token1] const token1 = allTokens[allExchanges[exchangeAddress].token1]
const balance = allBalances?.[account]?.[exchangeAddress]?.toSignificant(6) const balance = allBalances?.[account]?.[exchangeAddress]?.toSignificant(6)
return ( return (
<MenuItem <MenuItem
key={i} key={i}
......
...@@ -2,23 +2,23 @@ import React from 'react' ...@@ -2,23 +2,23 @@ import React from 'react'
import Slider from '@material-ui/core/Slider' import Slider from '@material-ui/core/Slider'
import { withStyles } from '@material-ui/core/styles' import { withStyles } from '@material-ui/core/styles'
const marks = [ // const marks = [
{ // {
value: 0 // value: 0
}, // },
{ // {
value: 25 // value: 25
}, // },
{ // {
value: 50 // value: 50
}, // },
{ // {
value: 75 // value: 75
}, // },
{ // {
value: 100 // value: 100
} // }
] // ]
const StyledSlider = withStyles({ const StyledSlider = withStyles({
root: { root: {
......
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 }) { ...@@ -39,7 +39,6 @@ export default function Web3ReactManager({ children }) {
const triedEager = useEagerConnect() const triedEager = useEagerConnect()
// after eagerly trying injected, if the network connect ever isn't active or in an error state, activate itd // 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(() => { useEffect(() => {
if (triedEager && !networkActive && !networkError && !active) { if (triedEager && !networkActive && !networkError && !active) {
activateNetwork(network) activateNetwork(network)
......
[ [
{
"inputs": [
{
"internalType": "address",
"name": "_WETH",
"type": "address"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"payable": true,
"stateMutability": "payable",
"type": "fallback"
},
{ {
"constant": true, "constant": true,
"inputs": [], "inputs": [],
...@@ -153,32 +136,6 @@ ...@@ -153,32 +136,6 @@
"stateMutability": "payable", "stateMutability": "payable",
"type": "function" "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, "constant": true,
"inputs": [], "inputs": [],
...@@ -191,7 +148,7 @@ ...@@ -191,7 +148,7 @@
} }
], ],
"payable": false, "payable": false,
"stateMutability": "view", "stateMutability": "pure",
"type": "function" "type": "function"
}, },
{ {
...@@ -308,52 +265,6 @@ ...@@ -308,52 +265,6 @@
"stateMutability": "view", "stateMutability": "view",
"type": "function" "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, "constant": true,
"inputs": [ "inputs": [
...@@ -525,6 +436,11 @@ ...@@ -525,6 +436,11 @@
"name": "deadline", "name": "deadline",
"type": "uint256" "type": "uint256"
}, },
{
"internalType": "bool",
"name": "approveMax",
"type": "bool"
},
{ {
"internalType": "uint8", "internalType": "uint8",
"name": "v", "name": "v",
...@@ -596,6 +512,11 @@ ...@@ -596,6 +512,11 @@
"name": "deadline", "name": "deadline",
"type": "uint256" "type": "uint256"
}, },
{
"internalType": "bool",
"name": "approveMax",
"type": "bool"
},
{ {
"internalType": "uint8", "internalType": "uint8",
"name": "v", "name": "v",
...@@ -629,37 +550,6 @@ ...@@ -629,37 +550,6 @@
"stateMutability": "nonpayable", "stateMutability": "nonpayable",
"type": "function" "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, "constant": false,
"inputs": [ "inputs": [
...@@ -895,35 +785,5 @@ ...@@ -895,35 +785,5 @@
"payable": false, "payable": false,
"stateMutability": "nonpayable", "stateMutability": "nonpayable",
"type": "function" "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"
} }
] ]
import { injected, walletconnect, walletlink, fortmatic, portis } from '../connectors' import { injected, walletconnect, walletlink, fortmatic, portis } from '../connectors'
export const FACTORY_ADDRESSES = { export const FACTORY_ADDRESSES = {
1: '0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95', 1: '',
3: '0x9c83dCE8CA20E9aAF9D3efc003b2ea62aBC08351', 3: '',
4: '0xf5D915570BC477f9B8D6C0E980aA81757A3AaC36', 4: '0xe2f197885abe8ec7c866cFf76605FD06d4576218',
42: '0xD3E51Ef092B2845f10401a0159B2B96e8B6c3D30' 42: ''
} }
export const ROUTER_ADDRESSES = { export const ROUTER_ADDRESSES = {
1: '', 1: '',
3: '', 3: '',
4: '0xd9210Ff5A0780E083BB40e30d005d93a2DcFA4EF', 4: '0xcDbE04934d89e97a24BCc07c3562DC8CF17d8167',
42: '' 42: ''
} }
...@@ -100,12 +100,4 @@ export const SUPPORTED_WALLETS = ...@@ -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' export const NetworkContextName = 'NETWORK'
...@@ -162,6 +162,7 @@ export function usePopups() { ...@@ -162,6 +162,7 @@ export function usePopups() {
if (key === item.key) { if (key === item.key) {
item.show = false item.show = false
} }
return true
}) })
setPopups(currentPopups) setPopups(currentPopups)
} }
......
...@@ -427,6 +427,7 @@ export function useAllBalances(): Array<TokenAmount> { ...@@ -427,6 +427,7 @@ export function useAllBalances(): Array<TokenAmount> {
} }
} }
} }
return true
}) })
}) })
return newBalances return newBalances
...@@ -443,6 +444,9 @@ export function useAddressBalance(address: string, token: Token): TokenAmount | ...@@ -443,6 +444,9 @@ export function useAddressBalance(address: string, token: Token): TokenAmount |
const { chainId } = useWeb3React() const { chainId } = useWeb3React()
const [state, { startListening, stopListening }] = useBalancesContext() 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 * @todo
* when catching for token, causes infinite rerender * when catching for token, causes infinite rerender
...@@ -457,9 +461,6 @@ export function useAddressBalance(address: string, token: Token): TokenAmount | ...@@ -457,9 +461,6 @@ export function useAddressBalance(address: string, token: Token): TokenAmount |
} }
}, [chainId, address, startListening, stopListening]) }, [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]) return useMemo(() => formattedValue, [formattedValue])
} }
...@@ -476,6 +477,7 @@ export function useAccountLPBalances(account: string) { ...@@ -476,6 +477,7 @@ export function useAccountLPBalances(account: string) {
stopListening(chainId, account, exchangeAddress) stopListening(chainId, account, exchangeAddress)
} }
} }
return true
}) })
}, [account, allExchanges, chainId, startListening, stopListening]) }, [account, allExchanges, chainId, startListening, stopListening])
} }
...@@ -11,7 +11,7 @@ const UPDATE = 'UPDATE' ...@@ -11,7 +11,7 @@ const UPDATE = 'UPDATE'
const ALL_EXCHANGES: [Token, Token][] = [ const ALL_EXCHANGES: [Token, Token][] = [
[ [
INITIAL_TOKENS_CONTEXT[ChainId.RINKEBY][WETH[ChainId.RINKEBY].address], 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'], INITIAL_TOKENS_CONTEXT[ChainId.RINKEBY]['0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735'],
......
...@@ -8,7 +8,7 @@ const UPDATE = 'UPDATE' ...@@ -8,7 +8,7 @@ const UPDATE = 'UPDATE'
export const ALL_TOKENS = [ export const ALL_TOKENS = [
WETH[ChainId.RINKEBY], WETH[ChainId.RINKEBY],
new Token(ChainId.RINKEBY, '0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735', 18, 'DAI', 'Dai Stablecoin'), 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! // only meant to be used in exchanges.ts!
...@@ -112,6 +112,11 @@ export function useAllTokens(): string[] { ...@@ -112,6 +112,11 @@ export function useAllTokens(): string[] {
const [state] = useTokensContext() const [state] = useTokensContext()
return useMemo(() => { 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] || {} return state?.[chainId] || {}
}, [state, chainId]) }, [state, chainId])
} }
import { useState, useMemo, useCallback, useEffect, useRef } from 'react' import { useState, useMemo, useCallback, useEffect, useRef } from 'react'
import { useWeb3React as useWeb3ReactCore } from '@web3-react/core' import { useWeb3React as useWeb3ReactCore } from '@web3-react/core'
import copy from 'copy-to-clipboard'
import { isMobile } from 'react-device-detect' import { isMobile } from 'react-device-detect'
import copy from 'copy-to-clipboard'
import { NetworkContextName } from '../constants'
import ERC20_ABI from '../constants/abis/erc20' import ERC20_ABI from '../constants/abis/erc20'
import { getContract, getFactoryContract, getExchangeContract, isAddress } from '../utils'
import { injected } from '../connectors' import { injected } from '../connectors'
import { NetworkContextName } from '../constants'
import { getContract, getFactoryContract, getExchangeContract, isAddress } from '../utils'
export function useWeb3React() { export function useWeb3React() {
const context = useWeb3ReactCore() const context = useWeb3ReactCore()
const contextNetwork = useWeb3ReactCore(NetworkContextName) const contextNetwork = useWeb3ReactCore(NetworkContextName)
return context.active ? context : contextNetwork return context.active ? context : contextNetwork
} }
export function useEagerConnect() { export function useEagerConnect() {
const { activate, active } = useWeb3ReactCore() // specifically using useWeb3ReactCore because of what this hook does const { activate, active } = useWeb3ReactCore() // specifically using useWeb3ReactCore because of what this hook does
const [tried, setTried] = useState(false) const [tried, setTried] = useState(false)
useEffect(() => { useEffect(() => {
...@@ -265,6 +263,5 @@ export function usePrevious(value) { ...@@ -265,6 +263,5 @@ export function usePrevious(value) {
export function useToggle(initialState = false) { export function useToggle(initialState = false) {
const [state, setState] = useState(initialState) const [state, setState] = useState(initialState)
const toggle = useCallback(() => setState(state => !state), []) const toggle = useCallback(() => setState(state => !state), [])
return [state, toggle] return [state, toggle]
} }
...@@ -2,10 +2,9 @@ import React, { Suspense, lazy } from 'react' ...@@ -2,10 +2,9 @@ import React, { Suspense, lazy } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom' import { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom'
import Web3ReactManager from '../components/Web3ReactManager'
import Header from '../components/Header' import Header from '../components/Header'
import NavigationTabs from '../components/NavigationTabs' import NavigationTabs from '../components/NavigationTabs'
import Web3ReactManager from '../components/Web3ReactManager'
import { isAddress, getAllQueryParams } from '../utils' import { isAddress, getAllQueryParams } from '../utils'
const Swap = lazy(() => import('./Swap')) const Swap = lazy(() => import('./Swap'))
...@@ -65,6 +64,7 @@ export default function App() { ...@@ -65,6 +64,7 @@ export default function App() {
{/* this Suspense is for route code-splitting */} {/* this Suspense is for route code-splitting */}
<Suspense fallback={null}> <Suspense fallback={null}>
<Switch> <Switch>
<Route exact strict path="/" render={() => <Redirect to={{ pathname: '/swap' }} />} />
<Route exact strict path="/find" component={() => <Find params={params} />} /> <Route exact strict path="/find" component={() => <Find params={params} />} />
<Route exact strict path="/swap" component={() => <Swap params={params} />} /> <Route exact strict path="/swap" component={() => <Swap params={params} />} />
<Route <Route
......
import React, { useState, useEffect } from 'react' import React from 'react'
import styled from 'styled-components'
import { darken } from 'polished'
import { TokenAmount, JSBI } from '@uniswap/sdk'
import QR from '../../assets/svg/QR.svg'
import TokenLogo from '../../components/TokenLogo'
import SearchModal from '../../components/SearchModal'
import ExchangePage from '../../components/ExchangePage' 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() { export default function Send() {
const { account } = useWeb3React() return <ExchangePage sendingInput={true} />
// 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=""
/>
</>
)
} }
...@@ -2,10 +2,10 @@ import React, { useReducer, useState, useCallback, useEffect } from 'react' ...@@ -2,10 +2,10 @@ import React, { useReducer, useState, useCallback, useEffect } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { ethers } from 'ethers' import { ethers } from 'ethers'
import { parseUnits, parseEther } from '@ethersproject/units' 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 TokenLogo from '../../components/TokenLogo'
import DoubleLogo from '../../components/DoubleLogo'
import SearchModal from '../../components/SearchModal' import SearchModal from '../../components/SearchModal'
import PositionCard from '../../components/PositionCard' import PositionCard from '../../components/PositionCard'
import ConfirmationModal from '../../components/ConfirmationModal' import ConfirmationModal from '../../components/ConfirmationModal'
...@@ -17,8 +17,8 @@ import { AutoColumn, ColumnCenter } from '../../components/Column' ...@@ -17,8 +17,8 @@ import { AutoColumn, ColumnCenter } from '../../components/Column'
import Row, { RowBetween, RowFlat, RowFixed } from '../../components/Row' import Row, { RowBetween, RowFlat, RowFixed } from '../../components/Row'
import { useToken } from '../../contexts/Tokens' import { useToken } from '../../contexts/Tokens'
import { useWeb3React } from '../../hooks'
import { usePopups } from '../../contexts/Application' import { usePopups } from '../../contexts/Application'
import { useWeb3React } from '../../hooks'
import { useAddressBalance } from '../../contexts/Balances' import { useAddressBalance } from '../../contexts/Balances'
import { useAddressAllowance } from '../../contexts/Allowances' import { useAddressAllowance } from '../../contexts/Allowances'
import { useTransactionAdder } from '../../contexts/Transactions' import { useTransactionAdder } from '../../contexts/Transactions'
...@@ -27,6 +27,7 @@ import { useExchange, useTotalSupply } from '../../contexts/Exchanges' ...@@ -27,6 +27,7 @@ import { useExchange, useTotalSupply } from '../../contexts/Exchanges'
import { BigNumber } from 'ethers/utils' import { BigNumber } from 'ethers/utils'
import { ROUTER_ADDRESSES } from '../../constants' import { ROUTER_ADDRESSES } from '../../constants'
import { getRouterContract, calculateGasMargin } from '../../utils' import { getRouterContract, calculateGasMargin } from '../../utils'
import { TYPE } from '../../theme'
// denominated in bips // denominated in bips
const ALLOWED_SLIPPAGE = 200 const ALLOWED_SLIPPAGE = 200
...@@ -134,12 +135,9 @@ function reducer( ...@@ -134,12 +135,9 @@ function reducer(
} }
} }
/**
* @todo should we ever not have prepopulated tokens?
*
*/
export default function AddLiquidity({ token0, token1 }) { export default function AddLiquidity({ token0, token1 }) {
const { account, chainId, library } = useWeb3React() const { account, chainId, library } = useWeb3React()
const [, addPopup] = usePopups()
const routerAddress: string = ROUTER_ADDRESSES[chainId] const routerAddress: string = ROUTER_ADDRESSES[chainId]
...@@ -152,28 +150,28 @@ export default function AddLiquidity({ token0, token1 }) { ...@@ -152,28 +150,28 @@ export default function AddLiquidity({ token0, token1 }) {
// input state // input state
const [state, dispatch] = useReducer(reducer, initializeAddState(token0, token1)) const [state, dispatch] = useReducer(reducer, initializeAddState(token0, token1))
const { independentField, typedValue, ...fieldData } = state 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 // get basic SDK entities
const tokens = { const tokens: { [field: number]: Token } = {
[Field.INPUT]: useToken(fieldData[Field.INPUT].address), [Field.INPUT]: useToken(fieldData[Field.INPUT].address),
[Field.OUTPUT]: useToken(fieldData[Field.OUTPUT].address) [Field.OUTPUT]: useToken(fieldData[Field.OUTPUT].address)
} }
// exhchange data // exhchange data
const exchange = useExchange(tokens[Field.INPUT], tokens[Field.OUTPUT]) const exchange: Exchange = useExchange(tokens[Field.INPUT], tokens[Field.OUTPUT])
const route = exchange ? new Route([exchange], tokens[independentField]) : undefined const route: Route = exchange ? new Route([exchange], tokens[independentField]) : undefined
const totalSupply = useTotalSupply(exchange) const totalSupply: TokenAmount = useTotalSupply(exchange)
const [noLiquidity, setNoLiquidity] = useState<boolean>(false) const [noLiquidity, setNoLiquidity] = useState<boolean>(false) // used to detect new exchange
// state for amount approvals // state for amount approvals
const inputApproval = useAddressAllowance(account, tokens[Field.INPUT], routerAddress) const inputApproval: TokenAmount = useAddressAllowance(account, tokens[Field.INPUT], routerAddress)
const outputApproval = useAddressAllowance(account, tokens[Field.OUTPUT], routerAddress) const outputApproval: TokenAmount = useAddressAllowance(account, tokens[Field.OUTPUT], routerAddress)
const [showInputUnlock, setShowInputUnlock] = useState<boolean>(false) const [showInputUnlock, setShowInputUnlock] = useState<boolean>(false)
const [showOutputUnlock, setShowOutputUnlock] = useState<boolean>(false) const [showOutputUnlock, setShowOutputUnlock] = useState<boolean>(false)
// get user-pecific and token-specific lookup data // get user-pecific and token-specific lookup data
const userBalances = { const userBalances: { [field: number]: TokenAmount } = {
[Field.INPUT]: useAddressBalance(account, tokens[Field.INPUT]), [Field.INPUT]: useAddressBalance(account, tokens[Field.INPUT]),
[Field.OUTPUT]: useAddressBalance(account, tokens[Field.OUTPUT]) [Field.OUTPUT]: useAddressBalance(account, tokens[Field.OUTPUT])
} }
...@@ -219,7 +217,6 @@ export default function AddLiquidity({ token0, token1 }) { ...@@ -219,7 +217,6 @@ export default function AddLiquidity({ token0, token1 }) {
if (typedValueParsed !== '0') if (typedValueParsed !== '0')
parsedAmounts[independentField] = new TokenAmount(tokens[independentField], typedValueParsed) parsedAmounts[independentField] = new TokenAmount(tokens[independentField], typedValueParsed)
} catch (error) { } catch (error) {
// should only fail if the user specifies too many decimal places of precision (or maybe exceed max uint?)
console.error(error) console.error(error)
} }
} }
...@@ -240,12 +237,16 @@ export default function AddLiquidity({ token0, token1 }) { ...@@ -240,12 +237,16 @@ export default function AddLiquidity({ token0, token1 }) {
} }
// check for estimated liquidity minted // check for estimated liquidity minted
const liquidityMinted = const liquidityMinted: TokenAmount =
!!exchange && !!parsedAmounts[Field.INPUT] && !!parsedAmounts[Field.OUTPUT] && !!totalSupply !!exchange && !!parsedAmounts[Field.INPUT] && !!parsedAmounts[Field.OUTPUT]
? exchange.getLiquidityMinted(totalSupply, parsedAmounts[Field.INPUT], parsedAmounts[Field.OUTPUT]) ? exchange.getLiquidityMinted(
totalSupply ? totalSupply : new TokenAmount(exchange?.liquidityToken, JSBI.BigInt(0)),
parsedAmounts[Field.INPUT],
parsedAmounts[Field.OUTPUT]
)
: undefined : undefined
const poolTokenPercentage = const poolTokenPercentage: Percent =
!!liquidityMinted && !!totalSupply !!liquidityMinted && !!totalSupply
? new Percent(liquidityMinted.raw, totalSupply.add(liquidityMinted).raw) ? new Percent(liquidityMinted.raw, totalSupply.add(liquidityMinted).raw)
: undefined : undefined
...@@ -271,10 +272,10 @@ export default function AddLiquidity({ token0, token1 }) { ...@@ -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 // 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] const field = Field[index]
return !!userBalances[Field[field]] && return !!userBalances[Field[field]] &&
JSBI.greaterThan( JSBI.greaterThan(
...@@ -287,7 +288,7 @@ export default function AddLiquidity({ token0, token1 }) { ...@@ -287,7 +288,7 @@ export default function AddLiquidity({ token0, token1 }) {
: undefined : 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 field = Field[index]
const maxAmount = index === Field.INPUT ? maxAmountInput : maxAmountOutput const maxAmount = index === Field.INPUT ? maxAmountInput : maxAmountOutput
return !!maxAmount && !!parsedAmounts[Field[field]] return !!maxAmount && !!parsedAmounts[Field[field]]
...@@ -357,22 +358,23 @@ export default function AddLiquidity({ token0, token1 }) { ...@@ -357,22 +358,23 @@ export default function AddLiquidity({ token0, token1 }) {
// state for txn // state for txn
const addTransaction = useTransactionAdder() const addTransaction = useTransactionAdder()
const [txHash, setTxHash] = useState() const [txHash, setTxHash] = useState<string>('')
// format ETH value for transaction
function hex(value: JSBI) { function hex(value: JSBI) {
return ethers.utils.bigNumberify(value.toString()) return ethers.utils.bigNumberify(value.toString())
} }
// calculate slippage bounds based on current reserves
function calculateSlippageAmount(value: TokenAmount): JSBI[] { function calculateSlippageAmount(value: TokenAmount): JSBI[] {
if (value && value.raw) { if (value && value.raw) {
const offset = JSBI.divide(JSBI.multiply(JSBI.BigInt(ALLOWED_SLIPPAGE), value.raw), JSBI.BigInt(10000)) 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)] return [JSBI.subtract(value.raw, offset), JSBI.add(value.raw, offset)]
} else {
return null
} }
return null
} }
const [, addPopup] = usePopups()
async function onAdd() { async function onAdd() {
setAttemptingTxn(true) setAttemptingTxn(true)
const router = getRouterContract(chainId, library, account) const router = getRouterContract(chainId, library, account)
...@@ -387,6 +389,7 @@ export default function AddLiquidity({ token0, token1 }) { ...@@ -387,6 +389,7 @@ export default function AddLiquidity({ token0, token1 }) {
if (tokens[Field.INPUT] === WETH[chainId] || tokens[Field.OUTPUT] === WETH[chainId]) { if (tokens[Field.INPUT] === WETH[chainId] || tokens[Field.OUTPUT] === WETH[chainId]) {
method = router.addLiquidityETH method = router.addLiquidityETH
estimate = router.estimate.addLiquidityETH estimate = router.estimate.addLiquidityETH
args = [ args = [
tokens[Field.OUTPUT] === WETH[chainId] ? tokens[Field.INPUT].address : tokens[Field.OUTPUT].address, // token tokens[Field.OUTPUT] === WETH[chainId] ? tokens[Field.INPUT].address : tokens[Field.OUTPUT].address, // token
tokens[Field.OUTPUT] === WETH[chainId] // token desired tokens[Field.OUTPUT] === WETH[chainId] // token desired
...@@ -466,60 +469,48 @@ export default function AddLiquidity({ token0, token1 }) { ...@@ -466,60 +469,48 @@ export default function AddLiquidity({ token0, token1 }) {
return ( return (
<> <>
<RowBetween> <RowBetween>
<Text color="#565A69" fontWeight={500} fontSize={16}> <TYPE.body>{tokens[Field.INPUT]?.symbol} Deposited</TYPE.body>
{tokens[Field.INPUT]?.symbol} Deposited
</Text>
<RowFixed> <RowFixed>
<TokenLogo address={tokens[Field.INPUT]?.address || ''} style={{ marginRight: '8px' }} /> <TokenLogo address={tokens[Field.INPUT]?.address || ''} style={{ marginRight: '8px' }} />
<Text fontWeight={500} fontSize={16}> <TYPE.body>{!!parsedAmounts[Field.INPUT] && parsedAmounts[Field.INPUT].toSignificant(6)}</TYPE.body>
{!!parsedAmounts[Field.INPUT] && parsedAmounts[Field.INPUT].toSignificant(6)}
</Text>
</RowFixed> </RowFixed>
</RowBetween> </RowBetween>
<RowBetween> <RowBetween>
<Text color="#565A69" fontWeight={500} fontSize={16}> <TYPE.body>{tokens[Field.OUTPUT]?.symbol} Deposited</TYPE.body>
{tokens[Field.OUTPUT]?.symbol} Deposited
</Text>
<RowFixed> <RowFixed>
<TokenLogo address={tokens[Field.OUTPUT]?.address || ''} style={{ marginRight: '8px' }} /> <TokenLogo address={tokens[Field.OUTPUT]?.address || ''} style={{ marginRight: '8px' }} />
<Text fontWeight={500} fontSize={16}> <TYPE.body>{!!parsedAmounts[Field.OUTPUT] && parsedAmounts[Field.OUTPUT].toSignificant(6)}</TYPE.body>
{!!parsedAmounts[Field.OUTPUT] && parsedAmounts[Field.OUTPUT].toSignificant(6)}
</Text>
</RowFixed> </RowFixed>
</RowBetween> </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> <RowBetween>
<Text color="#565A69" fontWeight={500} fontSize={16}> <TYPE.body>Minted Pool Share:</TYPE.body>
Rate <TYPE.body>{noLiquidity ? '100%' : poolTokenPercentage?.toFixed(6) + '%'}</TYPE.body>
</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>
</RowBetween> </RowBetween>
<ButtonPrimary style={{ margin: '20px 0' }} onClick={onAdd}> <ButtonPrimary style={{ margin: '20px 0' }} onClick={onAdd}>
<Text fontWeight={500} fontSize={20}> <Text fontWeight={500} fontSize={20}>
Confirm Supply Confirm Supply
</Text> </Text>
</ButtonPrimary> </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 ${ {`Output is estimated. You will receive at least ${liquidityMinted?.toFixed(6)} UNI ${
tokens[Field.INPUT]?.symbol tokens[Field.INPUT]?.symbol
}/${tokens[Field.OUTPUT]?.symbol} or the transaction will revert.`} }/${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 tokens[Field.INPUT]?.symbol
} ${'and'} ${parsedAmounts[Field.OUTPUT]?.toSignificant(6)} ${tokens[Field.OUTPUT]?.symbol}` } ${'and'} ${parsedAmounts[Field.OUTPUT]?.toSignificant(6)} ${tokens[Field.OUTPUT]?.symbol}`
...@@ -549,12 +540,12 @@ export default function AddLiquidity({ token0, token1 }) { ...@@ -549,12 +540,12 @@ export default function AddLiquidity({ token0, token1 }) {
<AutoColumn gap="20px"> <AutoColumn gap="20px">
{noLiquidity && ( {noLiquidity && (
<ColumnCenter> <ColumnCenter>
<Text fontWeight={500} style={{ textAlign: 'center' }}> <TYPE.main textAlign="center">
<span role="img" aria-label="Thinking"> <span role="img" aria-label="Thinking">
🥇 🥇
</span>{' '} </span>{' '}
You are the first to add liquidity. Make sure you're setting rates correctly. You are the first to add liquidity. Make sure you're setting rates correctly.
</Text> </TYPE.main>
</ColumnCenter> </ColumnCenter>
)} )}
<CurrencyInputPanel <CurrencyInputPanel
...@@ -592,13 +583,15 @@ export default function AddLiquidity({ token0, token1 }) { ...@@ -592,13 +583,15 @@ export default function AddLiquidity({ token0, token1 }) {
showUnlock={showOutputUnlock} showUnlock={showOutputUnlock}
disableTokenSelect disableTokenSelect
/> />
<RowBetween> {!noLiquidity && (
Rate: <RowBetween>
<div> Rate:
1 {tokens[independentField].symbol} = {route?.midPrice?.toSignificant(6)} <div>
{tokens[dependentField].symbol} 1 {tokens[independentField].symbol} = {route?.midPrice?.toSignificant(6)}
</div> {tokens[dependentField].symbol}
</RowBetween> </div>
</RowBetween>
)}
<ButtonPrimary <ButtonPrimary
onClick={() => { onClick={() => {
setShowConfirm(true) setShowConfirm(true)
......
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' ...@@ -2,7 +2,7 @@ import React, { useReducer, useState, useCallback, useEffect } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { ethers } from 'ethers' import { ethers } from 'ethers'
import { parseUnits } from '@ethersproject/units' 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 Slider from '../../components/Slider'
import TokenLogo from '../../components/TokenLogo' import TokenLogo from '../../components/TokenLogo'
...@@ -21,8 +21,8 @@ import Row, { RowBetween, RowFixed } from '../../components/Row' ...@@ -21,8 +21,8 @@ import Row, { RowBetween, RowFixed } from '../../components/Row'
import { useToken } from '../../contexts/Tokens' import { useToken } from '../../contexts/Tokens'
import { useWeb3React } from '../../hooks' import { useWeb3React } from '../../hooks'
import { useAllBalances } from '../../contexts/Balances' import { useAllBalances } from '../../contexts/Balances'
import { useTransactionAdder } from '../../contexts/Transactions'
import { useExchangeContract } from '../../hooks' import { useExchangeContract } from '../../hooks'
import { useTransactionAdder } from '../../contexts/Transactions'
import { useExchange, useTotalSupply } from '../../contexts/Exchanges' import { useExchange, useTotalSupply } from '../../contexts/Exchanges'
import { BigNumber } from 'ethers/utils' import { BigNumber } from 'ethers/utils'
...@@ -145,34 +145,34 @@ const ConfirmedText = styled(Text)` ...@@ -145,34 +145,34 @@ const ConfirmedText = styled(Text)`
export default function RemoveLiquidity({ token0, token1 }) { export default function RemoveLiquidity({ token0, token1 }) {
const { account, chainId, library } = useWeb3React() const { account, chainId, library } = useWeb3React()
const routerAddress = ROUTER_ADDRESSES[chainId] const routerAddress: string = ROUTER_ADDRESSES[chainId]
const [showConfirm, setShowConfirm] = useState(false) const [showConfirm, setShowConfirm] = useState<boolean>(false)
const [showAdvanced, setShowAdvanced] = useState(false) const [showAdvanced, setShowAdvanced] = useState<boolean>(false)
const inputToken = useToken(token0) const inputToken: Token = useToken(token0)
const outputToken = useToken(token1) const outputToken: Token = useToken(token1)
// get basic SDK entities // get basic SDK entities
const tokens = { const tokens: { [field: number]: Token } = {
[Field.TOKEN0]: inputToken, [Field.TOKEN0]: inputToken,
[Field.TOKEN1]: outputToken [Field.TOKEN1]: outputToken
} }
const exchange = useExchange(inputToken, outputToken) const exchange: Exchange = useExchange(inputToken, outputToken)
const exchangeContract = useExchangeContract(exchange?.liquidityToken.address) const exchangeContract: ethers.Contract = useExchangeContract(exchange?.liquidityToken.address)
// pool token data // pool token data
const totalPoolTokens = useTotalSupply(exchange) const totalPoolTokens: TokenAmount = useTotalSupply(exchange)
const allBalances = useAllBalances() const allBalances: TokenAmount[] = useAllBalances()
const userLiquidity = allBalances?.[account]?.[exchange?.liquidityToken?.address] const userLiquidity: TokenAmount = allBalances?.[account]?.[exchange?.liquidityToken?.address]
// input state // input state
const [state, dispatch] = useReducer(reducer, initializeRemoveState(userLiquidity?.toExact(), token0, token1)) const [state, dispatch] = useReducer(reducer, initializeRemoveState(userLiquidity?.toExact(), token0, token1))
const { independentField, typedValue } = state const { independentField, typedValue } = state
const TokensDeposited = { const TokensDeposited: { [field: number]: TokenAmount } = {
[Field.TOKEN0]: [Field.TOKEN0]:
exchange && exchange &&
totalPoolTokens && totalPoolTokens &&
...@@ -185,7 +185,7 @@ export default function RemoveLiquidity({ token0, token1 }) { ...@@ -185,7 +185,7 @@ export default function RemoveLiquidity({ token0, token1 }) {
exchange.getLiquidityValue(tokens[Field.TOKEN1], totalPoolTokens, userLiquidity, false) 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]) ? new Route([exchange], independentField !== Field.LIQUIDITY ? tokens[independentField] : tokens[Field.TOKEN1])
: undefined : undefined
...@@ -307,11 +307,11 @@ export default function RemoveLiquidity({ token0, token1 }) { ...@@ -307,11 +307,11 @@ export default function RemoveLiquidity({ token0, token1 }) {
: false : false
// errors // errors
const [generalError, setGeneralError] = useState('') const [generalError, setGeneralError] = useState<string>('')
const [inputError, setInputError] = useState('') const [inputError, setInputError] = useState<string>('')
const [outputError, setOutputError] = useState('') const [outputError, setOutputError] = useState<string>('')
const [poolTokenError, setPoolTokenError] = useState('') const [poolTokenError, setPoolTokenError] = useState<string>('')
const [isValid, setIsValid] = useState(false) const [isValid, setIsValid] = useState<boolean>(false)
// update errors live // update errors live
useEffect(() => { useEffect(() => {
...@@ -351,7 +351,7 @@ export default function RemoveLiquidity({ token0, token1 }) { ...@@ -351,7 +351,7 @@ export default function RemoveLiquidity({ token0, token1 }) {
const addTransaction = useTransactionAdder() const addTransaction = useTransactionAdder()
const [txHash, setTxHash] = useState() const [txHash, setTxHash] = useState()
const [sigInputs, setSigInputs] = useState([]) const [sigInputs, setSigInputs] = useState([])
const [deadline, setDeadline] = useState() const [deadline, setDeadline] = useState(null)
const [signed, setSigned] = useState(false) // waiting for signature sign const [signed, setSigned] = useState(false) // waiting for signature sign
const [attemptedRemoval, setAttemptedRemoval] = useState(false) // clicke confirm const [attemptedRemoval, setAttemptedRemoval] = useState(false) // clicke confirm
const [pendingConfirmation, setPendingConfirmation] = useState(true) // waiting for const [pendingConfirmation, setPendingConfirmation] = useState(true) // waiting for
...@@ -359,7 +359,7 @@ export default function RemoveLiquidity({ token0, token1 }) { ...@@ -359,7 +359,7 @@ export default function RemoveLiquidity({ token0, token1 }) {
async function onSign() { async function onSign() {
const nonce = await exchangeContract.nonces(account) 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) setDeadline(newDeadline)
const EIP712Domain = [ const EIP712Domain = [
...@@ -428,6 +428,7 @@ export default function RemoveLiquidity({ token0, token1 }) { ...@@ -428,6 +428,7 @@ export default function RemoveLiquidity({ token0, token1 }) {
: parsedAmounts[Field.TOKEN0].raw.toString(), : parsedAmounts[Field.TOKEN0].raw.toString(),
account, account,
deadline, deadline,
false,
sigInputs[0], sigInputs[0],
sigInputs[1], sigInputs[1],
sigInputs[2] sigInputs[2]
...@@ -445,6 +446,7 @@ export default function RemoveLiquidity({ token0, token1 }) { ...@@ -445,6 +446,7 @@ export default function RemoveLiquidity({ token0, token1 }) {
parsedAmounts[Field.TOKEN1].raw.toString(), parsedAmounts[Field.TOKEN1].raw.toString(),
account, account,
deadline, deadline,
false,
sigInputs[0], sigInputs[0],
sigInputs[1], sigInputs[1],
sigInputs[2] sigInputs[2]
...@@ -466,18 +468,13 @@ export default function RemoveLiquidity({ token0, token1 }) { ...@@ -466,18 +468,13 @@ export default function RemoveLiquidity({ token0, token1 }) {
setTxHash(response.hash) setTxHash(response.hash)
addTransaction(response) addTransaction(response)
}) })
.catch(() => { .catch(e => {
console.log(e)
resetModalState() resetModalState()
setShowConfirm(false) setShowConfirm(false)
}) })
} }
/**
* @todo
* if the input values stay the same,
* we should probably not reset the signature values,
* move to an effect
*/
function resetModalState() { function resetModalState() {
setSigned(false) setSigned(false)
setSigInputs(null) setSigInputs(null)
...@@ -562,7 +559,7 @@ export default function RemoveLiquidity({ token0, token1 }) { ...@@ -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 tokens[Field.TOKEN0]?.symbol
} and ${parsedAmounts[Field.TOKEN1]?.toSignificant(6)} ${tokens[Field.TOKEN1]?.symbol}` } and ${parsedAmounts[Field.TOKEN1]?.toSignificant(6)} ${tokens[Field.TOKEN1]?.symbol}`
......
...@@ -72,7 +72,7 @@ const theme = darkMode => ({ ...@@ -72,7 +72,7 @@ const theme = darkMode => ({
tokenRowHover: darkMode ? '#404040' : '#F2F2F2', tokenRowHover: darkMode ? '#404040' : '#F2F2F2',
outlineGrey: darkMode ? '#292C2F' : '#EDEEF2', outlineGrey: darkMode ? '#292C2F' : '#EDEEF2',
darkGrey: darkMode ? '#888D9B' : '#888D9B', darkGray: darkMode ? '#888D9B' : '#888D9B',
//blacks //blacks
charcoalBlack: darkMode ? '#F2F2F2' : '#404040', charcoalBlack: darkMode ? '#F2F2F2' : '#404040',
...@@ -85,6 +85,7 @@ const theme = darkMode => ({ ...@@ -85,6 +85,7 @@ const theme = darkMode => ({
// purples // purples
wisteriaPurple: '#DC6BE5', wisteriaPurple: '#DC6BE5',
// reds // reds
salmonRed: '#FF6871', salmonRed: '#FF6871',
// orange // orange
...@@ -93,6 +94,7 @@ const theme = darkMode => ({ ...@@ -93,6 +94,7 @@ const theme = darkMode => ({
warningYellow: '#FFE270', warningYellow: '#FFE270',
// pink // pink
uniswapPink: '#DC6BE5', uniswapPink: '#DC6BE5',
darkPink: '#ff007a',
//green //green
connectedGreen: '#27AE60', connectedGreen: '#27AE60',
...@@ -124,6 +126,16 @@ export const TYPE = { ...@@ -124,6 +126,16 @@ export const TYPE = {
{children} {children}
</Text> </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 }) => ( blue: ({ children, ...rest }) => (
<Text fontWeight={500} color={theme().royalBlue} {...rest}> <Text fontWeight={500} color={theme().royalBlue} {...rest}>
{children} {children}
...@@ -134,6 +146,11 @@ export const TYPE = { ...@@ -134,6 +146,11 @@ export const TYPE = {
{children} {children}
</Text> </Text>
), ),
darkGray: ({ children, ...rest }) => (
<Text fontWeight={500} color={theme().darkGray} {...rest}>
{children}
</Text>
),
italic: ({ children, ...rest }) => ( italic: ({ children, ...rest }) => (
<Text fontWeight={500} fontSize={12} fontStyle={'italic'} color={theme().mineshaftGray} {...rest}> <Text fontWeight={500} fontSize={12} fontStyle={'italic'} color={theme().mineshaftGray} {...rest}>
{children} {children}
......
...@@ -5,7 +5,7 @@ import EXCHANGE_ABI from '../constants/abis/exchange' ...@@ -5,7 +5,7 @@ import EXCHANGE_ABI from '../constants/abis/exchange'
import ROUTER_ABI from '../constants/abis/router' import ROUTER_ABI from '../constants/abis/router'
import ERC20_ABI from '../constants/abis/erc20' import ERC20_ABI from '../constants/abis/erc20'
import ERC20_BYTES32_ABI from '../constants/abis/erc20_bytes32' 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 { bigNumberify, keccak256, defaultAbiCoder, toUtf8Bytes, solidityPack } from 'ethers/utils'
import UncheckedJsonRpcSigner from './signer' import UncheckedJsonRpcSigner from './signer'
...@@ -131,8 +131,8 @@ export function getContract(address, ABI, library, account) { ...@@ -131,8 +131,8 @@ export function getContract(address, ABI, library, account) {
} }
// account is optional // account is optional
export function getRouterContract(networkId, library, account) { export function getRouterContract(chainId, library, account) {
const router = getContract('0xd9210Ff5A0780E083BB40e30d005d93a2DcFA4EF', ROUTER_ABI, library, account) const router = getContract(ROUTER_ADDRESSES[chainId], ROUTER_ABI, library, account)
return router return router
} }
......
...@@ -1319,7 +1319,7 @@ ...@@ -1319,7 +1319,7 @@
"@ethersproject/logger" ">=5.0.0-beta.129" "@ethersproject/logger" ">=5.0.0-beta.129"
"@ethersproject/strings" ">=5.0.0-beta.130" "@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" version "5.0.0-beta.131"
resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.0.0-beta.131.tgz#b5778723ee75208065b9b9ad30c71d480f41bb31" resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.0.0-beta.131.tgz#b5778723ee75208065b9b9ad30c71d480f41bb31"
integrity sha512-KQnqMwGV0IMOjAr/UTFO8DuLrmN1uaMvcV3zh9hiXhh3rCuY+WXdeUh49w1VQ94kBKmaP0qfGb7z4SdhUWUHjw== integrity sha512-KQnqMwGV0IMOjAr/UTFO8DuLrmN1uaMvcV3zh9hiXhh3rCuY+WXdeUh49w1VQ94kBKmaP0qfGb7z4SdhUWUHjw==
...@@ -1382,6 +1382,15 @@ ...@@ -1382,6 +1382,15 @@
dependencies: dependencies:
"@ethersproject/bytes" ">=5.0.0-beta.129" "@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": "@ethersproject/signing-key@>=5.0.0-beta.129":
version "5.0.0-beta.135" version "5.0.0-beta.135"
resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.0.0-beta.135.tgz#f739e800aad9e01b77a8ec2c353b9b66ce5738fa" resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.0.0-beta.135.tgz#f739e800aad9e01b77a8ec2c353b9b66ce5738fa"
...@@ -1392,6 +1401,17 @@ ...@@ -1392,6 +1401,17 @@
"@ethersproject/properties" ">=5.0.0-beta.131" "@ethersproject/properties" ">=5.0.0-beta.131"
elliptic "6.5.2" 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": "@ethersproject/strings@>=5.0.0-beta.130":
version "5.0.0-beta.136" version "5.0.0-beta.136"
resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.0.0-beta.136.tgz#053cbf4f9f96a7537cbc50300597f2d707907f51" resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.0.0-beta.136.tgz#053cbf4f9f96a7537cbc50300597f2d707907f51"
...@@ -2365,16 +2385,16 @@ ...@@ -2365,16 +2385,16 @@
semver "^6.3.0" semver "^6.3.0"
tsutils "^3.17.1" tsutils "^3.17.1"
"@uniswap/sdk@@uniswap/sdk@2.0.0-beta.17": "@uniswap/sdk@@uniswap/sdk@2.0.0-beta.19":
version "2.0.0-beta.17" version "2.0.0-beta.19"
resolved "https://registry.yarnpkg.com/@uniswap/sdk/-/sdk-2.0.0-beta.17.tgz#8f24be0375d5f8137eae75afe75b2356c75bb793" resolved "https://registry.yarnpkg.com/@uniswap/sdk/-/sdk-2.0.0-beta.19.tgz#1f0228a1d5451d62f209e09c48cd1d6bea5ffe01"
integrity sha512-Nd3S/VE51z4jsNs9G9hkslUkS862dpslnU86lXEJi7mbbbPIagh31iR0s/uBPnrBFGiktucgvzRn6WJJIvojWA== integrity sha512-mqDZkeX2TU7e3yOOKHSeUryv94//mJ6dsU6dCv6FExrfOY9yi6+O5ZWpe43rVi0XFVdQGRIRhJapdWKWaGsung==
dependencies: dependencies:
"@ethersproject/address" "^5.0.0-beta.134" "@ethersproject/address" "^5.0.0-beta.134"
"@ethersproject/contracts" "^5.0.0-beta.143" "@ethersproject/contracts" "^5.0.0-beta.143"
"@ethersproject/keccak256" "^5.0.0-beta.131"
"@ethersproject/networks" "^5.0.0-beta.135" "@ethersproject/networks" "^5.0.0-beta.135"
"@ethersproject/providers" "^5.0.0-beta.153" "@ethersproject/providers" "^5.0.0-beta.153"
"@ethersproject/solidity" "^5.0.0-beta.131"
big.js "^5.2.2" big.js "^5.2.2"
decimal.js-light "^2.5.0" decimal.js-light "^2.5.0"
jsbi "^3.1.1" jsbi "^3.1.1"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment