Commit 6211dff0 authored by ianlapham's avatar ianlapham

typed pages

parent 655b7956
......@@ -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>
......
import React, { useState } from 'react'
import styled from 'styled-components'
import QuestionHelper from '../Question'
import NumericalInput from '../NumericalInput'
import { Link } from '../../theme/components'
import { TYPE } from '../../theme'
import { AutoColumn } from '../../components/Column'
import Row, { RowBetween, RowFixed } from '../../components/Row'
import { ButtonRadio } from '../Button'
const InputWrapper = styled(RowBetween)`
width: 200px;
background-color: ${({ theme }) => theme.inputBackground};
border-radius: 8px;
padding: 4px 8px;
border: 1px solid transparent;
border: ${({ active, error, theme }) =>
error ? '1px solid ' + theme.salmonRed : active ? '1px solid ' + theme.royalBlue : ''};
`
const SLIPPAGE_INDEX = {
1: 1,
2: 2,
3: 3,
4: 4
}
export default function AdvancedSettings({ setIsOpen, setDeadline, setAllowedSlippage }) {
const [deadlineInput, setDeadlineInput] = useState(15)
const [slippageInput, setSlippageInput] = useState()
const [activeIndex, setActiveIndex] = useState(SLIPPAGE_INDEX[3])
const [slippageInputError, setSlippageInputError] = useState(null) // error
function parseCustomInput(val) {
const acceptableValues = [/^$/, /^\d{1,2}$/, /^\d{0,2}\.\d{0,2}$/]
if (val > 5) {
setSlippageInputError('Your transaction may be front-run.')
} else {
setSlippageInputError(null)
}
if (acceptableValues.some(a => a.test(val))) {
setSlippageInput(val)
setAllowedSlippage(val * 100)
}
}
function parseCustomDeadline(val) {
const acceptableValues = [/^$/, /^\d+$/]
if (acceptableValues.some(re => re.test(val))) {
setDeadlineInput(val)
setDeadline(val * 60)
}
}
return (
<AutoColumn gap="20px">
<Link
onClick={() => {
setIsOpen(false)
}}
>
back
</Link>
<RowBetween>
<TYPE.main>Limit additional price impact</TYPE.main>
<QuestionHelper text="" />
</RowBetween>
<Row>
<ButtonRadio
active={SLIPPAGE_INDEX[1] === activeIndex}
padding="4px 6px"
borderRadius="8px"
style={{ marginRight: '16px' }}
width={'60px'}
onClick={() => {
setActiveIndex(SLIPPAGE_INDEX[1])
setAllowedSlippage(10)
}}
>
0.1%
</ButtonRadio>
<ButtonRadio
active={SLIPPAGE_INDEX[2] === activeIndex}
padding="4px 6px"
borderRadius="8px"
style={{ marginRight: '16px' }}
width={'60px'}
onClick={() => {
setActiveIndex(SLIPPAGE_INDEX[2])
setAllowedSlippage(100)
}}
>
1%
</ButtonRadio>
<ButtonRadio
active={SLIPPAGE_INDEX[3] === activeIndex}
padding="4px"
borderRadius="8px"
width={'140px'}
onClick={() => {
setActiveIndex(SLIPPAGE_INDEX[3])
setAllowedSlippage(200)
}}
>
2% (suggested)
</ButtonRadio>
</Row>
<RowFixed>
<InputWrapper active={SLIPPAGE_INDEX[4] === activeIndex} error={slippageInputError}>
<NumericalInput
align={slippageInput ? 'right' : 'left'}
value={slippageInput || ''}
onUserInput={val => {
parseCustomInput(val)
setActiveIndex(SLIPPAGE_INDEX[4])
}}
placeHolder="Custom"
onClick={() => {
setActiveIndex(SLIPPAGE_INDEX[4])
if (slippageInput) {
parseCustomInput(slippageInput)
}
}}
/>
%
</InputWrapper>
{slippageInputError && (
<TYPE.error error={true} fontSize={12} style={{ marginLeft: '10px' }}>
Your transaction may be front-run
</TYPE.error>
)}
</RowFixed>
<RowBetween>
<TYPE.main>Adjust deadline (minutes from now)</TYPE.main>
</RowBetween>
<RowFixed>
<InputWrapper>
<NumericalInput
value={deadlineInput}
onUserInput={val => {
parseCustomDeadline(val)
}}
/>
</InputWrapper>
</RowFixed>
</AutoColumn>
)
}
import React from 'react'
import { 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>
......
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>
)
}
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import ReactGA from 'react-ga'
import { ReactComponent as Dropup } from '../../assets/images/dropup-blue.svg'
import { ReactComponent as Dropdown } from '../../assets/images/dropdown-blue.svg'
const SummaryWrapper = styled.div`
color: ${({ error, theme }) => (error ? theme.salmonRed : theme.doveGray)};
font-size: 0.75rem;
text-align: center;
margin-top: 1rem;
padding-top: 1rem;
`
const Details = styled.div`
background-color: ${({ theme }) => theme.concreteGray};
padding: 1.5rem;
border-radius: 1rem;
font-size: 0.75rem;
margin-top: 1rem;
`
const SummaryWrapperContainer = styled.div`
${({ theme }) => theme.flexRowNoWrap};
color: ${({ theme }) => theme.royalBlue};
text-align: center;
margin-top: 1rem;
padding-top: 1rem;
cursor: pointer;
align-items: center;
justify-content: center;
font-size: 0.75rem;
span {
margin-right: 12px;
}
img {
height: 0.75rem;
width: 0.75rem;
}
`
const WrappedDropup = ({ isError, highSlippageWarning, ...rest }) => <Dropup {...rest} />
const ColoredDropup = styled(WrappedDropup)`
path {
stroke: ${({ theme }) => theme.royalBlue};
}
`
const WrappedDropdown = ({ isError, highSlippageWarning, ...rest }) => <Dropdown {...rest} />
const ColoredDropdown = styled(WrappedDropdown)`
path {
stroke: ${({ theme }) => theme.royalBlue};
}
`
class ContextualInfo extends Component {
static propTypes = {
openDetailsText: PropTypes.string,
renderTransactionDetails: PropTypes.func,
contextualInfo: PropTypes.string,
isError: PropTypes.bool
}
static defaultProps = {
openDetailsText: 'Advanced Details',
closeDetailsText: 'Hide Advanced',
renderTransactionDetails() {},
contextualInfo: '',
isError: false
}
state = {
showDetails: false
}
renderDetails() {
if (!this.state.showDetails) {
return null
}
return <Details>{this.props.renderTransactionDetails()}</Details>
}
render() {
const { openDetailsText, closeDetailsText, contextualInfo, isError } = this.props
if (contextualInfo) {
return <SummaryWrapper error={isError}>{contextualInfo}</SummaryWrapper>
}
return (
<>
<SummaryWrapperContainer
onClick={() => {
!this.state.showDetails &&
ReactGA.event({
category: 'Advanced Interaction',
action: 'Open Advanced Details',
label: 'Pool Page Details'
})
this.setState(prevState => {
return { showDetails: !prevState.showDetails }
})
}}
>
{!this.state.showDetails ? (
<>
<span>{openDetailsText}</span>
<ColoredDropup />
</>
) : (
<>
<span>{closeDetailsText}</span>
<ColoredDropdown />
</>
)}
</SummaryWrapperContainer>
{this.renderDetails()}
</>
)
}
}
export default ContextualInfo
import React, { useState } from 'react'
import styled, { css } from 'styled-components'
import { transparentize } from 'polished'
import ReactGA from 'react-ga'
import { ReactComponent as Dropup } from '../../assets/images/dropup-blue.svg'
import { ReactComponent as Dropdown } from '../../assets/images/dropdown-blue.svg'
const SummaryWrapper = styled.div`
color: ${({ error, brokenTokenWarning, theme }) => (error || brokenTokenWarning ? theme.salmonRed : theme.doveGray)};
font-size: 0.75rem;
text-align: center;
margin-top: 1rem;
padding-top: 1rem;
`
const SummaryWrapperContainer = styled.div`
${({ theme }) => theme.flexRowNoWrap};
color: ${({ theme }) => theme.royalBlue};
text-align: center;
margin-top: 1rem;
padding-top: 1rem;
cursor: pointer;
align-items: center;
justify-content: center;
font-size: 0.75rem;
img {
height: 0.75rem;
width: 0.75rem;
}
`
const Details = styled.div`
background-color: ${({ theme }) => theme.concreteGray};
/* padding: 1.25rem 1.25rem 1rem 1.25rem; */
border-radius: 1rem;
font-size: 0.75rem;
margin: 1rem 0.5rem 0 0.5rem;
`
const ErrorSpan = styled.span`
margin-right: 12px;
font-size: 0.75rem;
line-height: 0.75rem;
color: ${({ isError, theme }) => isError && theme.salmonRed};
${({ slippageWarning, highSlippageWarning, theme }) =>
highSlippageWarning
? css`
color: ${theme.salmonRed};
font-weight: 600;
`
: slippageWarning &&
css`
background-color: ${transparentize(0.6, theme.warningYellow)};
font-weight: 600;
padding: 0.25rem;
`}
`
const WrappedDropup = ({ isError, highSlippageWarning, ...rest }) => <Dropup {...rest} />
const ColoredDropup = styled(WrappedDropup)`
path {
stroke: ${({ isError, theme }) => (isError ? theme.salmonRed : theme.royalBlue)};
${({ highSlippageWarning, theme }) =>
highSlippageWarning &&
css`
stroke: ${theme.salmonRed};
`}
}
`
const WrappedDropdown = ({ isError, highSlippageWarning, ...rest }) => <Dropdown {...rest} />
const ColoredDropdown = styled(WrappedDropdown)`
path {
stroke: ${({ isError, theme }) => (isError ? theme.salmonRed : theme.royalBlue)};
${({ highSlippageWarning, theme }) =>
highSlippageWarning &&
css`
stroke: ${theme.salmonRed};
`}
}
`
export default function ContextualInfo({
openDetailsText = 'Advanced Details',
closeDetailsText = 'Hide Advanced',
contextualInfo = '',
allowExpand = false,
isError = false,
slippageWarning,
highSlippageWarning,
brokenTokenWarning,
dropDownContent
}) {
const [showDetails, setShowDetails] = useState(false)
return !allowExpand ? (
<SummaryWrapper brokenTokenWarning={brokenTokenWarning}>{contextualInfo}</SummaryWrapper>
) : (
<>
<SummaryWrapperContainer
onClick={() => {
!showDetails &&
ReactGA.event({
category: 'Advanced Interaction',
action: 'Open Advanced Details',
label: 'Swap/Send Page Details'
})
setShowDetails(s => !s)
}}
>
<>
<ErrorSpan isError={isError} slippageWarning={slippageWarning} highSlippageWarning={highSlippageWarning}>
{(slippageWarning || highSlippageWarning) && (
<span role="img" aria-label="warning">
⚠️
</span>
)}
{contextualInfo ? contextualInfo : showDetails ? closeDetailsText : openDetailsText}
</ErrorSpan>
{showDetails ? (
<ColoredDropup isError={isError} highSlippageWarning={highSlippageWarning} />
) : (
<ColoredDropdown isError={isError} highSlippageWarning={highSlippageWarning} />
)}
</>
</SummaryWrapperContainer>
{showDetails && <Details>{dropDownContent()}</Details>}
</>
)
}
......@@ -8,6 +8,7 @@ import { WETH } from '@uniswap/sdk'
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>
......
This diff is collapsed.
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>
......
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 token0 = useToken(token0Address)
const token1 = useToken(token1Address)
const exchange = useExchange(token0, token1)
const [token0Address, setToken0Address] = useState<string>()
const [token1Address, setToken1Address] = useState<string>()
const position = useAddressBalance(account, exchange?.liquidityToken)
const token0: Token = useToken(token0Address)
const token1: Token = useToken(token1Address)
const newExchange = exchange && JSBI.equal(exchange.reserve0.raw, JSBI.BigInt(0))
const exchange: Exchange = useExchange(token0, token1)
const position: TokenAmount = useAddressBalance(account, exchange?.liquidityToken)
const allowImport = position && JSBI.greaterThan(position.raw, JSBI.BigInt(0))
const newExchange: boolean = exchange && JSBI.equal(exchange.reserve0.raw, JSBI.BigInt(0))
const allowImport: boolean = position && JSBI.greaterThan(position.raw, JSBI.BigInt(0))
function endSearch() {
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 allTokens = useAllTokens()
const allExchanges = useAllExchanges()
const allBalances = useAllBalances()
const [searchQuery, setSearchQuery] = useState('')
const [sortDirection, setSortDirection] = useState(true)
// get all exchanges
const allExchanges = useAllExchanges()
const token = useToken(searchQuery)
const tokenAddress = token && token.address
// get all tokens
const allTokens = useAllTokens()
// all balances for both account and exchanges
let allBalances = useAllBalances()
// amount of tokens to display at once
const [, setTokensShown] = useState(0)
const [, setPairsShown] = useState(0)
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 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 Object.keys(allExchanges).filter(exchangeAddress => {
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: {
......
This diff is collapsed.
......@@ -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)
......
[
{
"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"
}
]
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])
}
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
......
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} />
}
This diff is collapsed.
import React, { useState, useEffect } from 'react'
import { withRouter } from 'react-router'
import { createBrowserHistory } from 'history'
import { ethers } from 'ethers'
import styled from 'styled-components'
import { useTranslation } from 'react-i18next'
import ReactGA from 'react-ga'
import { useWeb3React, useFactoryContract } from '../../hooks'
import { Button } from '../../theme'
import AddressInputPanel from '../../components/AddressInputPanel'
import OversizedPanel from '../../components/OversizedPanel'
import { useTokenDetails } from '../../contexts/Tokens'
import { useTransactionAdder } from '../../contexts/Transactions'
const SummaryPanel = styled.div`
${({ theme }) => theme.flexColumnNoWrap};
padding: 1rem 0;
`
const ExchangeRateWrapper = styled.div`
${({ theme }) => theme.flexRowNoWrap};
align-items: center;
color: ${({ theme }) => theme.doveGray};
font-size: 0.75rem;
padding: 0.25rem 1rem 0;
`
const ExchangeRate = styled.span`
flex: 1 1 auto;
width: 0;
color: ${({ theme }) => theme.doveGray};
`
const CreateExchangeWrapper = styled.div`
color: ${({ theme }) => theme.doveGray};
text-align: center;
margin-top: 1rem;
padding-top: 1rem;
`
const SummaryText = styled.div`
font-size: 0.75rem;
color: ${({ error, theme }) => error && theme.salmonRed};
`
const Flex = styled.div`
display: flex;
justify-content: center;
padding: 2rem;
button {
max-width: 20rem;
}
`
function CreateExchange({ location, params }) {
const { t } = useTranslation()
const { account } = useWeb3React()
const factory = useFactoryContract()
const [tokenAddress, setTokenAddress] = useState({
address: params.tokenAddress ? params.tokenAddress : '',
name: ''
})
const [tokenAddressError, setTokenAddressError] = useState()
const { name, symbol, decimals, exchangeAddress } = useTokenDetails(tokenAddress.address)
const addTransaction = useTransactionAdder()
// clear url of query
useEffect(() => {
const history = createBrowserHistory()
history.push(window.location.pathname + '')
}, [])
// validate everything
const [errorMessage, setErrorMessage] = useState(!account && t('noWallet'))
useEffect(() => {
if (tokenAddressError) {
setErrorMessage(t('invalidTokenAddress'))
} else if (symbol === undefined || decimals === undefined || exchangeAddress === undefined) {
setErrorMessage()
} else if (symbol === null) {
setErrorMessage(t('invalidSymbol'))
} else if (decimals === null) {
setErrorMessage(t('invalidDecimals'))
} else if (exchangeAddress !== ethers.constants.AddressZero) {
setErrorMessage(t('exchangeExists'))
} else if (!account) {
setErrorMessage(t('noWallet'))
} else {
setErrorMessage(null)
}
return () => {
setErrorMessage()
}
}, [tokenAddress.address, symbol, decimals, exchangeAddress, account, t, tokenAddressError])
async function createExchange() {
const estimatedGasLimit = await factory.estimate.createExchange(tokenAddress.address)
factory.createExchange(tokenAddress.address, { gasLimit: estimatedGasLimit }).then(response => {
ReactGA.event({
category: 'Transaction',
action: 'Create Exchange'
})
addTransaction(response)
})
}
const isValid = errorMessage === null
return (
<>
<AddressInputPanel
title={t('tokenAddress')}
initialInput={
params.tokenAddress
? { address: params.tokenAddress }
: { address: (location.state && location.state.tokenAddress) || '' }
}
onChange={setTokenAddress}
onError={setTokenAddressError}
/>
<OversizedPanel hideBottom>
<SummaryPanel>
<ExchangeRateWrapper>
<ExchangeRate>{t('name')}</ExchangeRate>
<span>{name ? name : ' - '}</span>
</ExchangeRateWrapper>
<ExchangeRateWrapper>
<ExchangeRate>{t('symbol')}</ExchangeRate>
<span>{symbol ? symbol : ' - '}</span>
</ExchangeRateWrapper>
<ExchangeRateWrapper>
<ExchangeRate>{t('decimals')}</ExchangeRate>
<span>{decimals || decimals === 0 ? decimals : ' - '}</span>
</ExchangeRateWrapper>
</SummaryPanel>
</OversizedPanel>
<CreateExchangeWrapper>
<SummaryText>{errorMessage ? errorMessage : t('enterTokenCont')}</SummaryText>
</CreateExchangeWrapper>
<Flex>
<Button disabled={!isValid} onClick={createExchange}>
{t('createExchange')}
</Button>
</Flex>
</>
)
}
export default withRouter(CreateExchange)
......@@ -2,7 +2,7 @@ import React, { useReducer, useState, useCallback, useEffect } from 'react'
import styled from 'styled-components'
import { ethers } from 'ethers'
import { parseUnits } from '@ethersproject/units'
import { TokenAmount, JSBI, Route, WETH, Percent } from '@uniswap/sdk'
import { TokenAmount, JSBI, Route, WETH, Percent, Token, Exchange } from '@uniswap/sdk'
import Slider from '../../components/Slider'
import TokenLogo from '../../components/TokenLogo'
......@@ -21,8 +21,8 @@ import Row, { RowBetween, RowFixed } from '../../components/Row'
import { useToken } from '../../contexts/Tokens'
import { useWeb3React } from '../../hooks'
import { useAllBalances } from '../../contexts/Balances'
import { useTransactionAdder } from '../../contexts/Transactions'
import { useExchangeContract } from '../../hooks'
import { useTransactionAdder } from '../../contexts/Transactions'
import { useExchange, useTotalSupply } from '../../contexts/Exchanges'
import { BigNumber } from 'ethers/utils'
......@@ -145,34 +145,34 @@ const ConfirmedText = styled(Text)`
export default function RemoveLiquidity({ token0, token1 }) {
const { account, chainId, library } = useWeb3React()
const routerAddress = ROUTER_ADDRESSES[chainId]
const routerAddress: string = ROUTER_ADDRESSES[chainId]
const [showConfirm, setShowConfirm] = useState(false)
const [showAdvanced, setShowAdvanced] = useState(false)
const [showConfirm, setShowConfirm] = useState<boolean>(false)
const [showAdvanced, setShowAdvanced] = useState<boolean>(false)
const inputToken = useToken(token0)
const outputToken = useToken(token1)
const inputToken: Token = useToken(token0)
const outputToken: Token = useToken(token1)
// get basic SDK entities
const tokens = {
const tokens: { [field: number]: Token } = {
[Field.TOKEN0]: inputToken,
[Field.TOKEN1]: outputToken
}
const exchange = useExchange(inputToken, outputToken)
const exchangeContract = useExchangeContract(exchange?.liquidityToken.address)
const exchange: Exchange = useExchange(inputToken, outputToken)
const exchangeContract: ethers.Contract = useExchangeContract(exchange?.liquidityToken.address)
// pool token data
const totalPoolTokens = useTotalSupply(exchange)
const totalPoolTokens: TokenAmount = useTotalSupply(exchange)
const allBalances = useAllBalances()
const userLiquidity = allBalances?.[account]?.[exchange?.liquidityToken?.address]
const allBalances: TokenAmount[] = useAllBalances()
const userLiquidity: TokenAmount = allBalances?.[account]?.[exchange?.liquidityToken?.address]
// input state
const [state, dispatch] = useReducer(reducer, initializeRemoveState(userLiquidity?.toExact(), token0, token1))
const { independentField, typedValue } = state
const TokensDeposited = {
const TokensDeposited: { [field: number]: TokenAmount } = {
[Field.TOKEN0]:
exchange &&
totalPoolTokens &&
......@@ -185,7 +185,7 @@ export default function RemoveLiquidity({ token0, token1 }) {
exchange.getLiquidityValue(tokens[Field.TOKEN1], totalPoolTokens, userLiquidity, false)
}
const route = exchange
const route: Route = exchange
? new Route([exchange], independentField !== Field.LIQUIDITY ? tokens[independentField] : tokens[Field.TOKEN1])
: undefined
......@@ -307,11 +307,11 @@ export default function RemoveLiquidity({ token0, token1 }) {
: false
// errors
const [generalError, setGeneralError] = useState('')
const [inputError, setInputError] = useState('')
const [outputError, setOutputError] = useState('')
const [poolTokenError, setPoolTokenError] = useState('')
const [isValid, setIsValid] = useState(false)
const [generalError, setGeneralError] = useState<string>('')
const [inputError, setInputError] = useState<string>('')
const [outputError, setOutputError] = useState<string>('')
const [poolTokenError, setPoolTokenError] = useState<string>('')
const [isValid, setIsValid] = useState<boolean>(false)
// update errors live
useEffect(() => {
......@@ -351,7 +351,7 @@ export default function RemoveLiquidity({ token0, token1 }) {
const addTransaction = useTransactionAdder()
const [txHash, setTxHash] = useState()
const [sigInputs, setSigInputs] = useState([])
const [deadline, setDeadline] = useState()
const [deadline, setDeadline] = useState(null)
const [signed, setSigned] = useState(false) // waiting for signature sign
const [attemptedRemoval, setAttemptedRemoval] = useState(false) // clicke confirm
const [pendingConfirmation, setPendingConfirmation] = useState(true) // waiting for
......@@ -359,7 +359,7 @@ export default function RemoveLiquidity({ token0, token1 }) {
async function onSign() {
const nonce = await exchangeContract.nonces(account)
const newDeadline = Math.ceil(Date.now() / 1000) + DEADLINE_FROM_NOW
const newDeadline: number = Math.ceil(Date.now() / 1000) + DEADLINE_FROM_NOW
setDeadline(newDeadline)
const EIP712Domain = [
......@@ -428,6 +428,7 @@ export default function RemoveLiquidity({ token0, token1 }) {
: parsedAmounts[Field.TOKEN0].raw.toString(),
account,
deadline,
false,
sigInputs[0],
sigInputs[1],
sigInputs[2]
......@@ -445,6 +446,7 @@ export default function RemoveLiquidity({ token0, token1 }) {
parsedAmounts[Field.TOKEN1].raw.toString(),
account,
deadline,
false,
sigInputs[0],
sigInputs[1],
sigInputs[2]
......@@ -466,18 +468,13 @@ export default function RemoveLiquidity({ token0, token1 }) {
setTxHash(response.hash)
addTransaction(response)
})
.catch(() => {
.catch(e => {
console.log(e)
resetModalState()
setShowConfirm(false)
})
}
/**
* @todo
* if the input values stay the same,
* we should probably not reset the signature values,
* move to an effect
*/
function resetModalState() {
setSigned(false)
setSigInputs(null)
......@@ -562,7 +559,7 @@ export default function RemoveLiquidity({ token0, token1 }) {
</>
)
}
const pendingText = `Removed ${parsedAmounts[Field.TOKEN0]?.toSignificant(6)} ${
const pendingText: string = `Removed ${parsedAmounts[Field.TOKEN0]?.toSignificant(6)} ${
tokens[Field.TOKEN0]?.symbol
} and ${parsedAmounts[Field.TOKEN1]?.toSignificant(6)} ${tokens[Field.TOKEN1]?.symbol}`
......
......@@ -72,7 +72,7 @@ const theme = darkMode => ({
tokenRowHover: darkMode ? '#404040' : '#F2F2F2',
outlineGrey: darkMode ? '#292C2F' : '#EDEEF2',
darkGrey: darkMode ? '#888D9B' : '#888D9B',
darkGray: darkMode ? '#888D9B' : '#888D9B',
//blacks
charcoalBlack: darkMode ? '#F2F2F2' : '#404040',
......@@ -85,6 +85,7 @@ const theme = darkMode => ({
// purples
wisteriaPurple: '#DC6BE5',
// reds
salmonRed: '#FF6871',
// orange
......@@ -93,6 +94,7 @@ const theme = darkMode => ({
warningYellow: '#FFE270',
// pink
uniswapPink: '#DC6BE5',
darkPink: '#ff007a',
//green
connectedGreen: '#27AE60',
......@@ -124,6 +126,16 @@ export const TYPE = {
{children}
</Text>
),
largeHeader: ({ children, ...rest }) => (
<Text fontWeight={600} fontSize={24} color={theme().black} {...rest}>
{children}
</Text>
),
body: ({ children, ...rest }) => (
<Text fontWeight={500} fontSize={16} color={'#565A69'} {...rest}>
{children}
</Text>
),
blue: ({ children, ...rest }) => (
<Text fontWeight={500} color={theme().royalBlue} {...rest}>
{children}
......@@ -134,6 +146,11 @@ export const TYPE = {
{children}
</Text>
),
darkGray: ({ children, ...rest }) => (
<Text fontWeight={500} color={theme().darkGray} {...rest}>
{children}
</Text>
),
italic: ({ children, ...rest }) => (
<Text fontWeight={500} fontSize={12} fontStyle={'italic'} color={theme().mineshaftGray} {...rest}>
{children}
......
......@@ -5,7 +5,7 @@ import EXCHANGE_ABI from '../constants/abis/exchange'
import ROUTER_ABI from '../constants/abis/router'
import ERC20_ABI from '../constants/abis/erc20'
import ERC20_BYTES32_ABI from '../constants/abis/erc20_bytes32'
import { FACTORY_ADDRESSES, SUPPORTED_THEMES } from '../constants'
import { FACTORY_ADDRESSES, SUPPORTED_THEMES, ROUTER_ADDRESSES } from '../constants'
import { bigNumberify, keccak256, defaultAbiCoder, toUtf8Bytes, solidityPack } from 'ethers/utils'
import UncheckedJsonRpcSigner from './signer'
......@@ -131,8 +131,8 @@ export function getContract(address, ABI, library, account) {
}
// account is optional
export function getRouterContract(networkId, library, account) {
const router = getContract('0xd9210Ff5A0780E083BB40e30d005d93a2DcFA4EF', ROUTER_ABI, library, account)
export function getRouterContract(chainId, library, account) {
const router = getContract(ROUTER_ADDRESSES[chainId], ROUTER_ABI, library, account)
return router
}
......
......@@ -1319,7 +1319,7 @@
"@ethersproject/logger" ">=5.0.0-beta.129"
"@ethersproject/strings" ">=5.0.0-beta.130"
"@ethersproject/keccak256@>=5.0.0-beta.127", "@ethersproject/keccak256@^5.0.0-beta.130", "@ethersproject/keccak256@^5.0.0-beta.131":
"@ethersproject/keccak256@>=5.0.0-beta.127", "@ethersproject/keccak256@^5.0.0-beta.130":
version "5.0.0-beta.131"
resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.0.0-beta.131.tgz#b5778723ee75208065b9b9ad30c71d480f41bb31"
integrity sha512-KQnqMwGV0IMOjAr/UTFO8DuLrmN1uaMvcV3zh9hiXhh3rCuY+WXdeUh49w1VQ94kBKmaP0qfGb7z4SdhUWUHjw==
......@@ -1382,6 +1382,15 @@
dependencies:
"@ethersproject/bytes" ">=5.0.0-beta.129"
"@ethersproject/sha2@>=5.0.0-beta.129":
version "5.0.0-beta.135"
resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.0.0-beta.135.tgz#e597572ba991fe044d50f8d75704bb4a2b2c64b4"
integrity sha512-DK/cUT5ilCVLtf1xk7XDPB9xGHsXiU3TsULKsEg823cTBIhFl2l0IiHAGqu9uiMlSJRpb2BwrWQuMgmFe/vMwQ==
dependencies:
"@ethersproject/bytes" ">=5.0.0-beta.129"
"@ethersproject/logger" ">=5.0.0-beta.129"
hash.js "1.1.3"
"@ethersproject/signing-key@>=5.0.0-beta.129":
version "5.0.0-beta.135"
resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.0.0-beta.135.tgz#f739e800aad9e01b77a8ec2c353b9b66ce5738fa"
......@@ -1392,6 +1401,17 @@
"@ethersproject/properties" ">=5.0.0-beta.131"
elliptic "6.5.2"
"@ethersproject/solidity@^5.0.0-beta.131":
version "5.0.0-beta.131"
resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.0.0-beta.131.tgz#7d826e98cc0a29e25f0ff52ae17c07483f5d93d4"
integrity sha512-i5vuj2CXGMkVPo08bmElC2cvhjRDNRZZ8nzvx2WCi75Zh42xD0XNV77E9ZLYgS0WoZSiAi/F71nXSBnM7FAqJg==
dependencies:
"@ethersproject/bignumber" ">=5.0.0-beta.130"
"@ethersproject/bytes" ">=5.0.0-beta.129"
"@ethersproject/keccak256" ">=5.0.0-beta.127"
"@ethersproject/sha2" ">=5.0.0-beta.129"
"@ethersproject/strings" ">=5.0.0-beta.130"
"@ethersproject/strings@>=5.0.0-beta.130":
version "5.0.0-beta.136"
resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.0.0-beta.136.tgz#053cbf4f9f96a7537cbc50300597f2d707907f51"
......@@ -2365,16 +2385,16 @@
semver "^6.3.0"
tsutils "^3.17.1"
"@uniswap/sdk@@uniswap/sdk@2.0.0-beta.17":
version "2.0.0-beta.17"
resolved "https://registry.yarnpkg.com/@uniswap/sdk/-/sdk-2.0.0-beta.17.tgz#8f24be0375d5f8137eae75afe75b2356c75bb793"
integrity sha512-Nd3S/VE51z4jsNs9G9hkslUkS862dpslnU86lXEJi7mbbbPIagh31iR0s/uBPnrBFGiktucgvzRn6WJJIvojWA==
"@uniswap/sdk@@uniswap/sdk@2.0.0-beta.19":
version "2.0.0-beta.19"
resolved "https://registry.yarnpkg.com/@uniswap/sdk/-/sdk-2.0.0-beta.19.tgz#1f0228a1d5451d62f209e09c48cd1d6bea5ffe01"
integrity sha512-mqDZkeX2TU7e3yOOKHSeUryv94//mJ6dsU6dCv6FExrfOY9yi6+O5ZWpe43rVi0XFVdQGRIRhJapdWKWaGsung==
dependencies:
"@ethersproject/address" "^5.0.0-beta.134"
"@ethersproject/contracts" "^5.0.0-beta.143"
"@ethersproject/keccak256" "^5.0.0-beta.131"
"@ethersproject/networks" "^5.0.0-beta.135"
"@ethersproject/providers" "^5.0.0-beta.153"
"@ethersproject/solidity" "^5.0.0-beta.131"
big.js "^5.2.2"
decimal.js-light "^2.5.0"
jsbi "^3.1.1"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment