typed pages
This commit is contained in:
parent
655b79569b
commit
6211dff044
@ -13,7 +13,7 @@
|
||||
"@types/node": "^13.7.4",
|
||||
"@types/react": "^16.9.21",
|
||||
"@types/react-dom": "^16.9.5",
|
||||
"@uniswap/sdk": "@uniswap/sdk@2.0.0-beta.17",
|
||||
"@uniswap/sdk": "@uniswap/sdk@2.0.0-beta.19",
|
||||
"@web3-react/core": "^6.0.2",
|
||||
"@web3-react/fortmatic-connector": "^6.0.2",
|
||||
"@web3-react/injected-connector": "^6.0.3",
|
||||
|
@ -3,6 +3,8 @@ import styled from 'styled-components'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { transparentize } from 'polished'
|
||||
|
||||
import QR from '../../assets/svg/QR.svg'
|
||||
|
||||
import { isAddress } from '../../utils'
|
||||
import { useWeb3React, useDebounce } from '../../hooks'
|
||||
|
||||
@ -13,6 +15,7 @@ const InputPanel = styled.div`
|
||||
border-radius: 1.25rem;
|
||||
background-color: ${({ theme }) => theme.inputBackground};
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
const ContainerRow = styled.div`
|
||||
@ -49,7 +52,7 @@ const LabelContainer = styled.div`
|
||||
const InputRow = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
align-items: center;
|
||||
padding: 0.25rem 0.85rem 0.75rem;
|
||||
padding: 0.75rem;
|
||||
`
|
||||
|
||||
const Input = styled.input`
|
||||
@ -69,7 +72,17 @@ const Input = styled.input`
|
||||
}
|
||||
`
|
||||
|
||||
export default function AddressInputPanel({ title, initialInput = '', onChange = () => {}, onError = () => {} }) {
|
||||
const QRWrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid ${({ theme }) => theme.outlineGrey};
|
||||
background: #fbfbfb;
|
||||
padding: 4px;
|
||||
border-radius: 8px;
|
||||
`
|
||||
|
||||
export default function AddressInputPanel({ title, initialInput = '', onChange, onError}) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { library } = useWeb3React()
|
||||
@ -166,11 +179,6 @@ export default function AddressInputPanel({ title, initialInput = '', onChange =
|
||||
<InputPanel>
|
||||
<ContainerRow error={input !== '' && error}>
|
||||
<InputContainer>
|
||||
<LabelRow>
|
||||
<LabelContainer>
|
||||
<span>{title || t('recipientAddress')}</span>
|
||||
</LabelContainer>
|
||||
</LabelRow>
|
||||
<InputRow>
|
||||
<Input
|
||||
type="text"
|
||||
@ -183,6 +191,9 @@ export default function AddressInputPanel({ title, initialInput = '', onChange =
|
||||
onChange={onInput}
|
||||
value={input}
|
||||
/>
|
||||
<QRWrapper>
|
||||
<img src={QR} alt="" />
|
||||
</QRWrapper>
|
||||
</InputRow>
|
||||
</InputContainer>
|
||||
</ContainerRow>
|
||||
|
150
src/components/AdvancedSettings/index.js
Normal file
150
src/components/AdvancedSettings/index.js
Normal file
@ -0,0 +1,150 @@
|
||||
import React, { useState } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import QuestionHelper from '../Question'
|
||||
import NumericalInput from '../NumericalInput'
|
||||
import { Link } from '../../theme/components'
|
||||
import { TYPE } from '../../theme'
|
||||
import { AutoColumn } from '../../components/Column'
|
||||
import Row, { RowBetween, RowFixed } from '../../components/Row'
|
||||
import { ButtonRadio } from '../Button'
|
||||
|
||||
const InputWrapper = styled(RowBetween)`
|
||||
width: 200px;
|
||||
background-color: ${({ theme }) => theme.inputBackground};
|
||||
border-radius: 8px;
|
||||
padding: 4px 8px;
|
||||
border: 1px solid transparent;
|
||||
border: ${({ active, error, theme }) =>
|
||||
error ? '1px solid ' + theme.salmonRed : active ? '1px solid ' + theme.royalBlue : ''};
|
||||
`
|
||||
|
||||
const SLIPPAGE_INDEX = {
|
||||
1: 1,
|
||||
2: 2,
|
||||
3: 3,
|
||||
4: 4
|
||||
}
|
||||
|
||||
export default function AdvancedSettings({ setIsOpen, setDeadline, setAllowedSlippage }) {
|
||||
const [deadlineInput, setDeadlineInput] = useState(15)
|
||||
const [slippageInput, setSlippageInput] = useState()
|
||||
const [activeIndex, setActiveIndex] = useState(SLIPPAGE_INDEX[3])
|
||||
|
||||
const [slippageInputError, setSlippageInputError] = useState(null) // error
|
||||
|
||||
function parseCustomInput(val) {
|
||||
const acceptableValues = [/^$/, /^\d{1,2}$/, /^\d{0,2}\.\d{0,2}$/]
|
||||
if (val > 5) {
|
||||
setSlippageInputError('Your transaction may be front-run.')
|
||||
} else {
|
||||
setSlippageInputError(null)
|
||||
}
|
||||
if (acceptableValues.some(a => a.test(val))) {
|
||||
setSlippageInput(val)
|
||||
setAllowedSlippage(val * 100)
|
||||
}
|
||||
}
|
||||
|
||||
function parseCustomDeadline(val) {
|
||||
const acceptableValues = [/^$/, /^\d+$/]
|
||||
if (acceptableValues.some(re => re.test(val))) {
|
||||
setDeadlineInput(val)
|
||||
setDeadline(val * 60)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<AutoColumn gap="20px">
|
||||
<Link
|
||||
onClick={() => {
|
||||
setIsOpen(false)
|
||||
}}
|
||||
>
|
||||
back
|
||||
</Link>
|
||||
<RowBetween>
|
||||
<TYPE.main>Limit additional price impact</TYPE.main>
|
||||
<QuestionHelper text="" />
|
||||
</RowBetween>
|
||||
<Row>
|
||||
<ButtonRadio
|
||||
active={SLIPPAGE_INDEX[1] === activeIndex}
|
||||
padding="4px 6px"
|
||||
borderRadius="8px"
|
||||
style={{ marginRight: '16px' }}
|
||||
width={'60px'}
|
||||
onClick={() => {
|
||||
setActiveIndex(SLIPPAGE_INDEX[1])
|
||||
setAllowedSlippage(10)
|
||||
}}
|
||||
>
|
||||
0.1%
|
||||
</ButtonRadio>
|
||||
<ButtonRadio
|
||||
active={SLIPPAGE_INDEX[2] === activeIndex}
|
||||
padding="4px 6px"
|
||||
borderRadius="8px"
|
||||
style={{ marginRight: '16px' }}
|
||||
width={'60px'}
|
||||
onClick={() => {
|
||||
setActiveIndex(SLIPPAGE_INDEX[2])
|
||||
setAllowedSlippage(100)
|
||||
}}
|
||||
>
|
||||
1%
|
||||
</ButtonRadio>
|
||||
<ButtonRadio
|
||||
active={SLIPPAGE_INDEX[3] === activeIndex}
|
||||
padding="4px"
|
||||
borderRadius="8px"
|
||||
width={'140px'}
|
||||
onClick={() => {
|
||||
setActiveIndex(SLIPPAGE_INDEX[3])
|
||||
setAllowedSlippage(200)
|
||||
}}
|
||||
>
|
||||
2% (suggested)
|
||||
</ButtonRadio>
|
||||
</Row>
|
||||
<RowFixed>
|
||||
<InputWrapper active={SLIPPAGE_INDEX[4] === activeIndex} error={slippageInputError}>
|
||||
<NumericalInput
|
||||
align={slippageInput ? 'right' : 'left'}
|
||||
value={slippageInput || ''}
|
||||
onUserInput={val => {
|
||||
parseCustomInput(val)
|
||||
setActiveIndex(SLIPPAGE_INDEX[4])
|
||||
}}
|
||||
placeHolder="Custom"
|
||||
onClick={() => {
|
||||
setActiveIndex(SLIPPAGE_INDEX[4])
|
||||
if (slippageInput) {
|
||||
parseCustomInput(slippageInput)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
%
|
||||
</InputWrapper>
|
||||
{slippageInputError && (
|
||||
<TYPE.error error={true} fontSize={12} style={{ marginLeft: '10px' }}>
|
||||
Your transaction may be front-run
|
||||
</TYPE.error>
|
||||
)}
|
||||
</RowFixed>
|
||||
<RowBetween>
|
||||
<TYPE.main>Adjust deadline (minutes from now)</TYPE.main>
|
||||
</RowBetween>
|
||||
<RowFixed>
|
||||
<InputWrapper>
|
||||
<NumericalInput
|
||||
value={deadlineInput}
|
||||
onUserInput={val => {
|
||||
parseCustomDeadline(val)
|
||||
}}
|
||||
/>
|
||||
</InputWrapper>
|
||||
</RowFixed>
|
||||
</AutoColumn>
|
||||
)
|
||||
}
|
@ -1,15 +1,14 @@
|
||||
import React from 'react'
|
||||
import { Button as RebassButton } from 'rebass/styled-components'
|
||||
import styled from 'styled-components'
|
||||
import { darken, lighten } from 'polished'
|
||||
|
||||
import { RowBetween } from '../Row'
|
||||
import { ChevronDown } from 'react-feather'
|
||||
import { Button as RebassButton } from 'rebass/styled-components'
|
||||
|
||||
const Base = styled(RebassButton)`
|
||||
padding: ${({ padding }) => (padding ? padding : '18px')};
|
||||
width: ${({ width }) => (width ? width : '100%')};
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
border-radius: 20px;
|
||||
@ -39,7 +38,7 @@ export const ButtonPrimary = styled(Base)`
|
||||
}
|
||||
&:disabled {
|
||||
background-color: ${({ theme }) => theme.outlineGrey};
|
||||
color: ${({ theme }) => theme.darkGrey}
|
||||
color: ${({ theme }) => theme.darkGray}
|
||||
cursor: auto;
|
||||
box-shadow: none;
|
||||
}
|
||||
@ -48,6 +47,7 @@ export const ButtonPrimary = styled(Base)`
|
||||
export const ButtonSecondary = styled(Base)`
|
||||
background-color: #ebf4ff;
|
||||
color: #2172e5;
|
||||
font-size: 16px;
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
|
||||
@ -69,6 +69,30 @@ export const ButtonSecondary = styled(Base)`
|
||||
}
|
||||
`
|
||||
|
||||
export const ButtonPink = styled(Base)`
|
||||
background-color: ${({ theme }) => theme.darkPink};
|
||||
color: white;
|
||||
|
||||
padding: 10px;
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.05, theme.darkPink)};
|
||||
background-color: ${({ theme }) => darken(0.05, theme.darkPink)};
|
||||
}
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => darken(0.05, theme.darkPink)};
|
||||
}
|
||||
&:active {
|
||||
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.1, theme.darkPink)};
|
||||
background-color: ${({ theme }) => darken(0.1, theme.darkPink)};
|
||||
}
|
||||
&:disabled {
|
||||
background-color: ${({ theme }) => theme.darkPink};
|
||||
opacity: 50%;
|
||||
cursor: auto;
|
||||
}
|
||||
`
|
||||
|
||||
export const ButtonEmpty = styled(Base)`
|
||||
border: 1px solid #edeef2;
|
||||
background-color: transparent;
|
||||
@ -157,7 +181,7 @@ export function ButtonError({ children, error, ...rest }) {
|
||||
}
|
||||
}
|
||||
|
||||
export function ButtonDropwdown({ disabled, children, ...rest }) {
|
||||
export function ButtonDropwdown({ disabled = false, children, ...rest }) {
|
||||
return (
|
||||
<ButtonPrimary {...rest}>
|
||||
<RowBetween>
|
||||
@ -168,7 +192,7 @@ export function ButtonDropwdown({ disabled, children, ...rest }) {
|
||||
)
|
||||
}
|
||||
|
||||
export function ButtonDropwdownLight({ disabled, children, ...rest }) {
|
||||
export function ButtonDropwdownLight({ disabled = false, children, ...rest }) {
|
||||
return (
|
||||
<ButtonEmpty {...rest}>
|
||||
<RowBetween>
|
||||
|
@ -1,4 +1,6 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { Text } from 'rebass'
|
||||
import { Box } from 'rebass/styled-components'
|
||||
|
||||
const Card = styled(Box)`
|
||||
@ -18,3 +20,21 @@ export const LightCard = styled(Card)`
|
||||
export const GreyCard = styled(Card)`
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
`
|
||||
|
||||
const BlueCardStyled = styled(Card)`
|
||||
background-color: #ebf4ff;
|
||||
color: #2172e5;
|
||||
border-radius: 12px;
|
||||
padding: 8px;
|
||||
width: fit-content;
|
||||
`
|
||||
|
||||
export const BlueCard = ({ children }) => {
|
||||
return (
|
||||
<BlueCardStyled>
|
||||
<Text textAlign="center" fontWeight={500} color="#2172E5">
|
||||
{children}
|
||||
</Text>
|
||||
</BlueCardStyled>
|
||||
)
|
||||
}
|
||||
|
@ -1,127 +0,0 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import ReactGA from 'react-ga'
|
||||
import { ReactComponent as Dropup } from '../../assets/images/dropup-blue.svg'
|
||||
import { ReactComponent as Dropdown } from '../../assets/images/dropdown-blue.svg'
|
||||
|
||||
const SummaryWrapper = styled.div`
|
||||
color: ${({ error, theme }) => (error ? theme.salmonRed : theme.doveGray)};
|
||||
font-size: 0.75rem;
|
||||
text-align: center;
|
||||
margin-top: 1rem;
|
||||
padding-top: 1rem;
|
||||
`
|
||||
|
||||
const Details = styled.div`
|
||||
background-color: ${({ theme }) => theme.concreteGray};
|
||||
padding: 1.5rem;
|
||||
border-radius: 1rem;
|
||||
font-size: 0.75rem;
|
||||
margin-top: 1rem;
|
||||
`
|
||||
|
||||
const SummaryWrapperContainer = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap};
|
||||
color: ${({ theme }) => theme.royalBlue};
|
||||
text-align: center;
|
||||
margin-top: 1rem;
|
||||
padding-top: 1rem;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.75rem;
|
||||
|
||||
span {
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
img {
|
||||
height: 0.75rem;
|
||||
width: 0.75rem;
|
||||
}
|
||||
`
|
||||
|
||||
const WrappedDropup = ({ isError, highSlippageWarning, ...rest }) => <Dropup {...rest} />
|
||||
const ColoredDropup = styled(WrappedDropup)`
|
||||
path {
|
||||
stroke: ${({ theme }) => theme.royalBlue};
|
||||
}
|
||||
`
|
||||
|
||||
const WrappedDropdown = ({ isError, highSlippageWarning, ...rest }) => <Dropdown {...rest} />
|
||||
const ColoredDropdown = styled(WrappedDropdown)`
|
||||
path {
|
||||
stroke: ${({ theme }) => theme.royalBlue};
|
||||
}
|
||||
`
|
||||
|
||||
class ContextualInfo extends Component {
|
||||
static propTypes = {
|
||||
openDetailsText: PropTypes.string,
|
||||
renderTransactionDetails: PropTypes.func,
|
||||
contextualInfo: PropTypes.string,
|
||||
isError: PropTypes.bool
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
openDetailsText: 'Advanced Details',
|
||||
closeDetailsText: 'Hide Advanced',
|
||||
renderTransactionDetails() {},
|
||||
contextualInfo: '',
|
||||
isError: false
|
||||
}
|
||||
|
||||
state = {
|
||||
showDetails: false
|
||||
}
|
||||
|
||||
renderDetails() {
|
||||
if (!this.state.showDetails) {
|
||||
return null
|
||||
}
|
||||
return <Details>{this.props.renderTransactionDetails()}</Details>
|
||||
}
|
||||
|
||||
render() {
|
||||
const { openDetailsText, closeDetailsText, contextualInfo, isError } = this.props
|
||||
|
||||
if (contextualInfo) {
|
||||
return <SummaryWrapper error={isError}>{contextualInfo}</SummaryWrapper>
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<SummaryWrapperContainer
|
||||
onClick={() => {
|
||||
!this.state.showDetails &&
|
||||
ReactGA.event({
|
||||
category: 'Advanced Interaction',
|
||||
action: 'Open Advanced Details',
|
||||
label: 'Pool Page Details'
|
||||
})
|
||||
this.setState(prevState => {
|
||||
return { showDetails: !prevState.showDetails }
|
||||
})
|
||||
}}
|
||||
>
|
||||
{!this.state.showDetails ? (
|
||||
<>
|
||||
<span>{openDetailsText}</span>
|
||||
<ColoredDropup />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span>{closeDetailsText}</span>
|
||||
<ColoredDropdown />
|
||||
</>
|
||||
)}
|
||||
</SummaryWrapperContainer>
|
||||
{this.renderDetails()}
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default ContextualInfo
|
@ -1,133 +0,0 @@
|
||||
import React, { useState } from 'react'
|
||||
import styled, { css } from 'styled-components'
|
||||
import { transparentize } from 'polished'
|
||||
import ReactGA from 'react-ga'
|
||||
import { ReactComponent as Dropup } from '../../assets/images/dropup-blue.svg'
|
||||
import { ReactComponent as Dropdown } from '../../assets/images/dropdown-blue.svg'
|
||||
|
||||
const SummaryWrapper = styled.div`
|
||||
color: ${({ error, brokenTokenWarning, theme }) => (error || brokenTokenWarning ? theme.salmonRed : theme.doveGray)};
|
||||
font-size: 0.75rem;
|
||||
text-align: center;
|
||||
margin-top: 1rem;
|
||||
padding-top: 1rem;
|
||||
`
|
||||
|
||||
const SummaryWrapperContainer = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap};
|
||||
color: ${({ theme }) => theme.royalBlue};
|
||||
text-align: center;
|
||||
margin-top: 1rem;
|
||||
padding-top: 1rem;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.75rem;
|
||||
|
||||
img {
|
||||
height: 0.75rem;
|
||||
width: 0.75rem;
|
||||
}
|
||||
`
|
||||
|
||||
const Details = styled.div`
|
||||
background-color: ${({ theme }) => theme.concreteGray};
|
||||
/* padding: 1.25rem 1.25rem 1rem 1.25rem; */
|
||||
border-radius: 1rem;
|
||||
font-size: 0.75rem;
|
||||
margin: 1rem 0.5rem 0 0.5rem;
|
||||
`
|
||||
|
||||
const ErrorSpan = styled.span`
|
||||
margin-right: 12px;
|
||||
font-size: 0.75rem;
|
||||
line-height: 0.75rem;
|
||||
|
||||
color: ${({ isError, theme }) => isError && theme.salmonRed};
|
||||
${({ slippageWarning, highSlippageWarning, theme }) =>
|
||||
highSlippageWarning
|
||||
? css`
|
||||
color: ${theme.salmonRed};
|
||||
font-weight: 600;
|
||||
`
|
||||
: slippageWarning &&
|
||||
css`
|
||||
background-color: ${transparentize(0.6, theme.warningYellow)};
|
||||
font-weight: 600;
|
||||
padding: 0.25rem;
|
||||
`}
|
||||
`
|
||||
|
||||
const WrappedDropup = ({ isError, highSlippageWarning, ...rest }) => <Dropup {...rest} />
|
||||
const ColoredDropup = styled(WrappedDropup)`
|
||||
path {
|
||||
stroke: ${({ isError, theme }) => (isError ? theme.salmonRed : theme.royalBlue)};
|
||||
|
||||
${({ highSlippageWarning, theme }) =>
|
||||
highSlippageWarning &&
|
||||
css`
|
||||
stroke: ${theme.salmonRed};
|
||||
`}
|
||||
}
|
||||
`
|
||||
|
||||
const WrappedDropdown = ({ isError, highSlippageWarning, ...rest }) => <Dropdown {...rest} />
|
||||
const ColoredDropdown = styled(WrappedDropdown)`
|
||||
path {
|
||||
stroke: ${({ isError, theme }) => (isError ? theme.salmonRed : theme.royalBlue)};
|
||||
|
||||
${({ highSlippageWarning, theme }) =>
|
||||
highSlippageWarning &&
|
||||
css`
|
||||
stroke: ${theme.salmonRed};
|
||||
`}
|
||||
}
|
||||
`
|
||||
|
||||
export default function ContextualInfo({
|
||||
openDetailsText = 'Advanced Details',
|
||||
closeDetailsText = 'Hide Advanced',
|
||||
contextualInfo = '',
|
||||
allowExpand = false,
|
||||
isError = false,
|
||||
slippageWarning,
|
||||
highSlippageWarning,
|
||||
brokenTokenWarning,
|
||||
dropDownContent
|
||||
}) {
|
||||
const [showDetails, setShowDetails] = useState(false)
|
||||
return !allowExpand ? (
|
||||
<SummaryWrapper brokenTokenWarning={brokenTokenWarning}>{contextualInfo}</SummaryWrapper>
|
||||
) : (
|
||||
<>
|
||||
<SummaryWrapperContainer
|
||||
onClick={() => {
|
||||
!showDetails &&
|
||||
ReactGA.event({
|
||||
category: 'Advanced Interaction',
|
||||
action: 'Open Advanced Details',
|
||||
label: 'Swap/Send Page Details'
|
||||
})
|
||||
setShowDetails(s => !s)
|
||||
}}
|
||||
>
|
||||
<>
|
||||
<ErrorSpan isError={isError} slippageWarning={slippageWarning} highSlippageWarning={highSlippageWarning}>
|
||||
{(slippageWarning || highSlippageWarning) && (
|
||||
<span role="img" aria-label="warning">
|
||||
⚠️
|
||||
</span>
|
||||
)}
|
||||
{contextualInfo ? contextualInfo : showDetails ? closeDetailsText : openDetailsText}
|
||||
</ErrorSpan>
|
||||
{showDetails ? (
|
||||
<ColoredDropup isError={isError} highSlippageWarning={highSlippageWarning} />
|
||||
) : (
|
||||
<ColoredDropdown isError={isError} highSlippageWarning={highSlippageWarning} />
|
||||
)}
|
||||
</>
|
||||
</SummaryWrapperContainer>
|
||||
{showDetails && <Details>{dropDownContent()}</Details>}
|
||||
</>
|
||||
)
|
||||
}
|
@ -8,6 +8,7 @@ import { WETH } from '@uniswap/sdk'
|
||||
import TokenLogo from '../TokenLogo'
|
||||
import DoubleLogo from '../DoubleLogo'
|
||||
import SearchModal from '../SearchModal'
|
||||
import { TYPE } from '../../theme'
|
||||
import { Text } from 'rebass'
|
||||
import { RowBetween } from '../Row'
|
||||
import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg'
|
||||
@ -20,6 +21,8 @@ import { calculateGasMargin } from '../../utils'
|
||||
import { useAddressBalance } from '../../contexts/Balances'
|
||||
import { useTransactionAdder, usePendingApproval } from '../../contexts/Transactions'
|
||||
|
||||
import { ROUTER_ADDRESSES } from '../../constants'
|
||||
|
||||
const GAS_MARGIN = ethers.utils.bigNumberify(1000)
|
||||
|
||||
const SubCurrencySelect = styled.button`
|
||||
@ -51,26 +54,18 @@ const CurrencySelect = styled.button`
|
||||
font-size: 20px;
|
||||
background-color: ${({ selected, theme }) => (selected ? theme.buttonBackgroundPlain : theme.royalBlue)};
|
||||
color: ${({ selected, theme }) => (selected ? theme.textColor : theme.white)};
|
||||
border: 1px solid
|
||||
${({ selected, theme, disableTokenSelect }) =>
|
||||
disableTokenSelect ? theme.buttonBackgroundPlain : selected ? theme.buttonOutlinePlain : theme.royalBlue};
|
||||
border-radius: 8px;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
border: 1px solid
|
||||
${({ selected, theme }) => (selected ? darken(0.1, theme.outlineGrey) : darken(0.1, theme.royalBlue))};
|
||||
|
||||
:focus,
|
||||
:hover {
|
||||
border: 1px solid
|
||||
${({ selected, theme }) => (selected ? darken(0.1, theme.outlineGrey) : darken(0.1, theme.royalBlue))};
|
||||
}
|
||||
|
||||
:focus {
|
||||
border: 1px solid ${({ theme }) => darken(0.1, theme.royalBlue)};
|
||||
}
|
||||
|
||||
:active {
|
||||
background-color: ${({ selected, theme }) =>
|
||||
selected ? darken(0.1, theme.zumthorBlue) : darken(0.1, theme.royalBlue)};
|
||||
${({ selected, theme }) => (selected ? darken(0.2, theme.outlineGrey) : darken(0.2, theme.royalBlue))};
|
||||
}
|
||||
`
|
||||
|
||||
@ -99,12 +94,8 @@ const InputPanel = styled.div`
|
||||
|
||||
const Container = styled.div`
|
||||
border-radius: ${({ hideInput }) => (hideInput ? '8px' : '20px')};
|
||||
border: 1px solid ${({ error, theme }) => (error ? theme.salmonRed : theme.mercuryGray)};
|
||||
|
||||
border: 1px solid ${({ error, theme }) => (error ? theme.salmonRed : theme.backgroundColor)};
|
||||
background-color: ${({ theme }) => theme.inputBackground};
|
||||
:focus-within {
|
||||
border: 1px solid ${({ error, theme }) => (error ? theme.salmonRed : theme.malibuBlue)};
|
||||
}
|
||||
`
|
||||
|
||||
const LabelRow = styled.div`
|
||||
@ -113,7 +104,7 @@ const LabelRow = styled.div`
|
||||
color: ${({ theme }) => theme.doveGray};
|
||||
font-size: 0.75rem;
|
||||
line-height: 1rem;
|
||||
padding: 0.75rem 1rem 0;
|
||||
padding: 0.5rem 1rem 1rem 1rem;
|
||||
span:hover {
|
||||
cursor: pointer;
|
||||
color: ${({ theme }) => darken(0.2, theme.doveGray)};
|
||||
@ -161,12 +152,12 @@ export default function CurrencyInputPanel({
|
||||
value,
|
||||
field,
|
||||
onUserInput,
|
||||
onTokenSelection = null,
|
||||
title,
|
||||
onMax,
|
||||
atMax,
|
||||
error,
|
||||
urlAddedTokens = [], // used
|
||||
onTokenSelection = null,
|
||||
token = null,
|
||||
showUnlock = false, // used to show unlock if approval needed
|
||||
disableUnlock = false,
|
||||
@ -176,23 +167,19 @@ export default function CurrencyInputPanel({
|
||||
exchange = null, // used for double token logo
|
||||
customBalance = null, // used for LP balances instead of token balance
|
||||
hideInput = false,
|
||||
showSendWithSwap = false,
|
||||
onTokenSelectSendWithSwap = null
|
||||
showSendWithSwap = false
|
||||
}) {
|
||||
const { account, chainId } = useWeb3React()
|
||||
const { t } = useTranslation()
|
||||
const addTransaction = useTransactionAdder()
|
||||
const { account, chainId } = useWeb3React()
|
||||
const routerAddress = ROUTER_ADDRESSES[chainId]
|
||||
|
||||
const addTransaction = useTransactionAdder()
|
||||
const [modalOpen, setModalOpen] = useState(false)
|
||||
|
||||
// this one causes the infinite loop
|
||||
const userTokenBalance = useAddressBalance(account, token)
|
||||
|
||||
const tokenContract = useTokenContract(token?.address)
|
||||
const pendingApproval = usePendingApproval(token?.address)
|
||||
|
||||
const routerAddress = '0xd9210Ff5A0780E083BB40e30d005d93a2DcFA4EF'
|
||||
|
||||
function renderUnlockButton() {
|
||||
if (
|
||||
disableUnlock ||
|
||||
@ -240,19 +227,6 @@ export default function CurrencyInputPanel({
|
||||
return (
|
||||
<InputPanel>
|
||||
<Container error={!!error} hideInput={hideInput}>
|
||||
{!hideBalance && (
|
||||
<LabelRow>
|
||||
<RowBetween>
|
||||
<Text>{title}</Text>
|
||||
<ErrorSpan data-tip={'Enter max'} error={!!error} onClick={() => {}}></ErrorSpan>
|
||||
<ClickableText onClick={onMax}>
|
||||
<Text>
|
||||
Balance: {customBalance ? customBalance?.toSignificant(4) : userTokenBalance?.toSignificant(4)}
|
||||
</Text>
|
||||
</ClickableText>
|
||||
</RowBetween>
|
||||
</LabelRow>
|
||||
)}
|
||||
<InputRow style={hideInput ? { padding: '0', borderRadius: '8px' } : {}} hideInput={hideInput}>
|
||||
{!hideInput && (
|
||||
<>
|
||||
@ -267,7 +241,7 @@ export default function CurrencyInputPanel({
|
||||
</>
|
||||
)}
|
||||
<CurrencySelect
|
||||
selected={!!token?.address}
|
||||
selected={!!token}
|
||||
onClick={() => {
|
||||
if (!disableTokenSelect) {
|
||||
setModalOpen(true)
|
||||
@ -292,6 +266,19 @@ export default function CurrencyInputPanel({
|
||||
</Aligner>
|
||||
</CurrencySelect>
|
||||
</InputRow>
|
||||
{!hideBalance && !!token && (
|
||||
<LabelRow>
|
||||
<RowBetween>
|
||||
<Text>{'-'}</Text>
|
||||
<ErrorSpan data-tip={'Enter max'} error={!!error} onClick={() => {}}></ErrorSpan>
|
||||
<ClickableText onClick={onMax}>
|
||||
<TYPE.body>
|
||||
Balance: {customBalance ? customBalance?.toSignificant(4) : userTokenBalance?.toSignificant(4)}
|
||||
</TYPE.body>
|
||||
</ClickableText>
|
||||
</RowBetween>
|
||||
</LabelRow>
|
||||
)}
|
||||
</Container>
|
||||
{!disableTokenSelect && (
|
||||
<SearchModal
|
||||
@ -304,7 +291,6 @@ export default function CurrencyInputPanel({
|
||||
field={field}
|
||||
onTokenSelect={onTokenSelection}
|
||||
showSendWithSwap={showSendWithSwap}
|
||||
onTokenSelectSendWithSwap={onTokenSelectSendWithSwap}
|
||||
/>
|
||||
)}
|
||||
</InputPanel>
|
||||
|
@ -2,33 +2,34 @@ import React, { useState, useReducer, useCallback, useEffect } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { ethers } from 'ethers'
|
||||
import { parseUnits, parseEther } from '@ethersproject/units'
|
||||
import { WETH, TradeType, Route, Trade, TokenAmount, JSBI } from '@uniswap/sdk'
|
||||
import { WETH, TradeType, Route, Exchange, Trade, TokenAmount, JSBI, Percent } from '@uniswap/sdk'
|
||||
|
||||
import QR from '../../assets/svg/QR.svg'
|
||||
import TokenLogo from '../TokenLogo'
|
||||
import AddressInputPanel from '../AddressInputPanel'
|
||||
import QuestionHelper from '../Question'
|
||||
import NumericalInput from '../NumericalInput'
|
||||
import AdvancedSettings from '../AdvancedSettings'
|
||||
import ConfirmationModal from '../ConfirmationModal'
|
||||
import CurrencyInputPanel from '../CurrencyInputPanel'
|
||||
import { Link } from '../../theme/components'
|
||||
import { Text } from 'rebass'
|
||||
import { TYPE } from '../../theme'
|
||||
import { GreyCard, LightCard } from '../../components/Card'
|
||||
import { ArrowDown, ArrowUp } from 'react-feather'
|
||||
import { ButtonPrimary, ButtonError, ButtonRadio } from '../Button'
|
||||
import { GreyCard, BlueCard } from '../../components/Card'
|
||||
import { AutoColumn, ColumnCenter } from '../../components/Column'
|
||||
import Row, { RowBetween, RowFixed } from '../../components/Row'
|
||||
import { ButtonPrimary, ButtonError } from '../Button'
|
||||
import { RowBetween, RowFixed, AutoRow } from '../../components/Row'
|
||||
|
||||
import { usePopups } from '../../contexts/Application'
|
||||
import { useToken } from '../../contexts/Tokens'
|
||||
import { usePopups } from '../../contexts/Application'
|
||||
import { useExchange } from '../../contexts/Exchanges'
|
||||
import { useWeb3React, useTokenContract } from '../../hooks'
|
||||
import { useAddressBalance } from '../../contexts/Balances'
|
||||
import { useTransactionAdder } from '../../contexts/Transactions'
|
||||
import { useAddressAllowance } from '../../contexts/Allowances'
|
||||
import { useWeb3React, useTokenContract } from '../../hooks'
|
||||
import { useAddressBalance, useAllBalances } from '../../contexts/Balances'
|
||||
|
||||
import { ROUTER_ADDRESSES } from '../../constants'
|
||||
import { getRouterContract, calculateGasMargin, isAddress, getProviderOrSigner } from '../../utils'
|
||||
import { getRouterContract, calculateGasMargin, getProviderOrSigner } from '../../utils'
|
||||
|
||||
const Wrapper = styled.div`
|
||||
position: relative;
|
||||
@ -59,42 +60,11 @@ const ErrorText = styled(Text)`
|
||||
warningHigh ? theme.salmonRed : warningMedium ? theme.warningYellow : theme.textColor};
|
||||
`
|
||||
|
||||
const InputWrapper = styled(RowBetween)`
|
||||
width: 200px;
|
||||
background-color: ${({ theme }) => theme.inputBackground};
|
||||
border-radius: 8px;
|
||||
padding: 4px 8px;
|
||||
border: 1px solid transparent;
|
||||
border: ${({ active, error, theme }) =>
|
||||
error ? '1px solid ' + theme.salmonRed : active ? '1px solid ' + theme.royalBlue : ''};
|
||||
`
|
||||
|
||||
const InputGroup = styled(AutoColumn)`
|
||||
position: relative;
|
||||
padding: 40px 0;
|
||||
`
|
||||
|
||||
const QRWrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid ${({ theme }) => theme.outlineGrey};
|
||||
background: #fbfbfb;
|
||||
padding: 4px;
|
||||
border-radius: 8px;
|
||||
`
|
||||
|
||||
const StyledInput = styled.input`
|
||||
width: ${({ width }) => width};
|
||||
border: none;
|
||||
outline: none;
|
||||
font-size: 20px;
|
||||
|
||||
::placeholder {
|
||||
color: #edeef2;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledNumerical = styled(NumericalInput)`
|
||||
text-align: center;
|
||||
font-size: 48px;
|
||||
@ -227,13 +197,6 @@ function hex(value: JSBI) {
|
||||
return ethers.utils.bigNumberify(value.toString())
|
||||
}
|
||||
|
||||
const SLIPPAGE_INDEX = {
|
||||
1: 1,
|
||||
2: 2,
|
||||
3: 3,
|
||||
4: 4
|
||||
}
|
||||
|
||||
const SWAP_TYPE = {
|
||||
EXACT_TOKENS_FOR_TOKENS: 'EXACT_TOKENS_FOR_TOKENS',
|
||||
EXACT_TOKENS_FOR_ETH: 'EXACT_TOKENS_FOR_ETH',
|
||||
@ -257,52 +220,50 @@ const ALLOWED_SLIPPAGE_HIGH = 500
|
||||
|
||||
export default function ExchangePage({ sendingInput = false }) {
|
||||
const { chainId, account, library } = useWeb3React()
|
||||
const routerAddress = ROUTER_ADDRESSES[chainId]
|
||||
const routerAddress: string = ROUTER_ADDRESSES[chainId]
|
||||
|
||||
// adding notifications on txns
|
||||
const [, addPopup] = usePopups()
|
||||
const addTransaction = useTransactionAdder()
|
||||
|
||||
// sending state
|
||||
const [sending, setSending] = useState(sendingInput)
|
||||
const [sendingWithSwap, setSendingWithSwap] = useState(false)
|
||||
const [recipient, setRecipient] = useState('')
|
||||
const [sending] = useState<boolean>(sendingInput)
|
||||
const [sendingWithSwap, setSendingWithSwap] = useState<boolean>(false)
|
||||
const [recipient, setRecipient] = useState<string>('')
|
||||
|
||||
// input details
|
||||
// trade details
|
||||
const [state, dispatch] = useReducer(reducer, WETH[chainId].address, initializeSwapState)
|
||||
const { independentField, typedValue, ...fieldData } = state
|
||||
const dependentField = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
|
||||
const tradeType = independentField === Field.INPUT ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT
|
||||
const [tradeError, setTradeError] = useState('') // error for things like reserve size or route
|
||||
const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
|
||||
const tradeType: TradeType = independentField === Field.INPUT ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT
|
||||
const [tradeError, setTradeError] = useState<string>('') // error for things like reserve size or route
|
||||
|
||||
const tokens = {
|
||||
[Field.INPUT]: useToken(fieldData[Field.INPUT].address),
|
||||
[Field.OUTPUT]: useToken(fieldData[Field.OUTPUT].address)
|
||||
}
|
||||
|
||||
const exchange = useExchange(tokens[Field.INPUT], tokens[Field.OUTPUT])
|
||||
const route = !!exchange ? new Route([exchange], tokens[Field.INPUT]) : undefined
|
||||
const exchange: Exchange = useExchange(tokens[Field.INPUT], tokens[Field.OUTPUT])
|
||||
const route: Route = !!exchange ? new Route([exchange], tokens[Field.INPUT]) : undefined
|
||||
const emptyReserves: boolean = exchange && JSBI.equal(JSBI.BigInt(0), exchange.reserve0.raw)
|
||||
|
||||
// modal and loading
|
||||
const [showConfirm, setShowConfirm] = useState(false)
|
||||
const [pendingConfirmation, setPendingConfirmation] = useState(true) // waiting for user confirmation
|
||||
const [attemptingTxn, setAttemptingTxn] = useState(false) // clicked confirmed
|
||||
|
||||
// advanced settings
|
||||
const [showAdvanced, setShowAdvanced] = useState(false)
|
||||
const [activeIndex, setActiveIndex] = useState(SLIPPAGE_INDEX[3])
|
||||
const [customSlippage, setCustomSlippage] = useState()
|
||||
const [customDeadline, setCustomDeadline] = useState(DEFAULT_DEADLINE_FROM_NOW / 60)
|
||||
const [slippageInputError, setSlippageInputError] = useState(null)
|
||||
const [showConfirm, setShowConfirm] = useState<boolean>(false)
|
||||
const [showAdvanced, setShowAdvanced] = useState<boolean>(false)
|
||||
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // clicked confirmed
|
||||
const [pendingConfirmation, setPendingConfirmation] = useState<boolean>(true) // waiting for user confirmation
|
||||
|
||||
// txn values
|
||||
const [txHash, setTxHash] = useState()
|
||||
const [deadline, setDeadline] = useState(DEFAULT_DEADLINE_FROM_NOW)
|
||||
const [allowedSlippage, setAllowedSlippage] = useState(INITIAL_ALLOWED_SLIPPAGE)
|
||||
const [txHash, setTxHash] = useState<string>('')
|
||||
const [deadline, setDeadline] = useState<number>(DEFAULT_DEADLINE_FROM_NOW)
|
||||
const [allowedSlippage, setAllowedSlippage] = useState<number>(INITIAL_ALLOWED_SLIPPAGE)
|
||||
|
||||
// approvals
|
||||
const inputApproval = useAddressAllowance(account, tokens[Field.INPUT], routerAddress)
|
||||
const outputApproval = useAddressAllowance(account, tokens[Field.OUTPUT], routerAddress)
|
||||
const inputApproval: TokenAmount = useAddressAllowance(account, tokens[Field.INPUT], routerAddress)
|
||||
const outputApproval: TokenAmount = useAddressAllowance(account, tokens[Field.OUTPUT], routerAddress)
|
||||
|
||||
// all balances for detecting a swap with send
|
||||
const allBalances: TokenAmount[] = useAllBalances()
|
||||
|
||||
// get user- and token-specific lookup data
|
||||
const userBalances = {
|
||||
@ -311,18 +272,13 @@ export default function ExchangePage({ sendingInput = false }) {
|
||||
}
|
||||
|
||||
const parsedAmounts: { [field: number]: TokenAmount } = {}
|
||||
// try to parse typed value
|
||||
if (typedValue !== '' && typedValue !== '.' && tokens[independentField]) {
|
||||
try {
|
||||
const typedValueParsed = parseUnits(typedValue, tokens[independentField].decimals).toString()
|
||||
if (typedValueParsed !== '0')
|
||||
parsedAmounts[independentField] = new TokenAmount(tokens[independentField], typedValueParsed)
|
||||
} catch (error) {
|
||||
// should only fail if the user specifies too many decimal places of precision (or maybe exceed max uint?)
|
||||
/**
|
||||
* @todo reserve limit error here
|
||||
*/
|
||||
console.error('found error here ')
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
@ -335,12 +291,11 @@ export default function ExchangePage({ sendingInput = false }) {
|
||||
: undefined
|
||||
} catch (error) {}
|
||||
|
||||
const slippageFromTrade = trade && trade.slippage
|
||||
const slippageFromTrade: Percent = trade && trade.slippage
|
||||
|
||||
if (trade)
|
||||
parsedAmounts[dependentField] = tradeType === TradeType.EXACT_INPUT ? trade.outputAmount : trade.inputAmount
|
||||
|
||||
// get formatted amounts
|
||||
const formattedAmounts = {
|
||||
[independentField]: typedValue,
|
||||
[dependentField]: parsedAmounts[dependentField] ? parsedAmounts[dependentField].toSignificant(8) : ''
|
||||
@ -384,8 +339,8 @@ export default function ExchangePage({ sendingInput = false }) {
|
||||
})
|
||||
}, [])
|
||||
|
||||
const MIN_ETHER = chainId && new TokenAmount(WETH[chainId], JSBI.BigInt(parseEther('.01')))
|
||||
const maxAmountInput =
|
||||
const MIN_ETHER: TokenAmount = chainId && new TokenAmount(WETH[chainId], JSBI.BigInt(parseEther('.01')))
|
||||
const maxAmountInput: TokenAmount =
|
||||
!!userBalances[Field.INPUT] &&
|
||||
JSBI.greaterThan(
|
||||
userBalances[Field.INPUT].raw,
|
||||
@ -395,22 +350,22 @@ export default function ExchangePage({ sendingInput = false }) {
|
||||
? userBalances[Field.INPUT].subtract(MIN_ETHER)
|
||||
: userBalances[Field.INPUT]
|
||||
: undefined
|
||||
const atMaxAmountInput =
|
||||
const atMaxAmountInput: boolean =
|
||||
!!maxAmountInput && !!parsedAmounts[Field.INPUT]
|
||||
? JSBI.equal(maxAmountInput.raw, parsedAmounts[Field.INPUT].raw)
|
||||
: undefined
|
||||
|
||||
const maxAmountOutput =
|
||||
const maxAmountOutput: TokenAmount =
|
||||
!!userBalances[Field.OUTPUT] && JSBI.greaterThan(userBalances[Field.OUTPUT].raw, JSBI.BigInt(0))
|
||||
? userBalances[Field.OUTPUT]
|
||||
: undefined
|
||||
|
||||
const atMaxAmountOutput =
|
||||
const atMaxAmountOutput: boolean =
|
||||
!!maxAmountOutput && !!parsedAmounts[Field.OUTPUT]
|
||||
? JSBI.equal(maxAmountOutput.raw, parsedAmounts[Field.OUTPUT].raw)
|
||||
: undefined
|
||||
|
||||
function getSwapType() {
|
||||
function getSwapType(): string {
|
||||
if (tradeType === TradeType.EXACT_INPUT) {
|
||||
if (tokens[Field.INPUT] === WETH[chainId]) {
|
||||
return SWAP_TYPE.EXACT_ETH_FOR_TOKENS
|
||||
@ -438,7 +393,7 @@ export default function ExchangePage({ sendingInput = false }) {
|
||||
return null
|
||||
}
|
||||
|
||||
const slippageAdjustedAmounts = {
|
||||
const slippageAdjustedAmounts: { [field: number]: TokenAmount } = {
|
||||
[Field.INPUT]:
|
||||
Field.INPUT === independentField
|
||||
? parsedAmounts[Field.INPUT]
|
||||
@ -451,37 +406,15 @@ export default function ExchangePage({ sendingInput = false }) {
|
||||
new TokenAmount(tokens[Field.INPUT], calculateSlippageAmount(parsedAmounts[Field.OUTPUT])?.[0])
|
||||
}
|
||||
|
||||
const showInputUnlock =
|
||||
const showInputUnlock: boolean =
|
||||
parsedAmounts[Field.INPUT] && inputApproval && JSBI.greaterThan(parsedAmounts[Field.INPUT].raw, inputApproval.raw)
|
||||
|
||||
const showOutputUnlock =
|
||||
const showOutputUnlock: boolean =
|
||||
parsedAmounts[Field.OUTPUT] &&
|
||||
outputApproval &&
|
||||
JSBI.greaterThan(parsedAmounts[Field.OUTPUT].raw, outputApproval.raw)
|
||||
|
||||
// parse the input for custom slippage
|
||||
function parseCustomInput(val) {
|
||||
const acceptableValues = [/^$/, /^\d{1,2}$/, /^\d{0,2}\.\d{0,2}$/]
|
||||
if (val > 5) {
|
||||
setSlippageInputError('Your transaction may be front-run.')
|
||||
} else {
|
||||
setSlippageInputError(null)
|
||||
}
|
||||
if (acceptableValues.some(a => a.test(val))) {
|
||||
setCustomSlippage(val)
|
||||
setAllowedSlippage(val * 100)
|
||||
}
|
||||
}
|
||||
|
||||
function parseCustomDeadline(val) {
|
||||
const acceptableValues = [/^$/, /^\d+$/]
|
||||
if (acceptableValues.some(re => re.test(val))) {
|
||||
setCustomDeadline(val)
|
||||
setDeadline(val * 60)
|
||||
}
|
||||
}
|
||||
|
||||
const tokenContract = useTokenContract(tokens[Field.INPUT]?.address)
|
||||
const tokenContract: ethers.Contract = useTokenContract(tokens[Field.INPUT]?.address)
|
||||
|
||||
// function for a pure send
|
||||
async function onSend() {
|
||||
@ -494,7 +427,6 @@ export default function ExchangePage({ sendingInput = false }) {
|
||||
signer
|
||||
.sendTransaction({ to: recipient.toString(), value: hex(parsedAmounts[Field.INPUT].raw) })
|
||||
.then(response => {
|
||||
console.log(response)
|
||||
setTxHash(response.hash)
|
||||
addTransaction(response)
|
||||
setPendingConfirmation(false)
|
||||
@ -537,17 +469,17 @@ export default function ExchangePage({ sendingInput = false }) {
|
||||
}
|
||||
}
|
||||
|
||||
// covers swap or swap with send
|
||||
async function onSwap() {
|
||||
const routerContract = getRouterContract(chainId, library, account)
|
||||
setAttemptingTxn(true)
|
||||
const routerContract: ethers.Contract = getRouterContract(chainId, library, account)
|
||||
|
||||
setAttemptingTxn(true) // mark that user is attempting transaction
|
||||
|
||||
const path = Object.keys(route.path).map(key => {
|
||||
return route.path[key].address
|
||||
})
|
||||
|
||||
let estimate: Function, method: Function, args, value
|
||||
|
||||
const deadlineFromNow = Math.ceil(Date.now() / 1000) + deadline
|
||||
let estimate: Function, method: Function, args: any[], value: ethers.utils.BigNumber
|
||||
const deadlineFromNow: number = Math.ceil(Date.now() / 1000) + deadline
|
||||
|
||||
const swapType = getSwapType()
|
||||
switch (swapType) {
|
||||
@ -558,7 +490,7 @@ export default function ExchangePage({ sendingInput = false }) {
|
||||
slippageAdjustedAmounts[Field.INPUT].raw.toString(),
|
||||
slippageAdjustedAmounts[Field.OUTPUT].raw.toString(),
|
||||
path,
|
||||
account,
|
||||
sending ? recipient : account,
|
||||
deadlineFromNow
|
||||
]
|
||||
value = ethers.constants.Zero
|
||||
@ -570,7 +502,7 @@ export default function ExchangePage({ sendingInput = false }) {
|
||||
slippageAdjustedAmounts[Field.OUTPUT].raw.toString(),
|
||||
slippageAdjustedAmounts[Field.INPUT].raw.toString(),
|
||||
path,
|
||||
account,
|
||||
sending ? recipient : account,
|
||||
deadlineFromNow
|
||||
]
|
||||
value = ethers.constants.Zero
|
||||
@ -578,7 +510,12 @@ export default function ExchangePage({ sendingInput = false }) {
|
||||
case SWAP_TYPE.EXACT_ETH_FOR_TOKENS:
|
||||
estimate = routerContract.estimate.swapExactETHForTokens
|
||||
method = routerContract.swapExactETHForTokens
|
||||
args = [slippageAdjustedAmounts[Field.OUTPUT].raw.toString(), path, account, deadlineFromNow]
|
||||
args = [
|
||||
slippageAdjustedAmounts[Field.OUTPUT].raw.toString(),
|
||||
path,
|
||||
sending ? recipient : account,
|
||||
deadlineFromNow
|
||||
]
|
||||
value = hex(slippageAdjustedAmounts[Field.INPUT].raw)
|
||||
break
|
||||
case SWAP_TYPE.TOKENS_FOR_EXACT_ETH:
|
||||
@ -588,7 +525,7 @@ export default function ExchangePage({ sendingInput = false }) {
|
||||
slippageAdjustedAmounts[Field.OUTPUT].raw.toString(),
|
||||
slippageAdjustedAmounts[Field.INPUT].raw.toString(),
|
||||
path,
|
||||
account,
|
||||
sending ? recipient : account,
|
||||
deadlineFromNow
|
||||
]
|
||||
value = ethers.constants.Zero
|
||||
@ -600,7 +537,7 @@ export default function ExchangePage({ sendingInput = false }) {
|
||||
slippageAdjustedAmounts[Field.INPUT].raw.toString(),
|
||||
slippageAdjustedAmounts[Field.OUTPUT].raw.toString(),
|
||||
path,
|
||||
account,
|
||||
sending ? recipient : account,
|
||||
deadlineFromNow
|
||||
]
|
||||
value = ethers.constants.Zero
|
||||
@ -608,13 +545,18 @@ export default function ExchangePage({ sendingInput = false }) {
|
||||
case SWAP_TYPE.ETH_FOR_EXACT_TOKENS:
|
||||
estimate = routerContract.estimate.swapETHForExactTokens
|
||||
method = routerContract.swapETHForExactTokens
|
||||
args = [slippageAdjustedAmounts[Field.OUTPUT].raw.toString(), path, account, deadlineFromNow]
|
||||
args = [
|
||||
slippageAdjustedAmounts[Field.OUTPUT].raw.toString(),
|
||||
path,
|
||||
sending ? recipient : account,
|
||||
deadlineFromNow
|
||||
]
|
||||
value = hex(slippageAdjustedAmounts[Field.INPUT].raw)
|
||||
break
|
||||
}
|
||||
|
||||
const estimatedGasLimit = await estimate(...args, { value }).catch(e => {
|
||||
console.log('error getting gas limit')
|
||||
console.log(e)
|
||||
})
|
||||
|
||||
method(...args, {
|
||||
@ -626,7 +568,7 @@ export default function ExchangePage({ sendingInput = false }) {
|
||||
addTransaction(response)
|
||||
setPendingConfirmation(false)
|
||||
})
|
||||
.catch(e => {
|
||||
.catch(() => {
|
||||
addPopup(
|
||||
<AutoColumn gap="10px">
|
||||
<Text>Transaction Failed: try again.</Text>
|
||||
@ -638,13 +580,13 @@ export default function ExchangePage({ sendingInput = false }) {
|
||||
}
|
||||
|
||||
// errors
|
||||
const [generalError, setGeneralError] = useState('')
|
||||
const [inputError, setInputError] = useState('')
|
||||
const [outputError, setOutputError] = useState('')
|
||||
const [recipientError, setRecipientError] = useState('')
|
||||
const [isValid, setIsValid] = useState(false)
|
||||
const [generalError, setGeneralError] = useState<string>('')
|
||||
const [inputError, setInputError] = useState<string>('')
|
||||
const [outputError, setOutputError] = useState<string>('')
|
||||
const [recipientError, setRecipientError] = useState<string>('')
|
||||
const [isValid, setIsValid] = useState<boolean>(false)
|
||||
|
||||
const ignoreOutput = sending ? !sendingWithSwap : false
|
||||
const ignoreOutput: boolean = sending ? !sendingWithSwap : false
|
||||
|
||||
useEffect(() => {
|
||||
// reset errors
|
||||
@ -652,11 +594,9 @@ export default function ExchangePage({ sendingInput = false }) {
|
||||
setInputError(null)
|
||||
setOutputError(null)
|
||||
setTradeError(null)
|
||||
setRecipientError(null)
|
||||
setIsValid(true)
|
||||
|
||||
if (!isAddress(recipient) && sending) {
|
||||
setRecipientError('Invalid Recipient')
|
||||
if (recipientError) {
|
||||
setIsValid(false)
|
||||
}
|
||||
|
||||
@ -675,7 +615,7 @@ export default function ExchangePage({ sendingInput = false }) {
|
||||
exchange &&
|
||||
JSBI.greaterThan(parsedAmounts[Field.INPUT].raw, exchange.reserveOf(tokens[Field.INPUT]).raw)
|
||||
) {
|
||||
setTradeError('Low Liquidity Error')
|
||||
setTradeError('Insufficient Liquidity')
|
||||
setIsValid(false)
|
||||
}
|
||||
|
||||
@ -685,7 +625,7 @@ export default function ExchangePage({ sendingInput = false }) {
|
||||
exchange &&
|
||||
JSBI.greaterThan(parsedAmounts[Field.OUTPUT].raw, exchange.reserveOf(tokens[Field.OUTPUT]).raw)
|
||||
) {
|
||||
setTradeError('Low Liquidity Error')
|
||||
setTradeError('Insufficient Liquidity')
|
||||
setIsValid(false)
|
||||
}
|
||||
|
||||
@ -712,6 +652,7 @@ export default function ExchangePage({ sendingInput = false }) {
|
||||
ignoreOutput,
|
||||
parsedAmounts,
|
||||
recipient,
|
||||
recipientError,
|
||||
sending,
|
||||
sendingWithSwap,
|
||||
showInputUnlock,
|
||||
@ -721,9 +662,12 @@ export default function ExchangePage({ sendingInput = false }) {
|
||||
])
|
||||
|
||||
// warnings on slippage
|
||||
const warningMedium = slippageFromTrade && parseFloat(slippageFromTrade.toFixed(4)) > ALLOWED_SLIPPAGE_MEDIUM / 100
|
||||
const warningHigh = slippageFromTrade && parseFloat(slippageFromTrade.toFixed(4)) > ALLOWED_SLIPPAGE_HIGH / 100
|
||||
const warningMedium: boolean =
|
||||
slippageFromTrade && parseFloat(slippageFromTrade.toFixed(4)) > ALLOWED_SLIPPAGE_MEDIUM / 100
|
||||
const warningHigh: boolean =
|
||||
slippageFromTrade && parseFloat(slippageFromTrade.toFixed(4)) > ALLOWED_SLIPPAGE_HIGH / 100
|
||||
|
||||
// reset modal state when closed
|
||||
function resetModal() {
|
||||
setPendingConfirmation(true)
|
||||
setAttemptingTxn(false)
|
||||
@ -736,11 +680,11 @@ export default function ExchangePage({ sendingInput = false }) {
|
||||
<AutoColumn gap="30px" style={{ marginTop: '40px' }}>
|
||||
<RowBetween>
|
||||
<Text fontSize={36} fontWeight={500}>
|
||||
{parsedAmounts[Field.INPUT]?.toFixed(8)}
|
||||
{parsedAmounts[Field.INPUT]?.toFixed(8)} {tokens[Field.INPUT]?.symbol}
|
||||
</Text>
|
||||
<TokenLogo address={tokens[Field.INPUT]?.address} size={'30px'} />
|
||||
</RowBetween>
|
||||
<ArrowDown size={24} color="#888D9B" />
|
||||
<TYPE.darkGray fontSize={20}>To</TYPE.darkGray>
|
||||
<TYPE.blue fontSize={36}>
|
||||
{recipient?.slice(0, 6)}...{recipient?.slice(36, 42)}
|
||||
</TYPE.blue>
|
||||
@ -749,6 +693,27 @@ export default function ExchangePage({ sendingInput = false }) {
|
||||
}
|
||||
|
||||
if (sending && sendingWithSwap) {
|
||||
return (
|
||||
<AutoColumn gap="30px" style={{ marginTop: '40px' }}>
|
||||
<AutoColumn gap="10px">
|
||||
<AutoRow gap="10px">
|
||||
<TokenLogo address={tokens[Field.OUTPUT]?.address} size={'30px'} />
|
||||
<Text fontSize={36} fontWeight={500}>
|
||||
{slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(4)} {tokens[Field.OUTPUT]?.symbol}
|
||||
</Text>
|
||||
</AutoRow>
|
||||
<BlueCard>
|
||||
Via {parsedAmounts[Field.INPUT]?.toSignificant(4)} {tokens[Field.INPUT]?.symbol} swap
|
||||
</BlueCard>
|
||||
</AutoColumn>
|
||||
<AutoColumn gap="10px">
|
||||
<TYPE.darkGray fontSize={20}>To</TYPE.darkGray>
|
||||
<TYPE.blue fontSize={36}>
|
||||
{recipient?.slice(0, 6)}...{recipient?.slice(36, 42)}
|
||||
</TYPE.blue>
|
||||
</AutoColumn>
|
||||
</AutoColumn>
|
||||
)
|
||||
}
|
||||
|
||||
if (!sending) {
|
||||
@ -796,119 +761,29 @@ export default function ExchangePage({ sendingInput = false }) {
|
||||
</AutoColumn>
|
||||
)
|
||||
}
|
||||
|
||||
if (sending && sendingWithSwap) {
|
||||
}
|
||||
|
||||
if (showAdvanced) {
|
||||
return (
|
||||
<AutoColumn gap="20px">
|
||||
<Link
|
||||
onClick={() => {
|
||||
setShowAdvanced(false)
|
||||
}}
|
||||
>
|
||||
back
|
||||
</Link>
|
||||
<RowBetween>
|
||||
<TYPE.main>Limit additional price impact</TYPE.main>
|
||||
<QuestionHelper text="" />
|
||||
</RowBetween>
|
||||
<Row>
|
||||
<ButtonRadio
|
||||
active={SLIPPAGE_INDEX[1] === activeIndex}
|
||||
padding="4px 6px"
|
||||
borderRadius="8px"
|
||||
style={{ marginRight: '16px' }}
|
||||
width={'60px'}
|
||||
onClick={() => {
|
||||
setActiveIndex(SLIPPAGE_INDEX[1])
|
||||
setAllowedSlippage(10)
|
||||
}}
|
||||
>
|
||||
0.1%
|
||||
</ButtonRadio>
|
||||
<ButtonRadio
|
||||
active={SLIPPAGE_INDEX[2] === activeIndex}
|
||||
padding="4px 6px"
|
||||
borderRadius="8px"
|
||||
style={{ marginRight: '16px' }}
|
||||
width={'60px'}
|
||||
onClick={() => {
|
||||
setActiveIndex(SLIPPAGE_INDEX[2])
|
||||
setAllowedSlippage(100)
|
||||
}}
|
||||
>
|
||||
1%
|
||||
</ButtonRadio>
|
||||
<ButtonRadio
|
||||
active={SLIPPAGE_INDEX[3] === activeIndex}
|
||||
padding="4px"
|
||||
borderRadius="8px"
|
||||
width={'140px'}
|
||||
onClick={() => {
|
||||
setActiveIndex(SLIPPAGE_INDEX[3])
|
||||
setAllowedSlippage(200)
|
||||
}}
|
||||
>
|
||||
2% (suggested)
|
||||
</ButtonRadio>
|
||||
</Row>
|
||||
<RowFixed>
|
||||
<InputWrapper active={SLIPPAGE_INDEX[4] === activeIndex} error={slippageInputError}>
|
||||
<NumericalInput
|
||||
align={customSlippage ? 'right' : 'left'}
|
||||
value={customSlippage || ''}
|
||||
onUserInput={val => {
|
||||
parseCustomInput(val)
|
||||
setActiveIndex(SLIPPAGE_INDEX[4])
|
||||
}}
|
||||
placeHolder="Custom"
|
||||
onClick={() => {
|
||||
setActiveIndex(SLIPPAGE_INDEX[4])
|
||||
if (customSlippage) {
|
||||
parseCustomInput(customSlippage)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
%
|
||||
</InputWrapper>
|
||||
{slippageInputError && (
|
||||
<TYPE.error error={true} fontSize={12} style={{ marginLeft: '10px' }}>
|
||||
Your transaction may be front-run
|
||||
</TYPE.error>
|
||||
)}
|
||||
</RowFixed>
|
||||
<RowBetween>
|
||||
<TYPE.main>Adjust deadline (minutes from now)</TYPE.main>
|
||||
</RowBetween>
|
||||
<RowFixed>
|
||||
<InputWrapper>
|
||||
<NumericalInput
|
||||
value={customDeadline}
|
||||
onUserInput={val => {
|
||||
parseCustomDeadline(val)
|
||||
}}
|
||||
/>
|
||||
</InputWrapper>
|
||||
</RowFixed>
|
||||
</AutoColumn>
|
||||
<AdvancedSettings
|
||||
setIsOpen={setShowAdvanced}
|
||||
setDeadline={setDeadline}
|
||||
setAllowedSlippage={setAllowedSlippage}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (!sending) {
|
||||
if (!sending || (sending && sendingWithSwap)) {
|
||||
return (
|
||||
<>
|
||||
<RowBetween>
|
||||
<Text color="#565A69" fontWeight={500} fontSize={16}>
|
||||
Price
|
||||
</Text>
|
||||
<Text fontWeight={500} fontSize={16}>
|
||||
{`1 ${tokens[Field.INPUT]?.symbol} = ${route && route.midPrice && route.midPrice.adjusted.toFixed(8)} ${
|
||||
tokens[Field.OUTPUT]?.symbol
|
||||
}`}
|
||||
</Text>
|
||||
</RowBetween>
|
||||
{route && route.midPrice && !emptyReserves && (
|
||||
<RowBetween>
|
||||
<Text color="#565A69" fontWeight={500} fontSize={16}>
|
||||
Price
|
||||
</Text>
|
||||
<Text fontWeight={500} fontSize={16}>
|
||||
{`1 ${tokens[Field.INPUT]?.symbol} = ${route.midPrice.toFixed(6)} ${tokens[Field.OUTPUT]?.symbol}`}
|
||||
</Text>
|
||||
</RowBetween>
|
||||
)}
|
||||
<RowBetween>
|
||||
<Text color="#565A69" fontWeight={500} fontSize={16}>
|
||||
Slippage <Link onClick={() => setShowAdvanced(true)}>(edit limits)</Link>
|
||||
@ -919,7 +794,7 @@ export default function ExchangePage({ sendingInput = false }) {
|
||||
</RowBetween>
|
||||
<ButtonError onClick={onSwap} error={!!warningHigh} style={{ margin: '10px 0' }}>
|
||||
<Text fontSize={20} fontWeight={500}>
|
||||
{warningHigh ? 'Swap Anyway' : 'Swap'}
|
||||
{warningHigh ? (sending ? 'Send Anyway' : 'Swap Anyway') : sending ? 'Confirm Send' : 'Confirm Swap'}
|
||||
</Text>
|
||||
</ButtonError>
|
||||
<AutoColumn justify="center" gap="20px">
|
||||
@ -933,7 +808,7 @@ export default function ExchangePage({ sendingInput = false }) {
|
||||
setShowAdvanced(true)
|
||||
}}
|
||||
>
|
||||
Advanced Options
|
||||
Advanced
|
||||
</Link>
|
||||
</AutoColumn>
|
||||
</>
|
||||
@ -941,27 +816,48 @@ export default function ExchangePage({ sendingInput = false }) {
|
||||
}
|
||||
}
|
||||
|
||||
const pendingText = sending
|
||||
? `Sending ${parsedAmounts[Field.INPUT]?.toSignificant(6)} ${tokens[Field.INPUT]?.symbol} to ${recipient}`
|
||||
// text to show while loading
|
||||
const pendingText: string = sending
|
||||
? sendingWithSwap
|
||||
? `Sending ${parsedAmounts[Field.OUTPUT]?.toSignificant(6)} ${tokens[Field.OUTPUT]?.symbol} to ${recipient}`
|
||||
: `Sending ${parsedAmounts[Field.INPUT]?.toSignificant(6)} ${tokens[Field.INPUT]?.symbol} to ${recipient}`
|
||||
: ` Swapped ${parsedAmounts[Field.INPUT]?.toSignificant(6)} ${tokens[Field.INPUT]?.symbol} for ${parsedAmounts[
|
||||
Field.OUTPUT
|
||||
]?.toSignificant(6)} ${tokens[Field.OUTPUT]?.symbol}`
|
||||
|
||||
function _onTokenSelect(address: string) {
|
||||
const balance = allBalances?.[account]?.[address]
|
||||
// if no user balance - switch view to a send with swap
|
||||
const hasBalance = balance && JSBI.greaterThan(JSBI.BigInt(0), balance.raw)
|
||||
if (!hasBalance && sending) {
|
||||
onTokenSelection(Field.OUTPUT, address)
|
||||
setSendingWithSwap(true)
|
||||
} else {
|
||||
onTokenSelection(Field.INPUT, address)
|
||||
}
|
||||
}
|
||||
|
||||
function _onRecipient(result) {
|
||||
if (result.address) {
|
||||
setRecipient(result.address)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<ConfirmationModal
|
||||
isOpen={showConfirm}
|
||||
title={sendingWithSwap ? 'Confirm swap and send' : sending ? 'Confirm Send' : 'Confirm Swap'}
|
||||
onDismiss={() => {
|
||||
resetModal()
|
||||
setShowConfirm(false)
|
||||
}}
|
||||
attemptingTxn={attemptingTxn}
|
||||
pendingConfirmation={pendingConfirmation}
|
||||
hash={txHash ? txHash : ''}
|
||||
hash={txHash}
|
||||
topContent={modalHeader}
|
||||
bottomContent={modalBottom}
|
||||
pendingText={pendingText}
|
||||
title={sendingWithSwap ? 'Confirm swap and send' : sending ? 'Confirm Send' : 'Confirm Swap'}
|
||||
/>
|
||||
|
||||
{sending && !sendingWithSwap && (
|
||||
@ -987,11 +883,7 @@ export default function ExchangePage({ sendingInput = false }) {
|
||||
}}
|
||||
atMax={atMaxAmountInput}
|
||||
token={tokens[Field.INPUT]}
|
||||
onTokenSelection={address => onTokenSelection(Field.INPUT, address)}
|
||||
onTokenSelectSendWithSwap={address => {
|
||||
onTokenSelection(Field.OUTPUT, address)
|
||||
setSendingWithSwap(true)
|
||||
}}
|
||||
onTokenSelection={address => _onTokenSelect(address)}
|
||||
title={'Input'}
|
||||
error={inputError}
|
||||
exchange={exchange}
|
||||
@ -1010,17 +902,17 @@ export default function ExchangePage({ sendingInput = false }) {
|
||||
<CurrencyInputPanel
|
||||
field={Field.INPUT}
|
||||
value={formattedAmounts[Field.INPUT]}
|
||||
onUserInput={onUserInput}
|
||||
onMax={() => {
|
||||
maxAmountInput && onMaxInput(maxAmountInput.toExact())
|
||||
}}
|
||||
atMax={atMaxAmountInput}
|
||||
token={tokens[Field.INPUT]}
|
||||
onTokenSelection={address => onTokenSelection(Field.INPUT, address)}
|
||||
title={'Input'}
|
||||
error={inputError}
|
||||
exchange={exchange}
|
||||
showUnlock={showInputUnlock}
|
||||
onUserInput={onUserInput}
|
||||
onMax={() => {
|
||||
maxAmountInput && onMaxInput(maxAmountInput.toExact())
|
||||
}}
|
||||
onTokenSelection={address => onTokenSelection(Field.INPUT, address)}
|
||||
/>
|
||||
<ColumnCenter>
|
||||
<ArrowWrapper onClick={onSwapTokens}>
|
||||
@ -1043,18 +935,20 @@ export default function ExchangePage({ sendingInput = false }) {
|
||||
exchange={exchange}
|
||||
showUnlock={showOutputUnlock}
|
||||
/>
|
||||
<RowBetween>
|
||||
<Text fontWeight={500} color="#565A69">
|
||||
Price
|
||||
</Text>
|
||||
<Text fontWeight={500} color="#565A69">
|
||||
{exchange
|
||||
? `1 ${tokens[Field.INPUT].symbol} = ${route?.midPrice.toSignificant(6)} ${
|
||||
tokens[Field.OUTPUT].symbol
|
||||
}`
|
||||
: '-'}
|
||||
</Text>
|
||||
</RowBetween>
|
||||
{!emptyReserves && ( // hide price if new exchange
|
||||
<RowBetween>
|
||||
<Text fontWeight={500} color="#565A69">
|
||||
Price
|
||||
</Text>
|
||||
<Text fontWeight={500} color="#565A69">
|
||||
{exchange
|
||||
? `1 ${tokens[Field.INPUT].symbol} = ${route?.midPrice.toSignificant(6)} ${
|
||||
tokens[Field.OUTPUT].symbol
|
||||
}`
|
||||
: '-'}
|
||||
</Text>
|
||||
</RowBetween>
|
||||
)}
|
||||
{warningMedium && (
|
||||
<RowBetween>
|
||||
<Text fontWeight={500} color="#565A69">
|
||||
@ -1070,39 +964,54 @@ export default function ExchangePage({ sendingInput = false }) {
|
||||
|
||||
{sending && (
|
||||
<AutoColumn gap="10px">
|
||||
<LightCard borderRadius={'20px'}>
|
||||
<RowBetween>
|
||||
<StyledInput placeholder="Recipient Address" onChange={e => setRecipient(e.target.value)} />
|
||||
<QRWrapper>
|
||||
<img src={QR} alt="" />
|
||||
</QRWrapper>
|
||||
</RowBetween>
|
||||
</LightCard>
|
||||
<AddressInputPanel
|
||||
title={''}
|
||||
onChange={_onRecipient}
|
||||
onError={error => {
|
||||
if (error) {
|
||||
setRecipientError('Inavlid Recipient')
|
||||
} else {
|
||||
setRecipientError(null)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</AutoColumn>
|
||||
)}
|
||||
<ButtonError
|
||||
onClick={() => {
|
||||
setShowConfirm(true)
|
||||
}}
|
||||
disabled={!isValid}
|
||||
error={!!warningHigh}
|
||||
>
|
||||
<Text fontSize={20} fontWeight={500}>
|
||||
{generalError
|
||||
? generalError
|
||||
: inputError
|
||||
? inputError
|
||||
: outputError
|
||||
? outputError
|
||||
: recipientError
|
||||
? recipientError
|
||||
: tradeError
|
||||
? tradeError
|
||||
: warningHigh
|
||||
? 'Swap Anyway'
|
||||
: 'Swap'}
|
||||
</Text>
|
||||
</ButtonError>
|
||||
|
||||
{emptyReserves ? (
|
||||
<RowBetween style={{ margin: '10px 0' }}>
|
||||
<TYPE.main>No exchange for this pair.</TYPE.main>
|
||||
<TYPE.blue> Create one now</TYPE.blue>
|
||||
</RowBetween>
|
||||
) : (
|
||||
<ButtonError
|
||||
onClick={() => {
|
||||
setShowConfirm(true)
|
||||
}}
|
||||
disabled={!isValid}
|
||||
error={!!warningHigh}
|
||||
>
|
||||
<Text fontSize={20} fontWeight={500}>
|
||||
{generalError
|
||||
? generalError
|
||||
: inputError
|
||||
? inputError
|
||||
: outputError
|
||||
? outputError
|
||||
: recipientError
|
||||
? recipientError
|
||||
: tradeError
|
||||
? tradeError
|
||||
: warningHigh
|
||||
? sendingWithSwap
|
||||
? 'Send Anyway'
|
||||
: 'Swap Anyway'
|
||||
: sending
|
||||
? 'Send'
|
||||
: 'Swap'}
|
||||
</Text>
|
||||
</ButtonError>
|
||||
)}
|
||||
</AutoColumn>
|
||||
|
||||
{warningHigh && (
|
||||
|
@ -1,15 +1,14 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import Row from '../Row'
|
||||
import Menu from '../Menu'
|
||||
import Logo from '../../assets/svg/logo.svg'
|
||||
import Row from '../Row'
|
||||
import Card from '../Card'
|
||||
import Web3Status from '../Web3Status'
|
||||
import { CloseIcon } from '../../theme/components'
|
||||
import { X } from 'react-feather'
|
||||
import { Link } from '../../theme'
|
||||
import { Text } from 'rebass'
|
||||
import Card from '../Card'
|
||||
import { X } from 'react-feather'
|
||||
|
||||
import { WETH } from '@uniswap/sdk'
|
||||
import { isMobile } from 'react-device-detect'
|
||||
@ -46,9 +45,7 @@ const Title = styled.div`
|
||||
const TitleText = styled.div`
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(119.64deg, #fb1868 -5.55%, #ff00f3 154.46%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
color: ${({ theme }) => theme.black};
|
||||
margin-left: 12px;
|
||||
`
|
||||
|
||||
@ -58,22 +55,21 @@ const AccountElement = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) => theme.outlineGrey};
|
||||
background-color: ${({ theme, active }) => (!active ? theme.white : theme.outlineGrey)};
|
||||
border: 1px solid ${({ theme }) => theme.outlineGrey};
|
||||
border-radius: 8px;
|
||||
padding-left: 8px;
|
||||
padding-left: ${({ active }) => (active ? '8px' : 0)};
|
||||
|
||||
:focus {
|
||||
border: 1px solid blue;
|
||||
}
|
||||
/* width: 100%; */
|
||||
`
|
||||
|
||||
const FixedPopupColumn = styled(AutoColumn)`
|
||||
position: absolute;
|
||||
top: 80px;
|
||||
right: 20px
|
||||
width: 340px;
|
||||
width: 380px;
|
||||
`
|
||||
|
||||
const StyledClose = styled(X)`
|
||||
@ -86,12 +82,10 @@ const StyledClose = styled(X)`
|
||||
`
|
||||
|
||||
const Popup = styled(Card)`
|
||||
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.04), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04),
|
||||
0px 24px 32px rgba(0, 0, 0, 0.04);
|
||||
z-index: 9999;
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
background: ${theme => theme.white};
|
||||
background-color: white;
|
||||
`
|
||||
|
||||
export default function Header() {
|
||||
@ -115,8 +109,8 @@ export default function Header() {
|
||||
</Title>
|
||||
</HeaderElement>
|
||||
<HeaderElement>
|
||||
<AccountElement>
|
||||
{!isMobile ? (
|
||||
<AccountElement active={!!account}>
|
||||
{!isMobile && account ? (
|
||||
<Row style={{ marginRight: '-1.25rem', paddingRight: '1.75rem' }}>
|
||||
<Text fontWeight={500}> {userEthBalance && userEthBalance?.toFixed(4) + ' ETH'}</Text>
|
||||
</Row>
|
||||
|
@ -1,24 +1,24 @@
|
||||
import React, { useState } from 'react'
|
||||
import { JSBI } from '@uniswap/sdk'
|
||||
import { withRouter } from 'react-router-dom'
|
||||
|
||||
import { useToken } from '../../contexts/Tokens'
|
||||
import { useExchange } from '../../contexts/Exchanges'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { useAddressBalance } from '../../contexts/Balances'
|
||||
import { usePopups } from '../../contexts/Application'
|
||||
import { TokenAmount, JSBI, Token, Exchange } from '@uniswap/sdk'
|
||||
|
||||
import Row from '../Row'
|
||||
import TokenLogo from '../TokenLogo'
|
||||
import SearchModal from '../SearchModal'
|
||||
import PositionCard from '../PositionCard'
|
||||
import DoubleTokenLogo from '../DoubleLogo'
|
||||
import { Link } from '../../theme'
|
||||
import { Text } from 'rebass'
|
||||
import { Plus } from 'react-feather'
|
||||
import { LightCard } from '../Card'
|
||||
import Column, { AutoColumn, ColumnCenter } from '../Column'
|
||||
import { AutoColumn, ColumnCenter } from '../Column'
|
||||
import { ButtonPrimary, ButtonDropwdown, ButtonDropwdownLight } from '../Button'
|
||||
import DoubleTokenLogo from '../DoubleLogo'
|
||||
|
||||
import { useToken } from '../../contexts/Tokens'
|
||||
import { usePopups } from '../../contexts/Application'
|
||||
import { useExchange } from '../../contexts/Exchanges'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { useAddressBalance } from '../../contexts/Balances'
|
||||
|
||||
function PoolFinder({ history }) {
|
||||
const Fields = {
|
||||
@ -27,27 +27,25 @@ function PoolFinder({ history }) {
|
||||
}
|
||||
|
||||
const { account } = useWeb3React()
|
||||
const [showSearch, setShowSearch] = useState(false)
|
||||
const [activeField, setActiveField] = useState(Fields.TOKEN0)
|
||||
const [showSearch, setShowSearch] = useState<boolean>(false)
|
||||
const [activeField, setActiveField] = useState<number>(Fields.TOKEN0)
|
||||
|
||||
const [, addPopup] = usePopups()
|
||||
|
||||
const [token0Address, setToken0Address] = useState()
|
||||
const [token1Address, setToken1Address] = useState()
|
||||
const [token0Address, setToken0Address] = useState<string>()
|
||||
const [token1Address, setToken1Address] = useState<string>()
|
||||
|
||||
const token0 = useToken(token0Address)
|
||||
const token1 = useToken(token1Address)
|
||||
const token0: Token = useToken(token0Address)
|
||||
const token1: Token = useToken(token1Address)
|
||||
|
||||
const exchange = useExchange(token0, token1)
|
||||
const exchange: Exchange = useExchange(token0, token1)
|
||||
const position: TokenAmount = useAddressBalance(account, exchange?.liquidityToken)
|
||||
|
||||
const position = useAddressBalance(account, exchange?.liquidityToken)
|
||||
|
||||
const newExchange = exchange && JSBI.equal(exchange.reserve0.raw, JSBI.BigInt(0))
|
||||
|
||||
const allowImport = position && JSBI.greaterThan(position.raw, JSBI.BigInt(0))
|
||||
const newExchange: boolean = exchange && JSBI.equal(exchange.reserve0.raw, JSBI.BigInt(0))
|
||||
const allowImport: boolean = position && JSBI.greaterThan(position.raw, JSBI.BigInt(0))
|
||||
|
||||
function endSearch() {
|
||||
history.goBack()
|
||||
history.goBack() // return to previous page
|
||||
addPopup(
|
||||
<Row>
|
||||
<DoubleTokenLogo a0={token0Address || ''} a1={token1Address || ''} margin={true} />
|
@ -2,12 +2,11 @@ import React, { useState, useRef, useMemo, useEffect } from 'react'
|
||||
import '@reach/tooltip/styles.css'
|
||||
import styled from 'styled-components'
|
||||
import escapeStringRegex from 'escape-string-regexp'
|
||||
import { JSBI } from '@uniswap/sdk'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { ethers } from 'ethers'
|
||||
import { isMobile } from 'react-device-detect'
|
||||
import { withRouter } from 'react-router-dom'
|
||||
import { JSBI } from '@uniswap/sdk'
|
||||
|
||||
import { Link as StyledLink } from '../../theme/components'
|
||||
|
||||
import Modal from '../Modal'
|
||||
@ -107,11 +106,17 @@ const PaddedItem = styled(RowBetween)`
|
||||
|
||||
const MenuItem = styled(PaddedItem)`
|
||||
cursor: pointer;
|
||||
|
||||
:hover {
|
||||
background-color: ${({ theme }) => theme.tokenRowHover};
|
||||
}
|
||||
`
|
||||
// filters on results
|
||||
const FILTERS = {
|
||||
VOLUME: 'VOLUME',
|
||||
LIQUIDITY: 'LIQUIDITY',
|
||||
BALANCES: 'BALANCES'
|
||||
}
|
||||
|
||||
function SearchModal({
|
||||
history,
|
||||
isOpen,
|
||||
@ -120,28 +125,26 @@ function SearchModal({
|
||||
urlAddedTokens,
|
||||
filterType,
|
||||
hiddenToken,
|
||||
showSendWithSwap,
|
||||
onTokenSelectSendWithSwap
|
||||
showSendWithSwap
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { account, chainId } = useWeb3React()
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
|
||||
// get all exchanges
|
||||
const allTokens = useAllTokens()
|
||||
const allExchanges = useAllExchanges()
|
||||
const token = useToken(searchQuery)
|
||||
const allBalances = useAllBalances()
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
const [sortDirection, setSortDirection] = useState(true)
|
||||
|
||||
const token = useToken(searchQuery)
|
||||
const tokenAddress = token && token.address
|
||||
|
||||
// get all tokens
|
||||
const allTokens = useAllTokens()
|
||||
// amount of tokens to display at once
|
||||
const [, setTokensShown] = useState(0)
|
||||
const [, setPairsShown] = useState(0)
|
||||
|
||||
// all balances for both account and exchanges
|
||||
let allBalances = useAllBalances()
|
||||
|
||||
const [sortDirection, setSortDirection] = useState(true)
|
||||
const [activeFilter, setActiveFilter] = useState(FILTERS.BALANCES)
|
||||
|
||||
const tokenList = useMemo(() => {
|
||||
return Object.keys(allTokens)
|
||||
@ -149,12 +152,10 @@ function SearchModal({
|
||||
if (allTokens[a].symbol && allTokens[b].symbol) {
|
||||
const aSymbol = allTokens[a].symbol.toLowerCase()
|
||||
const bSymbol = allTokens[b].symbol.toLowerCase()
|
||||
|
||||
// pin ETH to top
|
||||
if (aSymbol === 'ETH'.toLowerCase() || bSymbol === 'ETH'.toLowerCase()) {
|
||||
return aSymbol === bSymbol ? 0 : aSymbol === 'ETH'.toLowerCase() ? -1 : 1
|
||||
}
|
||||
|
||||
// sort by balance
|
||||
const balanceA = allBalances?.[account]?.[a]
|
||||
const balanceB = allBalances?.[account]?.[b]
|
||||
@ -162,16 +163,12 @@ function SearchModal({
|
||||
if (balanceA && !balanceB) {
|
||||
return sortDirection
|
||||
}
|
||||
|
||||
if (!balanceA && balanceB) {
|
||||
return sortDirection * -1
|
||||
}
|
||||
|
||||
if (balanceA && balanceB) {
|
||||
return sortDirection * parseFloat(balanceA.toExact()) > parseFloat(balanceB.toExact()) ? -1 : 1
|
||||
}
|
||||
|
||||
// sort alphabetically
|
||||
return aSymbol < bSymbol ? -1 : aSymbol > bSymbol ? 1 : 0
|
||||
} else {
|
||||
return 0
|
||||
@ -181,16 +178,11 @@ function SearchModal({
|
||||
if (k === hiddenToken) {
|
||||
return false
|
||||
}
|
||||
|
||||
let balance
|
||||
// only update if we have data
|
||||
balance = allBalances?.[account]?.[k]
|
||||
|
||||
return {
|
||||
name: allTokens[k].name,
|
||||
symbol: allTokens[k].symbol,
|
||||
address: k,
|
||||
balance: balance
|
||||
balance: allBalances?.[account]?.[k]
|
||||
}
|
||||
})
|
||||
}, [allTokens, allBalances, account, sortDirection, hiddenToken])
|
||||
@ -198,10 +190,7 @@ function SearchModal({
|
||||
const filteredTokenList = useMemo(() => {
|
||||
return tokenList.filter(tokenEntry => {
|
||||
const inputIsAddress = searchQuery.slice(0, 2) === '0x'
|
||||
|
||||
// check the regex for each field
|
||||
const regexMatches = Object.keys(tokenEntry).map(tokenEntryKey => {
|
||||
// if address field only search if input starts with 0x
|
||||
if (tokenEntryKey === 'address') {
|
||||
return (
|
||||
inputIsAddress &&
|
||||
@ -218,21 +207,14 @@ function SearchModal({
|
||||
})
|
||||
}, [tokenList, searchQuery])
|
||||
|
||||
function _onTokenSelect(address, sendWithSwap = false) {
|
||||
if (sendWithSwap) {
|
||||
setSearchQuery('')
|
||||
onTokenSelectSendWithSwap(address)
|
||||
onDismiss()
|
||||
} else {
|
||||
setSearchQuery('')
|
||||
onTokenSelect(address)
|
||||
onDismiss()
|
||||
}
|
||||
function _onTokenSelect(address) {
|
||||
setSearchQuery('')
|
||||
onTokenSelect(address)
|
||||
onDismiss()
|
||||
}
|
||||
|
||||
// manage focus on modal show
|
||||
const inputRef = useRef()
|
||||
|
||||
function onInput(event) {
|
||||
const input = event.target.value
|
||||
const checksummedInput = isAddress(input)
|
||||
@ -244,34 +226,39 @@ function SearchModal({
|
||||
onDismiss()
|
||||
}
|
||||
|
||||
// amount of tokens to display at once
|
||||
const [, setTokensShown] = useState(0)
|
||||
const [, setPairsShown] = useState(0)
|
||||
|
||||
// filters on results
|
||||
const FILTERS = {
|
||||
VOLUME: 'VOLUME',
|
||||
LIQUIDITY: 'LIQUIDITY',
|
||||
BALANCES: 'BALANCES'
|
||||
}
|
||||
const [activeFilter, setActiveFilter] = useState(FILTERS.BALANCES)
|
||||
|
||||
// sort tokens
|
||||
const escapeStringRegexp = string => string
|
||||
|
||||
// sort pairs
|
||||
const filteredPairList = useMemo(() => {
|
||||
// check if the search is an address
|
||||
const isAddress = searchQuery.slice(0, 2) === '0x'
|
||||
return Object.keys(allExchanges).filter(exchangeAddress => {
|
||||
const exchange = allExchanges[exchangeAddress]
|
||||
const sortedPairList = useMemo(() => {
|
||||
return Object.keys(allExchanges).sort((a, b) => {
|
||||
// sort by balance
|
||||
const balanceA = allBalances?.[account]?.[a]
|
||||
const balanceB = allBalances?.[account]?.[b]
|
||||
|
||||
if (balanceA && !balanceB) {
|
||||
return sortDirection
|
||||
}
|
||||
if (!balanceA && balanceB) {
|
||||
return sortDirection * -1
|
||||
}
|
||||
if (balanceA && balanceB) {
|
||||
const order = sortDirection * (parseFloat(balanceA.toExact()) > parseFloat(balanceB.toExact()) ? -1 : 1)
|
||||
return order ? 1 : -1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
})
|
||||
}, [account, allBalances, allExchanges, sortDirection])
|
||||
|
||||
const filteredPairList = useMemo(() => {
|
||||
const isAddress = searchQuery.slice(0, 2) === '0x'
|
||||
return sortedPairList.filter(exchangeAddress => {
|
||||
const exchange = allExchanges[exchangeAddress]
|
||||
if (searchQuery === '') {
|
||||
return true
|
||||
}
|
||||
const token0 = allTokens[exchange.token0]
|
||||
const token1 = allTokens[exchange.token1]
|
||||
|
||||
const regexMatches = Object.keys(token0).map(field => {
|
||||
if (
|
||||
(field === 'address' && isAddress) ||
|
||||
@ -288,7 +275,7 @@ function SearchModal({
|
||||
|
||||
return regexMatches.some(m => m)
|
||||
})
|
||||
}, [allExchanges, allTokens, searchQuery])
|
||||
}, [account, allBalances, allExchanges, allTokens, searchQuery, sortDirection])
|
||||
|
||||
// update the amount shown as filtered list changes
|
||||
useEffect(() => {
|
||||
@ -312,9 +299,7 @@ function SearchModal({
|
||||
filteredPairList.map((exchangeAddress, i) => {
|
||||
const token0 = allTokens[allExchanges[exchangeAddress].token0]
|
||||
const token1 = allTokens[allExchanges[exchangeAddress].token1]
|
||||
|
||||
const balance = allBalances?.[account]?.[exchangeAddress]?.toSignificant(6)
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
key={i}
|
||||
|
@ -2,23 +2,23 @@ import React from 'react'
|
||||
import Slider from '@material-ui/core/Slider'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
const marks = [
|
||||
{
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
value: 25
|
||||
},
|
||||
{
|
||||
value: 50
|
||||
},
|
||||
{
|
||||
value: 75
|
||||
},
|
||||
{
|
||||
value: 100
|
||||
}
|
||||
]
|
||||
// const marks = [
|
||||
// {
|
||||
// value: 0
|
||||
// },
|
||||
// {
|
||||
// value: 25
|
||||
// },
|
||||
// {
|
||||
// value: 50
|
||||
// },
|
||||
// {
|
||||
// value: 75
|
||||
// },
|
||||
// {
|
||||
// value: 100
|
||||
// }
|
||||
// ]
|
||||
|
||||
const StyledSlider = withStyles({
|
||||
root: {
|
||||
|
@ -1,765 +0,0 @@
|
||||
import React, { useState, useEffect, useRef, useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled, { css, keyframes } from 'styled-components'
|
||||
import { darken, lighten } from 'polished'
|
||||
import { isAddress, amountFormatter } from '../../utils'
|
||||
import { useDebounce } from '../../hooks'
|
||||
|
||||
import question from '../../assets/images/question.svg'
|
||||
|
||||
import NewContextualInfo from '../../components/ContextualInfoNew'
|
||||
|
||||
const WARNING_TYPE = Object.freeze({
|
||||
none: 'none',
|
||||
emptyInput: 'emptyInput',
|
||||
invalidEntryBound: 'invalidEntryBound',
|
||||
riskyEntryHigh: 'riskyEntryHigh',
|
||||
riskyEntryLow: 'riskyEntryLow'
|
||||
})
|
||||
|
||||
const Flex = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
`
|
||||
|
||||
const FlexBetween = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
`
|
||||
|
||||
const WrappedSlippageRow = ({ wrap, ...rest }) => <Flex {...rest} />
|
||||
const SlippageRow = styled(WrappedSlippageRow)`
|
||||
position: relative;
|
||||
flex-wrap: ${({ wrap }) => wrap && 'wrap'};
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
padding-top: ${({ wrap }) => wrap && '0.25rem'};
|
||||
`
|
||||
|
||||
const QuestionWrapper = styled.button`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin-left: 0.4rem;
|
||||
padding: 0.2rem;
|
||||
border: none;
|
||||
background: none;
|
||||
outline: none;
|
||||
cursor: default;
|
||||
border-radius: 36px;
|
||||
|
||||
:hover,
|
||||
:focus {
|
||||
opacity: 0.7;
|
||||
}
|
||||
`
|
||||
|
||||
const HelpCircleStyled = styled.img`
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
`
|
||||
|
||||
const fadeIn = keyframes`
|
||||
from {
|
||||
opacity : 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity : 1;
|
||||
}
|
||||
`
|
||||
|
||||
const Popup = styled(Flex)`
|
||||
position: absolute;
|
||||
width: 228px;
|
||||
left: -78px;
|
||||
top: -94px;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 0.6rem 1rem;
|
||||
line-height: 150%;
|
||||
background: ${({ theme }) => theme.inputBackground};
|
||||
border: 1px solid ${({ theme }) => theme.mercuryGray};
|
||||
|
||||
border-radius: 8px;
|
||||
|
||||
animation: ${fadeIn} 0.15s linear;
|
||||
|
||||
color: ${({ theme }) => theme.textColor};
|
||||
font-style: italic;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
left: -20px;
|
||||
`}
|
||||
`
|
||||
|
||||
const FancyButton = styled.button`
|
||||
color: ${({ theme }) => theme.textColor};
|
||||
align-items: center;
|
||||
min-width: 55px;
|
||||
border-radius: 36px;
|
||||
font-size: 12px;
|
||||
border: 1px solid ${({ theme }) => theme.mercuryGray};
|
||||
outline: none;
|
||||
background: ${({ theme }) => theme.inputBackground};
|
||||
|
||||
:hover {
|
||||
cursor: inherit;
|
||||
border: 1px solid ${({ theme }) => theme.chaliceGray};
|
||||
}
|
||||
:focus {
|
||||
border: 1px solid ${({ theme }) => theme.royalBlue};
|
||||
}
|
||||
`
|
||||
|
||||
const Option = styled(FancyButton)`
|
||||
margin-right: 8px;
|
||||
margin-top: 6px;
|
||||
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
${({ active, theme }) =>
|
||||
active &&
|
||||
css`
|
||||
background-color: ${({ theme }) => theme.royalBlue};
|
||||
color: ${({ theme }) => theme.white};
|
||||
border: none;
|
||||
|
||||
:hover {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
background-color: ${({ theme }) => darken(0.05, theme.royalBlue)};
|
||||
}
|
||||
|
||||
:focus {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
background-color: ${({ theme }) => lighten(0.05, theme.royalBlue)};
|
||||
}
|
||||
|
||||
:active {
|
||||
background-color: ${({ theme }) => darken(0.05, theme.royalBlue)};
|
||||
}
|
||||
|
||||
:hover:focus {
|
||||
background-color: ${({ theme }) => theme.royalBlue};
|
||||
}
|
||||
:hover:focus:active {
|
||||
background-color: ${({ theme }) => darken(0.05, theme.royalBlue)};
|
||||
}
|
||||
`}
|
||||
`
|
||||
|
||||
const OptionLarge = styled(Option)`
|
||||
width: 120px;
|
||||
`
|
||||
|
||||
const Input = styled.input`
|
||||
background: ${({ theme }) => theme.inputBackground};
|
||||
flex-grow: 1;
|
||||
font-size: 12px;
|
||||
min-width: 20px;
|
||||
|
||||
outline: none;
|
||||
box-sizing: border-box;
|
||||
|
||||
&::-webkit-outer-spin-button,
|
||||
&::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
cursor: inherit;
|
||||
|
||||
color: ${({ theme }) => theme.doveGray};
|
||||
text-align: left;
|
||||
${({ active }) =>
|
||||
active &&
|
||||
css`
|
||||
color: initial;
|
||||
cursor: initial;
|
||||
text-align: right;
|
||||
`}
|
||||
|
||||
${({ placeholder }) =>
|
||||
placeholder !== 'Custom' &&
|
||||
css`
|
||||
text-align: right;
|
||||
color: ${({ theme }) => theme.textColor};
|
||||
`}
|
||||
|
||||
${({ color }) =>
|
||||
color === 'red' &&
|
||||
css`
|
||||
color: ${({ theme }) => theme.salmonRed};
|
||||
`}
|
||||
`
|
||||
|
||||
const BottomError = styled.div`
|
||||
${({ show }) =>
|
||||
show &&
|
||||
css`
|
||||
padding-top: 12px;
|
||||
`}
|
||||
color: ${({ theme }) => theme.doveGray};
|
||||
${({ color }) =>
|
||||
color === 'red' &&
|
||||
css`
|
||||
color: ${({ theme }) => theme.salmonRed};
|
||||
`}
|
||||
`
|
||||
|
||||
const OptionCustom = styled(FancyButton)`
|
||||
position: relative;
|
||||
width: 120px;
|
||||
margin-top: 6px;
|
||||
padding: 0 0.75rem;
|
||||
|
||||
${({ active }) =>
|
||||
active &&
|
||||
css`
|
||||
border: 1px solid ${({ theme }) => theme.royalBlue};
|
||||
:hover {
|
||||
border: 1px solid ${({ theme }) => darken(0.1, theme.royalBlue)};
|
||||
}
|
||||
`}
|
||||
|
||||
${({ color }) =>
|
||||
color === 'red' &&
|
||||
css`
|
||||
border: 1px solid ${({ theme }) => theme.salmonRed};
|
||||
`}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0px;
|
||||
border-radius: 2rem;
|
||||
}
|
||||
`
|
||||
|
||||
const Bold = styled.span`
|
||||
font-weight: 500;
|
||||
`
|
||||
|
||||
const LastSummaryText = styled.div`
|
||||
padding-top: 0.5rem;
|
||||
`
|
||||
|
||||
const SlippageSelector = styled.div`
|
||||
background-color: ${({ theme }) => darken(0.04, theme.concreteGray)};
|
||||
padding: 1rem 1.25rem 1rem 1.25rem;
|
||||
border-radius: 12px 12px 0 0;
|
||||
`
|
||||
|
||||
const Percent = styled.div`
|
||||
color: inherit;
|
||||
font-size: 0, 8rem;
|
||||
flex-grow: 0;
|
||||
|
||||
${({ color, theme }) =>
|
||||
(color === 'faded' &&
|
||||
css`
|
||||
color: ${theme.doveGray};
|
||||
`) ||
|
||||
(color === 'red' &&
|
||||
css`
|
||||
color: ${theme.salmonRed};
|
||||
`)};
|
||||
`
|
||||
|
||||
const Faded = styled.span`
|
||||
opacity: 0.7;
|
||||
`
|
||||
|
||||
const TransactionInfo = styled.div`
|
||||
padding: 1.25rem 1.25rem 1rem 1.25rem;
|
||||
`
|
||||
|
||||
const ValueWrapper = styled.span`
|
||||
padding: 0.125rem 0.3rem 0.1rem 0.3rem;
|
||||
background-color: ${({ theme }) => darken(0.04, theme.concreteGray)};
|
||||
border-radius: 12px;
|
||||
font-variant: tabular-nums;
|
||||
`
|
||||
|
||||
const DeadlineSelector = styled.div`
|
||||
background-color: ${({ theme }) => darken(0.04, theme.concreteGray)};
|
||||
padding: 1rem 1.25rem 1rem 1.25rem;
|
||||
border-radius: 0 0 12px 12px;
|
||||
`
|
||||
const DeadlineRow = SlippageRow
|
||||
const DeadlineInput = OptionCustom
|
||||
|
||||
export default function TransactionDetails(props) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [activeIndex, setActiveIndex] = useState(3)
|
||||
|
||||
const [warningType, setWarningType] = useState(WARNING_TYPE.none)
|
||||
|
||||
const inputRef = useRef()
|
||||
|
||||
const [showPopup, setPopup] = useState(false)
|
||||
|
||||
const [userInput, setUserInput] = useState('')
|
||||
const debouncedInput = useDebounce(userInput, 150)
|
||||
|
||||
useEffect(() => {
|
||||
if (activeIndex === 4) {
|
||||
checkBounds(debouncedInput)
|
||||
}
|
||||
})
|
||||
|
||||
const [deadlineInput, setDeadlineInput] = useState('')
|
||||
|
||||
function renderSummary() {
|
||||
let contextualInfo = ''
|
||||
let isError = false
|
||||
if (props.brokenTokenWarning) {
|
||||
contextualInfo = t('brokenToken')
|
||||
isError = true
|
||||
} else if (props.inputError || props.independentError) {
|
||||
contextualInfo = props.inputError || props.independentError
|
||||
isError = true
|
||||
} else if (!props.inputCurrency || !props.outputCurrency) {
|
||||
contextualInfo = t('selectTokenCont')
|
||||
} else if (!props.independentValue) {
|
||||
contextualInfo = t('enterValueCont')
|
||||
} else if (props.sending && !props.recipientAddress) {
|
||||
contextualInfo = t('noRecipient')
|
||||
} else if (props.sending && !isAddress(props.recipientAddress)) {
|
||||
contextualInfo = t('invalidRecipient')
|
||||
} else if (!props.account) {
|
||||
contextualInfo = t('noWallet')
|
||||
isError = true
|
||||
}
|
||||
|
||||
const slippageWarningText = props.highSlippageWarning
|
||||
? t('highSlippageWarning')
|
||||
: props.slippageWarning
|
||||
? t('slippageWarning')
|
||||
: ''
|
||||
|
||||
return (
|
||||
<NewContextualInfo
|
||||
openDetailsText={t('transactionDetails')}
|
||||
closeDetailsText={t('hideDetails')}
|
||||
contextualInfo={contextualInfo ? contextualInfo : slippageWarningText}
|
||||
allowExpand={
|
||||
!!(
|
||||
!props.brokenTokenWarning &&
|
||||
props.inputCurrency &&
|
||||
props.outputCurrency &&
|
||||
props.inputValueParsed &&
|
||||
props.outputValueParsed &&
|
||||
(props.sending ? props.recipientAddress : true)
|
||||
)
|
||||
}
|
||||
isError={isError}
|
||||
slippageWarning={props.slippageWarning && !contextualInfo}
|
||||
highSlippageWarning={props.highSlippageWarning && !contextualInfo}
|
||||
brokenTokenWarning={props.brokenTokenWarning}
|
||||
renderTransactionDetails={renderTransactionDetails}
|
||||
dropDownContent={dropDownContent}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const dropDownContent = () => {
|
||||
return (
|
||||
<>
|
||||
{renderTransactionDetails()}
|
||||
<SlippageSelector>
|
||||
<SlippageRow>
|
||||
Limit additional price slippage
|
||||
<QuestionWrapper
|
||||
onClick={() => {
|
||||
setPopup(!showPopup)
|
||||
}}
|
||||
onMouseEnter={() => {
|
||||
setPopup(true)
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setPopup(false)
|
||||
}}
|
||||
>
|
||||
<HelpCircleStyled src={question} alt="popup" />
|
||||
</QuestionWrapper>
|
||||
{showPopup ? (
|
||||
<Popup>
|
||||
Lowering this limit decreases your risk of frontrunning. However, this makes it more likely that your
|
||||
transaction will fail due to normal price movements.
|
||||
</Popup>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</SlippageRow>
|
||||
<SlippageRow wrap>
|
||||
<Option
|
||||
onClick={() => {
|
||||
setFromFixed(1, 0.1)
|
||||
}}
|
||||
active={activeIndex === 1}
|
||||
>
|
||||
0.1%
|
||||
</Option>
|
||||
<OptionLarge
|
||||
onClick={() => {
|
||||
setFromFixed(2, 0.5)
|
||||
}}
|
||||
active={activeIndex === 2}
|
||||
>
|
||||
0.5% <Faded>(suggested)</Faded>
|
||||
</OptionLarge>
|
||||
<Option
|
||||
onClick={() => {
|
||||
setFromFixed(3, 1)
|
||||
}}
|
||||
active={activeIndex === 3}
|
||||
>
|
||||
1%
|
||||
</Option>
|
||||
<OptionCustom
|
||||
active={activeIndex === 4}
|
||||
color={
|
||||
warningType === WARNING_TYPE.emptyInput
|
||||
? ''
|
||||
: warningType !== WARNING_TYPE.none && warningType !== WARNING_TYPE.riskyEntryLow
|
||||
? 'red'
|
||||
: ''
|
||||
}
|
||||
onClick={() => {
|
||||
setFromCustom()
|
||||
}}
|
||||
>
|
||||
<FlexBetween>
|
||||
{!(warningType === WARNING_TYPE.none || warningType === WARNING_TYPE.emptyInput) && (
|
||||
<span role="img" aria-label="warning">
|
||||
⚠️
|
||||
</span>
|
||||
)}
|
||||
<Input
|
||||
tabIndex={-1}
|
||||
ref={inputRef}
|
||||
active={activeIndex === 4}
|
||||
placeholder={
|
||||
activeIndex === 4
|
||||
? !!userInput
|
||||
? ''
|
||||
: '0'
|
||||
: activeIndex !== 4 && userInput !== ''
|
||||
? userInput
|
||||
: 'Custom'
|
||||
}
|
||||
value={activeIndex === 4 ? userInput : ''}
|
||||
onChange={parseInput}
|
||||
color={
|
||||
warningType === WARNING_TYPE.emptyInput
|
||||
? ''
|
||||
: warningType !== WARNING_TYPE.none && warningType !== WARNING_TYPE.riskyEntryLow
|
||||
? 'red'
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
<Percent
|
||||
color={
|
||||
activeIndex !== 4
|
||||
? 'faded'
|
||||
: warningType === WARNING_TYPE.riskyEntryHigh || warningType === WARNING_TYPE.invalidEntryBound
|
||||
? 'red'
|
||||
: ''
|
||||
}
|
||||
>
|
||||
%
|
||||
</Percent>
|
||||
</FlexBetween>
|
||||
</OptionCustom>
|
||||
</SlippageRow>
|
||||
<SlippageRow>
|
||||
<BottomError
|
||||
show={activeIndex === 4}
|
||||
color={
|
||||
warningType === WARNING_TYPE.emptyInput
|
||||
? ''
|
||||
: warningType !== WARNING_TYPE.none && warningType !== WARNING_TYPE.riskyEntryLow
|
||||
? 'red'
|
||||
: ''
|
||||
}
|
||||
>
|
||||
{activeIndex === 4 && warningType.toString() === 'none' && 'Custom slippage value'}
|
||||
{warningType === WARNING_TYPE.emptyInput && 'Enter a slippage percentage'}
|
||||
{warningType === WARNING_TYPE.invalidEntryBound && 'Please select a value no greater than 50%'}
|
||||
{warningType === WARNING_TYPE.riskyEntryHigh && 'Your transaction may be frontrun'}
|
||||
{warningType === WARNING_TYPE.riskyEntryLow && 'Your transaction may fail'}
|
||||
</BottomError>
|
||||
</SlippageRow>
|
||||
</SlippageSelector>
|
||||
<DeadlineSelector>
|
||||
Set swap deadline (minutes from now)
|
||||
<DeadlineRow wrap>
|
||||
<DeadlineInput>
|
||||
<Input placeholder={'Deadline'} value={deadlineInput} onChange={parseDeadlineInput} />
|
||||
</DeadlineInput>
|
||||
</DeadlineRow>
|
||||
</DeadlineSelector>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const setFromCustom = () => {
|
||||
setActiveIndex(4)
|
||||
inputRef.current.focus()
|
||||
// if there's a value, evaluate the bounds
|
||||
checkBounds(debouncedInput)
|
||||
}
|
||||
|
||||
// destructure props for to limit effect callbacks
|
||||
const setRawSlippage = props.setRawSlippage
|
||||
const setRawTokenSlippage = props.setRawTokenSlippage
|
||||
const setcustomSlippageError = props.setcustomSlippageError
|
||||
const setDeadline = props.setDeadline
|
||||
|
||||
const updateSlippage = useCallback(
|
||||
newSlippage => {
|
||||
// round to 2 decimals to prevent ethers error
|
||||
let numParsed = parseInt(newSlippage * 100)
|
||||
|
||||
// set both slippage values in parents
|
||||
setRawSlippage(numParsed)
|
||||
setRawTokenSlippage(numParsed)
|
||||
},
|
||||
[setRawSlippage, setRawTokenSlippage]
|
||||
)
|
||||
|
||||
// used for slippage presets
|
||||
const setFromFixed = useCallback(
|
||||
(index, slippage) => {
|
||||
// update slippage in parent, reset errors and input state
|
||||
updateSlippage(slippage)
|
||||
setWarningType(WARNING_TYPE.none)
|
||||
setActiveIndex(index)
|
||||
setcustomSlippageError('valid`')
|
||||
},
|
||||
[setcustomSlippageError, updateSlippage]
|
||||
)
|
||||
|
||||
/**
|
||||
* @todo
|
||||
* Breaks without useState here, able to
|
||||
* break input parsing if typing is faster than
|
||||
* debounce time
|
||||
*/
|
||||
|
||||
const [initialSlippage] = useState(props.rawSlippage)
|
||||
|
||||
useEffect(() => {
|
||||
switch (Number.parseInt(initialSlippage)) {
|
||||
case 10:
|
||||
setFromFixed(1, 0.1)
|
||||
break
|
||||
case 50:
|
||||
setFromFixed(2, 0.5)
|
||||
break
|
||||
case 100:
|
||||
setFromFixed(3, 1)
|
||||
break
|
||||
default:
|
||||
// restrict to 2 decimal places
|
||||
let acceptableValues = [/^$/, /^\d{1,2}$/, /^\d{0,2}\.\d{0,2}$/]
|
||||
// if its within accepted decimal limit, update the input state
|
||||
if (acceptableValues.some(val => val.test(initialSlippage / 100))) {
|
||||
setUserInput(initialSlippage / 100)
|
||||
setActiveIndex(4)
|
||||
}
|
||||
}
|
||||
}, [initialSlippage, setFromFixed])
|
||||
|
||||
const checkBounds = useCallback(
|
||||
slippageValue => {
|
||||
setWarningType(WARNING_TYPE.none)
|
||||
setcustomSlippageError('valid')
|
||||
|
||||
if (slippageValue === '' || slippageValue === '.') {
|
||||
setcustomSlippageError('invalid')
|
||||
return setWarningType(WARNING_TYPE.emptyInput)
|
||||
}
|
||||
|
||||
// check bounds and set errors
|
||||
if (Number(slippageValue) < 0 || Number(slippageValue) > 50) {
|
||||
setcustomSlippageError('invalid')
|
||||
return setWarningType(WARNING_TYPE.invalidEntryBound)
|
||||
}
|
||||
if (Number(slippageValue) >= 0 && Number(slippageValue) < 0.1) {
|
||||
setcustomSlippageError('valid')
|
||||
setWarningType(WARNING_TYPE.riskyEntryLow)
|
||||
}
|
||||
if (Number(slippageValue) > 5) {
|
||||
setcustomSlippageError('warning')
|
||||
setWarningType(WARNING_TYPE.riskyEntryHigh)
|
||||
}
|
||||
//update the actual slippage value in parent
|
||||
updateSlippage(Number(slippageValue))
|
||||
},
|
||||
[setcustomSlippageError, updateSlippage]
|
||||
)
|
||||
|
||||
// check that the theyve entered number and correct decimal
|
||||
const parseInput = e => {
|
||||
let input = e.target.value
|
||||
|
||||
// restrict to 2 decimal places
|
||||
let acceptableValues = [/^$/, /^\d{1,2}$/, /^\d{0,2}\.\d{0,2}$/]
|
||||
// if its within accepted decimal limit, update the input state
|
||||
if (acceptableValues.some(a => a.test(input))) {
|
||||
setUserInput(input)
|
||||
}
|
||||
}
|
||||
|
||||
const [initialDeadline] = useState(props.deadline)
|
||||
|
||||
useEffect(() => {
|
||||
setDeadlineInput(initialDeadline / 60)
|
||||
}, [initialDeadline])
|
||||
|
||||
const parseDeadlineInput = e => {
|
||||
const input = e.target.value
|
||||
|
||||
const acceptableValues = [/^$/, /^\d+$/]
|
||||
if (acceptableValues.some(re => re.test(input))) {
|
||||
setDeadlineInput(input)
|
||||
setDeadline(parseInt(input) * 60)
|
||||
}
|
||||
}
|
||||
|
||||
const b = text => <Bold>{text}</Bold>
|
||||
|
||||
const renderTransactionDetails = () => {
|
||||
if (props.independentField === props.INPUT) {
|
||||
return props.sending ? (
|
||||
<TransactionInfo>
|
||||
<div>
|
||||
{t('youAreSelling')}{' '}
|
||||
<ValueWrapper>
|
||||
{b(
|
||||
`${amountFormatter(
|
||||
props.independentValueParsed,
|
||||
props.independentDecimals,
|
||||
Math.min(4, props.independentDecimals)
|
||||
)} ${props.inputSymbol}`
|
||||
)}
|
||||
</ValueWrapper>
|
||||
</div>
|
||||
<LastSummaryText>
|
||||
{b(props.recipientAddress)} {t('willReceive')}{' '}
|
||||
<ValueWrapper>
|
||||
{b(
|
||||
`${amountFormatter(
|
||||
props.dependentValueMinumum,
|
||||
props.dependentDecimals,
|
||||
Math.min(4, props.dependentDecimals)
|
||||
)} ${props.outputSymbol}`
|
||||
)}
|
||||
</ValueWrapper>{' '}
|
||||
</LastSummaryText>
|
||||
<LastSummaryText>
|
||||
{t('priceChange')} <ValueWrapper>{b(`${props.percentSlippageFormatted}%`)}</ValueWrapper>
|
||||
</LastSummaryText>
|
||||
</TransactionInfo>
|
||||
) : (
|
||||
<TransactionInfo>
|
||||
<div>
|
||||
{t('youAreSelling')}{' '}
|
||||
<ValueWrapper>
|
||||
{b(
|
||||
`${amountFormatter(
|
||||
props.independentValueParsed,
|
||||
props.independentDecimals,
|
||||
Math.min(4, props.independentDecimals)
|
||||
)} ${props.inputSymbol}`
|
||||
)}
|
||||
</ValueWrapper>{' '}
|
||||
{t('forAtLeast')}
|
||||
<ValueWrapper>
|
||||
{b(
|
||||
`${amountFormatter(
|
||||
props.dependentValueMinumum,
|
||||
props.dependentDecimals,
|
||||
Math.min(4, props.dependentDecimals)
|
||||
)} ${props.outputSymbol}`
|
||||
)}
|
||||
</ValueWrapper>
|
||||
</div>
|
||||
<LastSummaryText>
|
||||
{t('priceChange')} <ValueWrapper>{b(`${props.percentSlippageFormatted}%`)}</ValueWrapper>
|
||||
</LastSummaryText>
|
||||
</TransactionInfo>
|
||||
)
|
||||
} else {
|
||||
return props.sending ? (
|
||||
<TransactionInfo>
|
||||
<div>
|
||||
{t('youAreSending')}{' '}
|
||||
<ValueWrapper>
|
||||
{b(
|
||||
`${amountFormatter(
|
||||
props.independentValueParsed,
|
||||
props.independentDecimals,
|
||||
Math.min(4, props.independentDecimals)
|
||||
)} ${props.outputSymbol}`
|
||||
)}
|
||||
</ValueWrapper>{' '}
|
||||
{t('to')} {b(props.recipientAddress)} {t('forAtMost')}{' '}
|
||||
<ValueWrapper>
|
||||
{b(
|
||||
`${amountFormatter(
|
||||
props.dependentValueMaximum,
|
||||
props.dependentDecimals,
|
||||
Math.min(4, props.dependentDecimals)
|
||||
)} ${props.inputSymbol}`
|
||||
)}
|
||||
</ValueWrapper>{' '}
|
||||
</div>
|
||||
<LastSummaryText>
|
||||
{t('priceChange')} <ValueWrapper>{b(`${props.percentSlippageFormatted}%`)}</ValueWrapper>
|
||||
</LastSummaryText>
|
||||
</TransactionInfo>
|
||||
) : (
|
||||
<TransactionInfo>
|
||||
{t('youAreBuying')}{' '}
|
||||
<ValueWrapper>
|
||||
{b(
|
||||
`${amountFormatter(
|
||||
props.independentValueParsed,
|
||||
props.independentDecimals,
|
||||
Math.min(4, props.independentDecimals)
|
||||
)} ${props.outputSymbol}`
|
||||
)}
|
||||
</ValueWrapper>{' '}
|
||||
{t('forAtMost')}{' '}
|
||||
<ValueWrapper>
|
||||
{b(
|
||||
`${amountFormatter(
|
||||
props.dependentValueMaximum,
|
||||
props.dependentDecimals,
|
||||
Math.min(4, props.dependentDecimals)
|
||||
)} ${props.inputSymbol}`
|
||||
)}
|
||||
</ValueWrapper>{' '}
|
||||
<LastSummaryText>
|
||||
{t('priceChange')} <ValueWrapper>{b(`${props.percentSlippageFormatted}%`)}</ValueWrapper>
|
||||
</LastSummaryText>
|
||||
</TransactionInfo>
|
||||
)
|
||||
}
|
||||
}
|
||||
return <>{renderSummary()}</>
|
||||
}
|
@ -39,7 +39,6 @@ export default function Web3ReactManager({ children }) {
|
||||
const triedEager = useEagerConnect()
|
||||
|
||||
// after eagerly trying injected, if the network connect ever isn't active or in an error state, activate itd
|
||||
// TODO think about not doing this at all
|
||||
useEffect(() => {
|
||||
if (triedEager && !networkActive && !networkError && !active) {
|
||||
activateNetwork(network)
|
||||
|
@ -1,21 +1,4 @@
|
||||
[
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "_WETH",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "constructor"
|
||||
},
|
||||
{
|
||||
"payable": true,
|
||||
"stateMutability": "payable",
|
||||
"type": "fallback"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
@ -153,32 +136,6 @@
|
||||
"stateMutability": "payable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "tokenA",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "tokenB",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "exchangeFor",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "exchange",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "pure",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
@ -191,7 +148,7 @@
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"stateMutability": "pure",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
@ -308,52 +265,6 @@
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "tokenA",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "tokenB",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "getReserves",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "reserveA",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "reserveB",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "initCodeHash",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
@ -525,6 +436,11 @@
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "approveMax",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"internalType": "uint8",
|
||||
"name": "v",
|
||||
@ -596,6 +512,11 @@
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "approveMax",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"internalType": "uint8",
|
||||
"name": "v",
|
||||
@ -629,37 +550,6 @@
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "tokenA",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "tokenB",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "sortTokens",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "token0",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "token1",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "pure",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
@ -895,35 +785,5 @@
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "transferFromSelector",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bytes4",
|
||||
"name": "",
|
||||
"type": "bytes4"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "transferSelector",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bytes4",
|
||||
"name": "",
|
||||
"type": "bytes4"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
}
|
||||
]
|
||||
|
@ -1,16 +1,16 @@
|
||||
import { injected, walletconnect, walletlink, fortmatic, portis } from '../connectors'
|
||||
|
||||
export const FACTORY_ADDRESSES = {
|
||||
1: '0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95',
|
||||
3: '0x9c83dCE8CA20E9aAF9D3efc003b2ea62aBC08351',
|
||||
4: '0xf5D915570BC477f9B8D6C0E980aA81757A3AaC36',
|
||||
42: '0xD3E51Ef092B2845f10401a0159B2B96e8B6c3D30'
|
||||
1: '',
|
||||
3: '',
|
||||
4: '0xe2f197885abe8ec7c866cFf76605FD06d4576218',
|
||||
42: ''
|
||||
}
|
||||
|
||||
export const ROUTER_ADDRESSES = {
|
||||
1: '',
|
||||
3: '',
|
||||
4: '0xd9210Ff5A0780E083BB40e30d005d93a2DcFA4EF',
|
||||
4: '0xcDbE04934d89e97a24BCc07c3562DC8CF17d8167',
|
||||
42: ''
|
||||
}
|
||||
|
||||
@ -100,12 +100,4 @@ export const SUPPORTED_WALLETS =
|
||||
}
|
||||
}
|
||||
|
||||
// list of tokens that lock fund on adding liquidity - used to disable button
|
||||
export const brokenTokens = [
|
||||
'0xB8c77482e45F1F44dE1745F52C74426C631bDD52',
|
||||
'0x95dAaaB98046846bF4B2853e23cba236fa394A31',
|
||||
'0x55296f69f40Ea6d20E478533C15A6B08B654E758',
|
||||
'0xc3761EB917CD790B30dAD99f6Cc5b4Ff93C4F9eA'
|
||||
]
|
||||
|
||||
export const NetworkContextName = 'NETWORK'
|
||||
|
@ -162,6 +162,7 @@ export function usePopups() {
|
||||
if (key === item.key) {
|
||||
item.show = false
|
||||
}
|
||||
return true
|
||||
})
|
||||
setPopups(currentPopups)
|
||||
}
|
||||
|
@ -427,6 +427,7 @@ export function useAllBalances(): Array<TokenAmount> {
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
})
|
||||
return newBalances
|
||||
@ -443,6 +444,9 @@ export function useAddressBalance(address: string, token: Token): TokenAmount |
|
||||
const { chainId } = useWeb3React()
|
||||
const [state, { startListening, stopListening }] = useBalancesContext()
|
||||
|
||||
const value = typeof chainId === 'number' ? state?.[chainId]?.[address]?.[token?.address]?.value : undefined
|
||||
const formattedValue = value && token && new TokenAmount(token, value)
|
||||
|
||||
/**
|
||||
* @todo
|
||||
* when catching for token, causes infinite rerender
|
||||
@ -457,9 +461,6 @@ export function useAddressBalance(address: string, token: Token): TokenAmount |
|
||||
}
|
||||
}, [chainId, address, startListening, stopListening])
|
||||
|
||||
const value = typeof chainId === 'number' ? state?.[chainId]?.[address]?.[token?.address]?.value : undefined
|
||||
const formattedValue = value && token && new TokenAmount(token, value)
|
||||
|
||||
return useMemo(() => formattedValue, [formattedValue])
|
||||
}
|
||||
|
||||
@ -476,6 +477,7 @@ export function useAccountLPBalances(account: string) {
|
||||
stopListening(chainId, account, exchangeAddress)
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}, [account, allExchanges, chainId, startListening, stopListening])
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ const UPDATE = 'UPDATE'
|
||||
const ALL_EXCHANGES: [Token, Token][] = [
|
||||
[
|
||||
INITIAL_TOKENS_CONTEXT[ChainId.RINKEBY][WETH[ChainId.RINKEBY].address],
|
||||
INITIAL_TOKENS_CONTEXT[ChainId.RINKEBY]['0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735']
|
||||
INITIAL_TOKENS_CONTEXT[ChainId.RINKEBY]['0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735'] //dai
|
||||
],
|
||||
[
|
||||
INITIAL_TOKENS_CONTEXT[ChainId.RINKEBY]['0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735'],
|
||||
|
@ -8,7 +8,7 @@ const UPDATE = 'UPDATE'
|
||||
export const ALL_TOKENS = [
|
||||
WETH[ChainId.RINKEBY],
|
||||
new Token(ChainId.RINKEBY, '0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735', 18, 'DAI', 'Dai Stablecoin'),
|
||||
new Token(ChainId.RINKEBY, '0x8ab15C890E5C03B5F240f2D146e3DF54bEf3Df44', 18, 'IANV2', 'IAn V2 Coin')
|
||||
new Token(ChainId.RINKEBY, '0x8ab15C890E5C03B5F240f2D146e3DF54bEf3Df44', 18, 'IANV2', 'IAn V2 /Coin')
|
||||
]
|
||||
|
||||
// only meant to be used in exchanges.ts!
|
||||
@ -112,6 +112,11 @@ export function useAllTokens(): string[] {
|
||||
const [state] = useTokensContext()
|
||||
|
||||
return useMemo(() => {
|
||||
// hardcode overide weth as ETH
|
||||
if (state && state[chainId]) {
|
||||
state[chainId][WETH[chainId].address].symbol = 'ETH'
|
||||
state[chainId][WETH[chainId].address].name = 'ETH'
|
||||
}
|
||||
return state?.[chainId] || {}
|
||||
}, [state, chainId])
|
||||
}
|
||||
|
@ -1,23 +1,21 @@
|
||||
import { useState, useMemo, useCallback, useEffect, useRef } from 'react'
|
||||
import { useWeb3React as useWeb3ReactCore } from '@web3-react/core'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import { isMobile } from 'react-device-detect'
|
||||
import copy from 'copy-to-clipboard'
|
||||
|
||||
import { NetworkContextName } from '../constants'
|
||||
import ERC20_ABI from '../constants/abis/erc20'
|
||||
import { getContract, getFactoryContract, getExchangeContract, isAddress } from '../utils'
|
||||
import { injected } from '../connectors'
|
||||
import { NetworkContextName } from '../constants'
|
||||
import { getContract, getFactoryContract, getExchangeContract, isAddress } from '../utils'
|
||||
|
||||
export function useWeb3React() {
|
||||
const context = useWeb3ReactCore()
|
||||
const contextNetwork = useWeb3ReactCore(NetworkContextName)
|
||||
|
||||
return context.active ? context : contextNetwork
|
||||
}
|
||||
|
||||
export function useEagerConnect() {
|
||||
const { activate, active } = useWeb3ReactCore() // specifically using useWeb3ReactCore because of what this hook does
|
||||
|
||||
const [tried, setTried] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
@ -265,6 +263,5 @@ export function usePrevious(value) {
|
||||
export function useToggle(initialState = false) {
|
||||
const [state, setState] = useState(initialState)
|
||||
const toggle = useCallback(() => setState(state => !state), [])
|
||||
|
||||
return [state, toggle]
|
||||
}
|
||||
|
@ -2,10 +2,9 @@ import React, { Suspense, lazy } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom'
|
||||
|
||||
import Web3ReactManager from '../components/Web3ReactManager'
|
||||
import Header from '../components/Header'
|
||||
|
||||
import NavigationTabs from '../components/NavigationTabs'
|
||||
import Web3ReactManager from '../components/Web3ReactManager'
|
||||
import { isAddress, getAllQueryParams } from '../utils'
|
||||
|
||||
const Swap = lazy(() => import('./Swap'))
|
||||
@ -65,6 +64,7 @@ export default function App() {
|
||||
{/* this Suspense is for route code-splitting */}
|
||||
<Suspense fallback={null}>
|
||||
<Switch>
|
||||
<Route exact strict path="/" render={() => <Redirect to={{ pathname: '/swap' }} />} />
|
||||
<Route exact strict path="/find" component={() => <Find params={params} />} />
|
||||
<Route exact strict path="/swap" component={() => <Swap params={params} />} />
|
||||
<Route
|
||||
|
@ -1,251 +1,7 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { darken } from 'polished'
|
||||
import { TokenAmount, JSBI } from '@uniswap/sdk'
|
||||
import React from 'react'
|
||||
|
||||
import QR from '../../assets/svg/QR.svg'
|
||||
import TokenLogo from '../../components/TokenLogo'
|
||||
|
||||
import SearchModal from '../../components/SearchModal'
|
||||
import ExchangePage from '../../components/ExchangePage'
|
||||
import NumericalInput from '../../components/NumericalInput'
|
||||
import ConfirmationModal from '../../components/ConfirmationModal'
|
||||
import { Text } from 'rebass'
|
||||
import { TYPE } from '../../theme'
|
||||
import { LightCard } from '../../components/Card'
|
||||
import { ArrowDown } from 'react-feather'
|
||||
import { AutoColumn } from '../../components/Column'
|
||||
import { ButtonPrimary } from '../../components/Button'
|
||||
import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg'
|
||||
|
||||
import { useToken } from '../../contexts/Tokens'
|
||||
import { RowBetween } from '../../components/Row'
|
||||
import { useENSName } from '../../hooks'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { useAddressBalance } from '../../contexts/Balances'
|
||||
|
||||
import { parseUnits } from '@ethersproject/units'
|
||||
import { isAddress } from '../../utils'
|
||||
|
||||
const CurrencySelect = styled.button`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 20px;
|
||||
width: ${({ selected }) => (selected ? '128px' : '180px')}
|
||||
padding: 8px 12px;
|
||||
background-color: ${({ selected, theme }) => (selected ? theme.buttonBackgroundPlain : theme.royalBlue)};
|
||||
color: ${({ selected, theme }) => (selected ? theme.textColor : theme.white)};
|
||||
border: 1px solid
|
||||
${({ selected, theme }) => (selected ? theme.outlineGrey : theme.royalBlue)};
|
||||
border-radius: 8px;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
:hover {
|
||||
border: 1px solid
|
||||
${({ selected, theme }) => (selected ? darken(0.1, theme.outlineGrey) : darken(0.1, theme.royalBlue))};
|
||||
}
|
||||
|
||||
:focus {
|
||||
border: 1px solid ${({ selected, theme }) =>
|
||||
selected ? darken(0.1, theme.outlineGrey) : darken(0.1, theme.royalBlue)};
|
||||
}
|
||||
|
||||
:active {
|
||||
background-color: ${({ selected, theme }) => (selected ? theme.buttonBackgroundPlain : theme.royalBlue)};
|
||||
}
|
||||
`
|
||||
|
||||
const StyledDropDown = styled(DropDown)`
|
||||
height: 35%;
|
||||
|
||||
path {
|
||||
stroke: ${({ selected, theme }) => (selected ? theme.textColor : theme.white)};
|
||||
}
|
||||
`
|
||||
|
||||
const InputGroup = styled(AutoColumn)`
|
||||
position: relative;
|
||||
padding: 40px 0;
|
||||
`
|
||||
|
||||
const QRWrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid ${({ theme }) => theme.outlineGrey};
|
||||
background: #fbfbfb;
|
||||
padding: 4px;
|
||||
border-radius: 8px;
|
||||
`
|
||||
|
||||
const StyledInput = styled.input`
|
||||
width: ${({ width }) => width};
|
||||
border: none;
|
||||
outline: none;
|
||||
font-size: 20px;
|
||||
|
||||
::placeholder {
|
||||
color: #edeef2;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledNumerical = styled(NumericalInput)`
|
||||
text-align: center;
|
||||
font-size: 48px;
|
||||
font-weight: 500px;
|
||||
width: 100%;
|
||||
|
||||
::placeholder {
|
||||
color: #edeef2;
|
||||
}
|
||||
`
|
||||
|
||||
const MaxButton = styled.button`
|
||||
position: absolute;
|
||||
right: 70px;
|
||||
padding: 0.5rem 1rem;
|
||||
background-color: ${({ theme }) => theme.zumthorBlue};
|
||||
border: 1px solid ${({ theme }) => theme.zumthorBlue};
|
||||
border-radius: 0.5rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
margin-right: 0.5rem;
|
||||
color: ${({ theme }) => theme.royalBlue};
|
||||
:hover {
|
||||
border: 1px solid ${({ theme }) => theme.royalBlue};
|
||||
}
|
||||
:focus {
|
||||
border: 1px solid ${({ theme }) => theme.royalBlue};
|
||||
outline: none;
|
||||
}
|
||||
`
|
||||
|
||||
export default function Send() {
|
||||
const { account } = useWeb3React()
|
||||
|
||||
// setting for send with swap or regular swap
|
||||
const [withSwap, setWithSwap] = useState(true)
|
||||
|
||||
// modals
|
||||
const [modalOpen, setModalOpen] = useState(false)
|
||||
const [showConfirm, setShowConfirm] = useState(false)
|
||||
|
||||
// token selected
|
||||
const [activeTokenAddress, setActiveTokenAddress] = useState()
|
||||
const token = useToken(activeTokenAddress)
|
||||
|
||||
// user inputs
|
||||
const [typedValue, setTypedValue] = useState('')
|
||||
const [amount, setAmount] = useState(null)
|
||||
const [recipient, setRecipient] = useState('0x74Aa01d162E6dC6A657caC857418C403D48E2D77')
|
||||
|
||||
//ENS
|
||||
const recipientENS = useENSName(recipient)
|
||||
|
||||
// balances
|
||||
const userBalance = useAddressBalance(account, token)
|
||||
|
||||
//errors
|
||||
const [generalError, setGeneralError] = useState('')
|
||||
const [amountError, setAmountError] = useState('')
|
||||
const [recipientError, setRecipientError] = useState('')
|
||||
|
||||
function parseInputAmount(newtypedValue) {
|
||||
setTypedValue(newtypedValue)
|
||||
if (!!token && newtypedValue !== '' && newtypedValue !== '.') {
|
||||
const typedValueParsed = parseUnits(newtypedValue, token.decimals).toString()
|
||||
setAmount(new TokenAmount(token, typedValueParsed))
|
||||
}
|
||||
}
|
||||
|
||||
function onMax() {
|
||||
if (userBalance) {
|
||||
setTypedValue(userBalance.toExact())
|
||||
setAmount(userBalance)
|
||||
}
|
||||
}
|
||||
|
||||
const atMax = amount && userBalance && JSBI.equal(amount.raw, userBalance.raw) ? true : false
|
||||
|
||||
//error detection
|
||||
useEffect(() => {
|
||||
setGeneralError('')
|
||||
setRecipientError('')
|
||||
setAmountError('')
|
||||
|
||||
if (!amount) {
|
||||
setGeneralError('Enter an amount')
|
||||
}
|
||||
if (!isAddress(recipient)) {
|
||||
setRecipientError('Enter a valid address')
|
||||
}
|
||||
if (!!!token) {
|
||||
setGeneralError('Select a token')
|
||||
}
|
||||
if (amount && userBalance && JSBI.greaterThan(amount.raw, userBalance.raw)) {
|
||||
setAmountError('Insufficient Balance')
|
||||
}
|
||||
}, [recipient, token, amount, userBalance])
|
||||
|
||||
const TopContent = () => {
|
||||
return (
|
||||
<AutoColumn gap="30px" style={{ marginTop: '40px' }}>
|
||||
<RowBetween>
|
||||
<Text fontSize={36} fontWeight={500}>
|
||||
{amount?.toFixed(8)}
|
||||
</Text>
|
||||
<TokenLogo address={activeTokenAddress} size={'30px'} />
|
||||
</RowBetween>
|
||||
<ArrowDown size={24} color="#888D9B" />
|
||||
<TYPE.blue fontSize={36}>
|
||||
{recipient?.slice(0, 6)}...{recipient?.slice(36, 42)}
|
||||
</TYPE.blue>
|
||||
</AutoColumn>
|
||||
)
|
||||
}
|
||||
|
||||
const BottomContent = () => {
|
||||
return (
|
||||
<AutoColumn>
|
||||
<ButtonPrimary>
|
||||
<Text color="white" fontSize={20}>
|
||||
Confirm send
|
||||
</Text>
|
||||
</ButtonPrimary>
|
||||
</AutoColumn>
|
||||
)
|
||||
}
|
||||
|
||||
const [attemptedSend, setAttemptedSend] = useState(false) // clicke confirm
|
||||
const [pendingConfirmation, setPendingConfirmation] = useState(true) // waiting for
|
||||
|
||||
return withSwap ? (
|
||||
<ExchangePage sendingInput={true} />
|
||||
) : (
|
||||
<>
|
||||
<SearchModal
|
||||
isOpen={modalOpen}
|
||||
onDismiss={() => {
|
||||
setModalOpen(false)
|
||||
}}
|
||||
filterType="tokens"
|
||||
onTokenSelect={tokenAddress => setActiveTokenAddress(tokenAddress)}
|
||||
/>
|
||||
<ConfirmationModal
|
||||
isOpen={showConfirm}
|
||||
onDismiss={() => setShowConfirm(false)}
|
||||
hash=""
|
||||
title="Confirm Send"
|
||||
topContent={TopContent}
|
||||
bottomContent={BottomContent}
|
||||
attemptingTxn={attemptedSend}
|
||||
pendingConfirmation={pendingConfirmation}
|
||||
pendingText=""
|
||||
/>
|
||||
</>
|
||||
)
|
||||
return <ExchangePage sendingInput={true} />
|
||||
}
|
||||
|
@ -2,10 +2,10 @@ import React, { useReducer, useState, useCallback, useEffect } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { ethers } from 'ethers'
|
||||
import { parseUnits, parseEther } from '@ethersproject/units'
|
||||
import { WETH, TokenAmount, JSBI, Percent, Route } from '@uniswap/sdk'
|
||||
import { WETH, TokenAmount, JSBI, Percent, Route, Token, Exchange } from '@uniswap/sdk'
|
||||
|
||||
import DoubleLogo from '../../components/DoubleLogo'
|
||||
import TokenLogo from '../../components/TokenLogo'
|
||||
import DoubleLogo from '../../components/DoubleLogo'
|
||||
import SearchModal from '../../components/SearchModal'
|
||||
import PositionCard from '../../components/PositionCard'
|
||||
import ConfirmationModal from '../../components/ConfirmationModal'
|
||||
@ -17,8 +17,8 @@ import { AutoColumn, ColumnCenter } from '../../components/Column'
|
||||
import Row, { RowBetween, RowFlat, RowFixed } from '../../components/Row'
|
||||
|
||||
import { useToken } from '../../contexts/Tokens'
|
||||
import { useWeb3React } from '../../hooks'
|
||||
import { usePopups } from '../../contexts/Application'
|
||||
import { useWeb3React } from '../../hooks'
|
||||
import { useAddressBalance } from '../../contexts/Balances'
|
||||
import { useAddressAllowance } from '../../contexts/Allowances'
|
||||
import { useTransactionAdder } from '../../contexts/Transactions'
|
||||
@ -27,6 +27,7 @@ import { useExchange, useTotalSupply } from '../../contexts/Exchanges'
|
||||
import { BigNumber } from 'ethers/utils'
|
||||
import { ROUTER_ADDRESSES } from '../../constants'
|
||||
import { getRouterContract, calculateGasMargin } from '../../utils'
|
||||
import { TYPE } from '../../theme'
|
||||
|
||||
// denominated in bips
|
||||
const ALLOWED_SLIPPAGE = 200
|
||||
@ -134,12 +135,9 @@ function reducer(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo should we ever not have prepopulated tokens?
|
||||
*
|
||||
*/
|
||||
export default function AddLiquidity({ token0, token1 }) {
|
||||
const { account, chainId, library } = useWeb3React()
|
||||
const [, addPopup] = usePopups()
|
||||
|
||||
const routerAddress: string = ROUTER_ADDRESSES[chainId]
|
||||
|
||||
@ -152,28 +150,28 @@ export default function AddLiquidity({ token0, token1 }) {
|
||||
// input state
|
||||
const [state, dispatch] = useReducer(reducer, initializeAddState(token0, token1))
|
||||
const { independentField, typedValue, ...fieldData } = state
|
||||
const dependentField = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
|
||||
const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
|
||||
|
||||
// get basic SDK entities
|
||||
const tokens = {
|
||||
const tokens: { [field: number]: Token } = {
|
||||
[Field.INPUT]: useToken(fieldData[Field.INPUT].address),
|
||||
[Field.OUTPUT]: useToken(fieldData[Field.OUTPUT].address)
|
||||
}
|
||||
|
||||
// exhchange data
|
||||
const exchange = useExchange(tokens[Field.INPUT], tokens[Field.OUTPUT])
|
||||
const route = exchange ? new Route([exchange], tokens[independentField]) : undefined
|
||||
const totalSupply = useTotalSupply(exchange)
|
||||
const [noLiquidity, setNoLiquidity] = useState<boolean>(false)
|
||||
const exchange: Exchange = useExchange(tokens[Field.INPUT], tokens[Field.OUTPUT])
|
||||
const route: Route = exchange ? new Route([exchange], tokens[independentField]) : undefined
|
||||
const totalSupply: TokenAmount = useTotalSupply(exchange)
|
||||
const [noLiquidity, setNoLiquidity] = useState<boolean>(false) // used to detect new exchange
|
||||
|
||||
// state for amount approvals
|
||||
const inputApproval = useAddressAllowance(account, tokens[Field.INPUT], routerAddress)
|
||||
const outputApproval = useAddressAllowance(account, tokens[Field.OUTPUT], routerAddress)
|
||||
const inputApproval: TokenAmount = useAddressAllowance(account, tokens[Field.INPUT], routerAddress)
|
||||
const outputApproval: TokenAmount = useAddressAllowance(account, tokens[Field.OUTPUT], routerAddress)
|
||||
const [showInputUnlock, setShowInputUnlock] = useState<boolean>(false)
|
||||
const [showOutputUnlock, setShowOutputUnlock] = useState<boolean>(false)
|
||||
|
||||
// get user-pecific and token-specific lookup data
|
||||
const userBalances = {
|
||||
const userBalances: { [field: number]: TokenAmount } = {
|
||||
[Field.INPUT]: useAddressBalance(account, tokens[Field.INPUT]),
|
||||
[Field.OUTPUT]: useAddressBalance(account, tokens[Field.OUTPUT])
|
||||
}
|
||||
@ -219,7 +217,6 @@ export default function AddLiquidity({ token0, token1 }) {
|
||||
if (typedValueParsed !== '0')
|
||||
parsedAmounts[independentField] = new TokenAmount(tokens[independentField], typedValueParsed)
|
||||
} catch (error) {
|
||||
// should only fail if the user specifies too many decimal places of precision (or maybe exceed max uint?)
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
@ -240,12 +237,16 @@ export default function AddLiquidity({ token0, token1 }) {
|
||||
}
|
||||
|
||||
// check for estimated liquidity minted
|
||||
const liquidityMinted =
|
||||
!!exchange && !!parsedAmounts[Field.INPUT] && !!parsedAmounts[Field.OUTPUT] && !!totalSupply
|
||||
? exchange.getLiquidityMinted(totalSupply, parsedAmounts[Field.INPUT], parsedAmounts[Field.OUTPUT])
|
||||
const liquidityMinted: TokenAmount =
|
||||
!!exchange && !!parsedAmounts[Field.INPUT] && !!parsedAmounts[Field.OUTPUT]
|
||||
? exchange.getLiquidityMinted(
|
||||
totalSupply ? totalSupply : new TokenAmount(exchange?.liquidityToken, JSBI.BigInt(0)),
|
||||
parsedAmounts[Field.INPUT],
|
||||
parsedAmounts[Field.OUTPUT]
|
||||
)
|
||||
: undefined
|
||||
|
||||
const poolTokenPercentage =
|
||||
const poolTokenPercentage: Percent =
|
||||
!!liquidityMinted && !!totalSupply
|
||||
? new Percent(liquidityMinted.raw, totalSupply.add(liquidityMinted).raw)
|
||||
: undefined
|
||||
@ -271,10 +272,10 @@ export default function AddLiquidity({ token0, token1 }) {
|
||||
})
|
||||
}, [])
|
||||
|
||||
const MIN_ETHER = new TokenAmount(WETH[chainId], JSBI.BigInt(parseEther('.01')))
|
||||
const MIN_ETHER: TokenAmount = new TokenAmount(WETH[chainId], JSBI.BigInt(parseEther('.01')))
|
||||
|
||||
// get the max amounts user can add
|
||||
const [maxAmountInput, maxAmountOutput] = [Field.INPUT, Field.OUTPUT].map(index => {
|
||||
const [maxAmountInput, maxAmountOutput]: TokenAmount[] = [Field.INPUT, Field.OUTPUT].map(index => {
|
||||
const field = Field[index]
|
||||
return !!userBalances[Field[field]] &&
|
||||
JSBI.greaterThan(
|
||||
@ -287,7 +288,7 @@ export default function AddLiquidity({ token0, token1 }) {
|
||||
: undefined
|
||||
})
|
||||
|
||||
const [atMaxAmountInput, atMaxAmountOutput] = [Field.INPUT, Field.OUTPUT].map(index => {
|
||||
const [atMaxAmountInput, atMaxAmountOutput]: boolean[] = [Field.INPUT, Field.OUTPUT].map(index => {
|
||||
const field = Field[index]
|
||||
const maxAmount = index === Field.INPUT ? maxAmountInput : maxAmountOutput
|
||||
return !!maxAmount && !!parsedAmounts[Field[field]]
|
||||
@ -357,22 +358,23 @@ export default function AddLiquidity({ token0, token1 }) {
|
||||
|
||||
// state for txn
|
||||
const addTransaction = useTransactionAdder()
|
||||
const [txHash, setTxHash] = useState()
|
||||
const [txHash, setTxHash] = useState<string>('')
|
||||
|
||||
// format ETH value for transaction
|
||||
function hex(value: JSBI) {
|
||||
return ethers.utils.bigNumberify(value.toString())
|
||||
}
|
||||
|
||||
// calculate slippage bounds based on current reserves
|
||||
function calculateSlippageAmount(value: TokenAmount): JSBI[] {
|
||||
if (value && value.raw) {
|
||||
const offset = JSBI.divide(JSBI.multiply(JSBI.BigInt(ALLOWED_SLIPPAGE), value.raw), JSBI.BigInt(10000))
|
||||
return [JSBI.subtract(value.raw, offset), JSBI.add(value.raw, offset)]
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const [, addPopup] = usePopups()
|
||||
|
||||
async function onAdd() {
|
||||
setAttemptingTxn(true)
|
||||
const router = getRouterContract(chainId, library, account)
|
||||
@ -387,6 +389,7 @@ export default function AddLiquidity({ token0, token1 }) {
|
||||
if (tokens[Field.INPUT] === WETH[chainId] || tokens[Field.OUTPUT] === WETH[chainId]) {
|
||||
method = router.addLiquidityETH
|
||||
estimate = router.estimate.addLiquidityETH
|
||||
|
||||
args = [
|
||||
tokens[Field.OUTPUT] === WETH[chainId] ? tokens[Field.INPUT].address : tokens[Field.OUTPUT].address, // token
|
||||
tokens[Field.OUTPUT] === WETH[chainId] // token desired
|
||||
@ -466,60 +469,48 @@ export default function AddLiquidity({ token0, token1 }) {
|
||||
return (
|
||||
<>
|
||||
<RowBetween>
|
||||
<Text color="#565A69" fontWeight={500} fontSize={16}>
|
||||
{tokens[Field.INPUT]?.symbol} Deposited
|
||||
</Text>
|
||||
<TYPE.body>{tokens[Field.INPUT]?.symbol} Deposited</TYPE.body>
|
||||
<RowFixed>
|
||||
<TokenLogo address={tokens[Field.INPUT]?.address || ''} style={{ marginRight: '8px' }} />
|
||||
<Text fontWeight={500} fontSize={16}>
|
||||
{!!parsedAmounts[Field.INPUT] && parsedAmounts[Field.INPUT].toSignificant(6)}
|
||||
</Text>
|
||||
<TYPE.body>{!!parsedAmounts[Field.INPUT] && parsedAmounts[Field.INPUT].toSignificant(6)}</TYPE.body>
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
<RowBetween>
|
||||
<Text color="#565A69" fontWeight={500} fontSize={16}>
|
||||
{tokens[Field.OUTPUT]?.symbol} Deposited
|
||||
</Text>
|
||||
<TYPE.body>{tokens[Field.OUTPUT]?.symbol} Deposited</TYPE.body>
|
||||
<RowFixed>
|
||||
<TokenLogo address={tokens[Field.OUTPUT]?.address || ''} style={{ marginRight: '8px' }} />
|
||||
<Text fontWeight={500} fontSize={16}>
|
||||
{!!parsedAmounts[Field.OUTPUT] && parsedAmounts[Field.OUTPUT].toSignificant(6)}
|
||||
</Text>
|
||||
<TYPE.body>{!!parsedAmounts[Field.OUTPUT] && parsedAmounts[Field.OUTPUT].toSignificant(6)}</TYPE.body>
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
{route && !JSBI.equal(route?.midPrice?.raw?.denominator, JSBI.BigInt(0)) && (
|
||||
<RowBetween>
|
||||
<TYPE.body>Rate</TYPE.body>
|
||||
<TYPE.body>
|
||||
{`1 ${tokens[Field.INPUT]?.symbol} = ${route?.midPrice &&
|
||||
route?.midPrice?.raw?.denominator &&
|
||||
route?.midPrice?.adjusted?.toFixed(8)} ${tokens[Field.OUTPUT]?.symbol}`}
|
||||
</TYPE.body>
|
||||
</RowBetween>
|
||||
)}
|
||||
<RowBetween>
|
||||
<Text color="#565A69" fontWeight={500} fontSize={16}>
|
||||
Rate
|
||||
</Text>
|
||||
<Text fontWeight={500} fontSize={16}>
|
||||
{`1 ${tokens[Field.INPUT]?.symbol} = ${route?.midPrice &&
|
||||
route?.midPrice?.raw?.denominator &&
|
||||
route.midPrice.adjusted.toFixed(8)} ${tokens[Field.OUTPUT]?.symbol}`}
|
||||
</Text>
|
||||
</RowBetween>
|
||||
<RowBetween>
|
||||
<Text color="#565A69" fontWeight={500} fontSize={16}>
|
||||
Minted Pool Share:
|
||||
</Text>
|
||||
<Text fontWeight={500} fontSize={16}>
|
||||
{poolTokenPercentage?.toFixed(6) + '%'}
|
||||
</Text>
|
||||
<TYPE.body>Minted Pool Share:</TYPE.body>
|
||||
<TYPE.body>{noLiquidity ? '100%' : poolTokenPercentage?.toFixed(6) + '%'}</TYPE.body>
|
||||
</RowBetween>
|
||||
<ButtonPrimary style={{ margin: '20px 0' }} onClick={onAdd}>
|
||||
<Text fontWeight={500} fontSize={20}>
|
||||
Confirm Supply
|
||||
</Text>
|
||||
</ButtonPrimary>
|
||||
<Text fontSize={12} color="#565A69" textAlign="center">
|
||||
<TYPE.italic fontSize={12} color="#565A69" textAlign="center">
|
||||
{`Output is estimated. You will receive at least ${liquidityMinted?.toFixed(6)} UNI ${
|
||||
tokens[Field.INPUT]?.symbol
|
||||
}/${tokens[Field.OUTPUT]?.symbol} or the transaction will revert.`}
|
||||
</Text>
|
||||
</TYPE.italic>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const pendingText = `Supplied ${parsedAmounts[Field.INPUT]?.toSignificant(6)} ${
|
||||
const pendingText: string = `Supplied ${parsedAmounts[Field.INPUT]?.toSignificant(6)} ${
|
||||
tokens[Field.INPUT]?.symbol
|
||||
} ${'and'} ${parsedAmounts[Field.OUTPUT]?.toSignificant(6)} ${tokens[Field.OUTPUT]?.symbol}`
|
||||
|
||||
@ -549,12 +540,12 @@ export default function AddLiquidity({ token0, token1 }) {
|
||||
<AutoColumn gap="20px">
|
||||
{noLiquidity && (
|
||||
<ColumnCenter>
|
||||
<Text fontWeight={500} style={{ textAlign: 'center' }}>
|
||||
<TYPE.main textAlign="center">
|
||||
<span role="img" aria-label="Thinking">
|
||||
🥇
|
||||
</span>{' '}
|
||||
You are the first to add liquidity. Make sure you're setting rates correctly.
|
||||
</Text>
|
||||
</TYPE.main>
|
||||
</ColumnCenter>
|
||||
)}
|
||||
<CurrencyInputPanel
|
||||
@ -592,13 +583,15 @@ export default function AddLiquidity({ token0, token1 }) {
|
||||
showUnlock={showOutputUnlock}
|
||||
disableTokenSelect
|
||||
/>
|
||||
<RowBetween>
|
||||
Rate:
|
||||
<div>
|
||||
1 {tokens[independentField].symbol} = {route?.midPrice?.toSignificant(6)}
|
||||
{tokens[dependentField].symbol}
|
||||
</div>
|
||||
</RowBetween>
|
||||
{!noLiquidity && (
|
||||
<RowBetween>
|
||||
Rate:
|
||||
<div>
|
||||
1 {tokens[independentField].symbol} = {route?.midPrice?.toSignificant(6)}
|
||||
{tokens[dependentField].symbol}
|
||||
</div>
|
||||
</RowBetween>
|
||||
)}
|
||||
<ButtonPrimary
|
||||
onClick={() => {
|
||||
setShowConfirm(true)
|
||||
|
@ -1,157 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { withRouter } from 'react-router'
|
||||
import { createBrowserHistory } from 'history'
|
||||
import { ethers } from 'ethers'
|
||||
import styled from 'styled-components'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ReactGA from 'react-ga'
|
||||
|
||||
import { useWeb3React, useFactoryContract } from '../../hooks'
|
||||
import { Button } from '../../theme'
|
||||
import AddressInputPanel from '../../components/AddressInputPanel'
|
||||
import OversizedPanel from '../../components/OversizedPanel'
|
||||
import { useTokenDetails } from '../../contexts/Tokens'
|
||||
import { useTransactionAdder } from '../../contexts/Transactions'
|
||||
|
||||
const SummaryPanel = styled.div`
|
||||
${({ theme }) => theme.flexColumnNoWrap};
|
||||
padding: 1rem 0;
|
||||
`
|
||||
|
||||
const ExchangeRateWrapper = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap};
|
||||
align-items: center;
|
||||
color: ${({ theme }) => theme.doveGray};
|
||||
font-size: 0.75rem;
|
||||
padding: 0.25rem 1rem 0;
|
||||
`
|
||||
|
||||
const ExchangeRate = styled.span`
|
||||
flex: 1 1 auto;
|
||||
width: 0;
|
||||
color: ${({ theme }) => theme.doveGray};
|
||||
`
|
||||
|
||||
const CreateExchangeWrapper = styled.div`
|
||||
color: ${({ theme }) => theme.doveGray};
|
||||
text-align: center;
|
||||
margin-top: 1rem;
|
||||
padding-top: 1rem;
|
||||
`
|
||||
|
||||
const SummaryText = styled.div`
|
||||
font-size: 0.75rem;
|
||||
color: ${({ error, theme }) => error && theme.salmonRed};
|
||||
`
|
||||
|
||||
const Flex = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 2rem;
|
||||
|
||||
button {
|
||||
max-width: 20rem;
|
||||
}
|
||||
`
|
||||
|
||||
function CreateExchange({ location, params }) {
|
||||
const { t } = useTranslation()
|
||||
const { account } = useWeb3React()
|
||||
|
||||
const factory = useFactoryContract()
|
||||
|
||||
const [tokenAddress, setTokenAddress] = useState({
|
||||
address: params.tokenAddress ? params.tokenAddress : '',
|
||||
name: ''
|
||||
})
|
||||
const [tokenAddressError, setTokenAddressError] = useState()
|
||||
|
||||
const { name, symbol, decimals, exchangeAddress } = useTokenDetails(tokenAddress.address)
|
||||
const addTransaction = useTransactionAdder()
|
||||
|
||||
// clear url of query
|
||||
useEffect(() => {
|
||||
const history = createBrowserHistory()
|
||||
history.push(window.location.pathname + '')
|
||||
}, [])
|
||||
|
||||
// validate everything
|
||||
const [errorMessage, setErrorMessage] = useState(!account && t('noWallet'))
|
||||
useEffect(() => {
|
||||
if (tokenAddressError) {
|
||||
setErrorMessage(t('invalidTokenAddress'))
|
||||
} else if (symbol === undefined || decimals === undefined || exchangeAddress === undefined) {
|
||||
setErrorMessage()
|
||||
} else if (symbol === null) {
|
||||
setErrorMessage(t('invalidSymbol'))
|
||||
} else if (decimals === null) {
|
||||
setErrorMessage(t('invalidDecimals'))
|
||||
} else if (exchangeAddress !== ethers.constants.AddressZero) {
|
||||
setErrorMessage(t('exchangeExists'))
|
||||
} else if (!account) {
|
||||
setErrorMessage(t('noWallet'))
|
||||
} else {
|
||||
setErrorMessage(null)
|
||||
}
|
||||
|
||||
return () => {
|
||||
setErrorMessage()
|
||||
}
|
||||
}, [tokenAddress.address, symbol, decimals, exchangeAddress, account, t, tokenAddressError])
|
||||
|
||||
async function createExchange() {
|
||||
const estimatedGasLimit = await factory.estimate.createExchange(tokenAddress.address)
|
||||
|
||||
factory.createExchange(tokenAddress.address, { gasLimit: estimatedGasLimit }).then(response => {
|
||||
ReactGA.event({
|
||||
category: 'Transaction',
|
||||
action: 'Create Exchange'
|
||||
})
|
||||
|
||||
addTransaction(response)
|
||||
})
|
||||
}
|
||||
|
||||
const isValid = errorMessage === null
|
||||
|
||||
return (
|
||||
<>
|
||||
<AddressInputPanel
|
||||
title={t('tokenAddress')}
|
||||
initialInput={
|
||||
params.tokenAddress
|
||||
? { address: params.tokenAddress }
|
||||
: { address: (location.state && location.state.tokenAddress) || '' }
|
||||
}
|
||||
onChange={setTokenAddress}
|
||||
onError={setTokenAddressError}
|
||||
/>
|
||||
<OversizedPanel hideBottom>
|
||||
<SummaryPanel>
|
||||
<ExchangeRateWrapper>
|
||||
<ExchangeRate>{t('name')}</ExchangeRate>
|
||||
<span>{name ? name : ' - '}</span>
|
||||
</ExchangeRateWrapper>
|
||||
<ExchangeRateWrapper>
|
||||
<ExchangeRate>{t('symbol')}</ExchangeRate>
|
||||
<span>{symbol ? symbol : ' - '}</span>
|
||||
</ExchangeRateWrapper>
|
||||
<ExchangeRateWrapper>
|
||||
<ExchangeRate>{t('decimals')}</ExchangeRate>
|
||||
<span>{decimals || decimals === 0 ? decimals : ' - '}</span>
|
||||
</ExchangeRateWrapper>
|
||||
</SummaryPanel>
|
||||
</OversizedPanel>
|
||||
<CreateExchangeWrapper>
|
||||
<SummaryText>{errorMessage ? errorMessage : t('enterTokenCont')}</SummaryText>
|
||||
</CreateExchangeWrapper>
|
||||
<Flex>
|
||||
<Button disabled={!isValid} onClick={createExchange}>
|
||||
{t('createExchange')}
|
||||
</Button>
|
||||
</Flex>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default withRouter(CreateExchange)
|
@ -2,7 +2,7 @@ import React, { useReducer, useState, useCallback, useEffect } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { ethers } from 'ethers'
|
||||
import { parseUnits } from '@ethersproject/units'
|
||||
import { TokenAmount, JSBI, Route, WETH, Percent } from '@uniswap/sdk'
|
||||
import { TokenAmount, JSBI, Route, WETH, Percent, Token, Exchange } from '@uniswap/sdk'
|
||||
|
||||
import Slider from '../../components/Slider'
|
||||
import TokenLogo from '../../components/TokenLogo'
|
||||
@ -21,8 +21,8 @@ import Row, { RowBetween, RowFixed } from '../../components/Row'
|
||||
import { useToken } from '../../contexts/Tokens'
|
||||
import { useWeb3React } from '../../hooks'
|
||||
import { useAllBalances } from '../../contexts/Balances'
|
||||
import { useTransactionAdder } from '../../contexts/Transactions'
|
||||
import { useExchangeContract } from '../../hooks'
|
||||
import { useTransactionAdder } from '../../contexts/Transactions'
|
||||
import { useExchange, useTotalSupply } from '../../contexts/Exchanges'
|
||||
|
||||
import { BigNumber } from 'ethers/utils'
|
||||
@ -145,34 +145,34 @@ const ConfirmedText = styled(Text)`
|
||||
|
||||
export default function RemoveLiquidity({ token0, token1 }) {
|
||||
const { account, chainId, library } = useWeb3React()
|
||||
const routerAddress = ROUTER_ADDRESSES[chainId]
|
||||
const routerAddress: string = ROUTER_ADDRESSES[chainId]
|
||||
|
||||
const [showConfirm, setShowConfirm] = useState(false)
|
||||
const [showAdvanced, setShowAdvanced] = useState(false)
|
||||
const [showConfirm, setShowConfirm] = useState<boolean>(false)
|
||||
const [showAdvanced, setShowAdvanced] = useState<boolean>(false)
|
||||
|
||||
const inputToken = useToken(token0)
|
||||
const outputToken = useToken(token1)
|
||||
const inputToken: Token = useToken(token0)
|
||||
const outputToken: Token = useToken(token1)
|
||||
|
||||
// get basic SDK entities
|
||||
const tokens = {
|
||||
const tokens: { [field: number]: Token } = {
|
||||
[Field.TOKEN0]: inputToken,
|
||||
[Field.TOKEN1]: outputToken
|
||||
}
|
||||
|
||||
const exchange = useExchange(inputToken, outputToken)
|
||||
const exchangeContract = useExchangeContract(exchange?.liquidityToken.address)
|
||||
const exchange: Exchange = useExchange(inputToken, outputToken)
|
||||
const exchangeContract: ethers.Contract = useExchangeContract(exchange?.liquidityToken.address)
|
||||
|
||||
// pool token data
|
||||
const totalPoolTokens = useTotalSupply(exchange)
|
||||
const totalPoolTokens: TokenAmount = useTotalSupply(exchange)
|
||||
|
||||
const allBalances = useAllBalances()
|
||||
const userLiquidity = allBalances?.[account]?.[exchange?.liquidityToken?.address]
|
||||
const allBalances: TokenAmount[] = useAllBalances()
|
||||
const userLiquidity: TokenAmount = allBalances?.[account]?.[exchange?.liquidityToken?.address]
|
||||
|
||||
// input state
|
||||
const [state, dispatch] = useReducer(reducer, initializeRemoveState(userLiquidity?.toExact(), token0, token1))
|
||||
const { independentField, typedValue } = state
|
||||
|
||||
const TokensDeposited = {
|
||||
const TokensDeposited: { [field: number]: TokenAmount } = {
|
||||
[Field.TOKEN0]:
|
||||
exchange &&
|
||||
totalPoolTokens &&
|
||||
@ -185,7 +185,7 @@ export default function RemoveLiquidity({ token0, token1 }) {
|
||||
exchange.getLiquidityValue(tokens[Field.TOKEN1], totalPoolTokens, userLiquidity, false)
|
||||
}
|
||||
|
||||
const route = exchange
|
||||
const route: Route = exchange
|
||||
? new Route([exchange], independentField !== Field.LIQUIDITY ? tokens[independentField] : tokens[Field.TOKEN1])
|
||||
: undefined
|
||||
|
||||
@ -307,11 +307,11 @@ export default function RemoveLiquidity({ token0, token1 }) {
|
||||
: false
|
||||
|
||||
// errors
|
||||
const [generalError, setGeneralError] = useState('')
|
||||
const [inputError, setInputError] = useState('')
|
||||
const [outputError, setOutputError] = useState('')
|
||||
const [poolTokenError, setPoolTokenError] = useState('')
|
||||
const [isValid, setIsValid] = useState(false)
|
||||
const [generalError, setGeneralError] = useState<string>('')
|
||||
const [inputError, setInputError] = useState<string>('')
|
||||
const [outputError, setOutputError] = useState<string>('')
|
||||
const [poolTokenError, setPoolTokenError] = useState<string>('')
|
||||
const [isValid, setIsValid] = useState<boolean>(false)
|
||||
|
||||
// update errors live
|
||||
useEffect(() => {
|
||||
@ -351,7 +351,7 @@ export default function RemoveLiquidity({ token0, token1 }) {
|
||||
const addTransaction = useTransactionAdder()
|
||||
const [txHash, setTxHash] = useState()
|
||||
const [sigInputs, setSigInputs] = useState([])
|
||||
const [deadline, setDeadline] = useState()
|
||||
const [deadline, setDeadline] = useState(null)
|
||||
const [signed, setSigned] = useState(false) // waiting for signature sign
|
||||
const [attemptedRemoval, setAttemptedRemoval] = useState(false) // clicke confirm
|
||||
const [pendingConfirmation, setPendingConfirmation] = useState(true) // waiting for
|
||||
@ -359,7 +359,7 @@ export default function RemoveLiquidity({ token0, token1 }) {
|
||||
async function onSign() {
|
||||
const nonce = await exchangeContract.nonces(account)
|
||||
|
||||
const newDeadline = Math.ceil(Date.now() / 1000) + DEADLINE_FROM_NOW
|
||||
const newDeadline: number = Math.ceil(Date.now() / 1000) + DEADLINE_FROM_NOW
|
||||
setDeadline(newDeadline)
|
||||
|
||||
const EIP712Domain = [
|
||||
@ -428,6 +428,7 @@ export default function RemoveLiquidity({ token0, token1 }) {
|
||||
: parsedAmounts[Field.TOKEN0].raw.toString(),
|
||||
account,
|
||||
deadline,
|
||||
false,
|
||||
sigInputs[0],
|
||||
sigInputs[1],
|
||||
sigInputs[2]
|
||||
@ -445,6 +446,7 @@ export default function RemoveLiquidity({ token0, token1 }) {
|
||||
parsedAmounts[Field.TOKEN1].raw.toString(),
|
||||
account,
|
||||
deadline,
|
||||
false,
|
||||
sigInputs[0],
|
||||
sigInputs[1],
|
||||
sigInputs[2]
|
||||
@ -466,18 +468,13 @@ export default function RemoveLiquidity({ token0, token1 }) {
|
||||
setTxHash(response.hash)
|
||||
addTransaction(response)
|
||||
})
|
||||
.catch(() => {
|
||||
.catch(e => {
|
||||
console.log(e)
|
||||
resetModalState()
|
||||
setShowConfirm(false)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo
|
||||
* if the input values stay the same,
|
||||
* we should probably not reset the signature values,
|
||||
* move to an effect
|
||||
*/
|
||||
function resetModalState() {
|
||||
setSigned(false)
|
||||
setSigInputs(null)
|
||||
@ -562,7 +559,7 @@ export default function RemoveLiquidity({ token0, token1 }) {
|
||||
</>
|
||||
)
|
||||
}
|
||||
const pendingText = `Removed ${parsedAmounts[Field.TOKEN0]?.toSignificant(6)} ${
|
||||
const pendingText: string = `Removed ${parsedAmounts[Field.TOKEN0]?.toSignificant(6)} ${
|
||||
tokens[Field.TOKEN0]?.symbol
|
||||
} and ${parsedAmounts[Field.TOKEN1]?.toSignificant(6)} ${tokens[Field.TOKEN1]?.symbol}`
|
||||
|
||||
|
@ -72,7 +72,7 @@ const theme = darkMode => ({
|
||||
tokenRowHover: darkMode ? '#404040' : '#F2F2F2',
|
||||
|
||||
outlineGrey: darkMode ? '#292C2F' : '#EDEEF2',
|
||||
darkGrey: darkMode ? '#888D9B' : '#888D9B',
|
||||
darkGray: darkMode ? '#888D9B' : '#888D9B',
|
||||
|
||||
//blacks
|
||||
charcoalBlack: darkMode ? '#F2F2F2' : '#404040',
|
||||
@ -85,6 +85,7 @@ const theme = darkMode => ({
|
||||
|
||||
// purples
|
||||
wisteriaPurple: '#DC6BE5',
|
||||
|
||||
// reds
|
||||
salmonRed: '#FF6871',
|
||||
// orange
|
||||
@ -93,6 +94,7 @@ const theme = darkMode => ({
|
||||
warningYellow: '#FFE270',
|
||||
// pink
|
||||
uniswapPink: '#DC6BE5',
|
||||
darkPink: '#ff007a',
|
||||
//green
|
||||
connectedGreen: '#27AE60',
|
||||
|
||||
@ -124,6 +126,16 @@ export const TYPE = {
|
||||
{children}
|
||||
</Text>
|
||||
),
|
||||
largeHeader: ({ children, ...rest }) => (
|
||||
<Text fontWeight={600} fontSize={24} color={theme().black} {...rest}>
|
||||
{children}
|
||||
</Text>
|
||||
),
|
||||
body: ({ children, ...rest }) => (
|
||||
<Text fontWeight={500} fontSize={16} color={'#565A69'} {...rest}>
|
||||
{children}
|
||||
</Text>
|
||||
),
|
||||
blue: ({ children, ...rest }) => (
|
||||
<Text fontWeight={500} color={theme().royalBlue} {...rest}>
|
||||
{children}
|
||||
@ -134,6 +146,11 @@ export const TYPE = {
|
||||
{children}
|
||||
</Text>
|
||||
),
|
||||
darkGray: ({ children, ...rest }) => (
|
||||
<Text fontWeight={500} color={theme().darkGray} {...rest}>
|
||||
{children}
|
||||
</Text>
|
||||
),
|
||||
italic: ({ children, ...rest }) => (
|
||||
<Text fontWeight={500} fontSize={12} fontStyle={'italic'} color={theme().mineshaftGray} {...rest}>
|
||||
{children}
|
||||
|
@ -5,7 +5,7 @@ import EXCHANGE_ABI from '../constants/abis/exchange'
|
||||
import ROUTER_ABI from '../constants/abis/router'
|
||||
import ERC20_ABI from '../constants/abis/erc20'
|
||||
import ERC20_BYTES32_ABI from '../constants/abis/erc20_bytes32'
|
||||
import { FACTORY_ADDRESSES, SUPPORTED_THEMES } from '../constants'
|
||||
import { FACTORY_ADDRESSES, SUPPORTED_THEMES, ROUTER_ADDRESSES } from '../constants'
|
||||
import { bigNumberify, keccak256, defaultAbiCoder, toUtf8Bytes, solidityPack } from 'ethers/utils'
|
||||
|
||||
import UncheckedJsonRpcSigner from './signer'
|
||||
@ -131,8 +131,8 @@ export function getContract(address, ABI, library, account) {
|
||||
}
|
||||
|
||||
// account is optional
|
||||
export function getRouterContract(networkId, library, account) {
|
||||
const router = getContract('0xd9210Ff5A0780E083BB40e30d005d93a2DcFA4EF', ROUTER_ABI, library, account)
|
||||
export function getRouterContract(chainId, library, account) {
|
||||
const router = getContract(ROUTER_ADDRESSES[chainId], ROUTER_ABI, library, account)
|
||||
return router
|
||||
}
|
||||
|
||||
|
32
yarn.lock
32
yarn.lock
@ -1319,7 +1319,7 @@
|
||||
"@ethersproject/logger" ">=5.0.0-beta.129"
|
||||
"@ethersproject/strings" ">=5.0.0-beta.130"
|
||||
|
||||
"@ethersproject/keccak256@>=5.0.0-beta.127", "@ethersproject/keccak256@^5.0.0-beta.130", "@ethersproject/keccak256@^5.0.0-beta.131":
|
||||
"@ethersproject/keccak256@>=5.0.0-beta.127", "@ethersproject/keccak256@^5.0.0-beta.130":
|
||||
version "5.0.0-beta.131"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.0.0-beta.131.tgz#b5778723ee75208065b9b9ad30c71d480f41bb31"
|
||||
integrity sha512-KQnqMwGV0IMOjAr/UTFO8DuLrmN1uaMvcV3zh9hiXhh3rCuY+WXdeUh49w1VQ94kBKmaP0qfGb7z4SdhUWUHjw==
|
||||
@ -1382,6 +1382,15 @@
|
||||
dependencies:
|
||||
"@ethersproject/bytes" ">=5.0.0-beta.129"
|
||||
|
||||
"@ethersproject/sha2@>=5.0.0-beta.129":
|
||||
version "5.0.0-beta.135"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.0.0-beta.135.tgz#e597572ba991fe044d50f8d75704bb4a2b2c64b4"
|
||||
integrity sha512-DK/cUT5ilCVLtf1xk7XDPB9xGHsXiU3TsULKsEg823cTBIhFl2l0IiHAGqu9uiMlSJRpb2BwrWQuMgmFe/vMwQ==
|
||||
dependencies:
|
||||
"@ethersproject/bytes" ">=5.0.0-beta.129"
|
||||
"@ethersproject/logger" ">=5.0.0-beta.129"
|
||||
hash.js "1.1.3"
|
||||
|
||||
"@ethersproject/signing-key@>=5.0.0-beta.129":
|
||||
version "5.0.0-beta.135"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.0.0-beta.135.tgz#f739e800aad9e01b77a8ec2c353b9b66ce5738fa"
|
||||
@ -1392,6 +1401,17 @@
|
||||
"@ethersproject/properties" ">=5.0.0-beta.131"
|
||||
elliptic "6.5.2"
|
||||
|
||||
"@ethersproject/solidity@^5.0.0-beta.131":
|
||||
version "5.0.0-beta.131"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.0.0-beta.131.tgz#7d826e98cc0a29e25f0ff52ae17c07483f5d93d4"
|
||||
integrity sha512-i5vuj2CXGMkVPo08bmElC2cvhjRDNRZZ8nzvx2WCi75Zh42xD0XNV77E9ZLYgS0WoZSiAi/F71nXSBnM7FAqJg==
|
||||
dependencies:
|
||||
"@ethersproject/bignumber" ">=5.0.0-beta.130"
|
||||
"@ethersproject/bytes" ">=5.0.0-beta.129"
|
||||
"@ethersproject/keccak256" ">=5.0.0-beta.127"
|
||||
"@ethersproject/sha2" ">=5.0.0-beta.129"
|
||||
"@ethersproject/strings" ">=5.0.0-beta.130"
|
||||
|
||||
"@ethersproject/strings@>=5.0.0-beta.130":
|
||||
version "5.0.0-beta.136"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.0.0-beta.136.tgz#053cbf4f9f96a7537cbc50300597f2d707907f51"
|
||||
@ -2365,16 +2385,16 @@
|
||||
semver "^6.3.0"
|
||||
tsutils "^3.17.1"
|
||||
|
||||
"@uniswap/sdk@@uniswap/sdk@2.0.0-beta.17":
|
||||
version "2.0.0-beta.17"
|
||||
resolved "https://registry.yarnpkg.com/@uniswap/sdk/-/sdk-2.0.0-beta.17.tgz#8f24be0375d5f8137eae75afe75b2356c75bb793"
|
||||
integrity sha512-Nd3S/VE51z4jsNs9G9hkslUkS862dpslnU86lXEJi7mbbbPIagh31iR0s/uBPnrBFGiktucgvzRn6WJJIvojWA==
|
||||
"@uniswap/sdk@@uniswap/sdk@2.0.0-beta.19":
|
||||
version "2.0.0-beta.19"
|
||||
resolved "https://registry.yarnpkg.com/@uniswap/sdk/-/sdk-2.0.0-beta.19.tgz#1f0228a1d5451d62f209e09c48cd1d6bea5ffe01"
|
||||
integrity sha512-mqDZkeX2TU7e3yOOKHSeUryv94//mJ6dsU6dCv6FExrfOY9yi6+O5ZWpe43rVi0XFVdQGRIRhJapdWKWaGsung==
|
||||
dependencies:
|
||||
"@ethersproject/address" "^5.0.0-beta.134"
|
||||
"@ethersproject/contracts" "^5.0.0-beta.143"
|
||||
"@ethersproject/keccak256" "^5.0.0-beta.131"
|
||||
"@ethersproject/networks" "^5.0.0-beta.135"
|
||||
"@ethersproject/providers" "^5.0.0-beta.153"
|
||||
"@ethersproject/solidity" "^5.0.0-beta.131"
|
||||
big.js "^5.2.2"
|
||||
decimal.js-light "^2.5.0"
|
||||
jsbi "^3.1.1"
|
||||
|
Loading…
Reference in New Issue
Block a user