Token check (#610)
* update market rate calculation and input parsing for 0 decimal tokens * remove style bump and revert tokens * add custom warning for pasted tokens * update centering * upload dark theme * update copy and loading state * copy edits * update color * multiple cards
This commit is contained in:
parent
8290cf565f
commit
e05be71ea5
@ -18,7 +18,7 @@ import Modal from '../Modal'
|
||||
import TokenLogo from '../TokenLogo'
|
||||
import SearchIcon from '../../assets/images/magnifying-glass.svg'
|
||||
import { useTransactionAdder, usePendingApproval } from '../../contexts/Transactions'
|
||||
import { useTokenDetails, useAllTokenDetails } from '../../contexts/Tokens'
|
||||
import { useTokenDetails, useAllTokenDetails, INITIAL_TOKENS_CONTEXT } from '../../contexts/Tokens'
|
||||
import { useAddressBalance } from '../../contexts/Balances'
|
||||
import { ReactComponent as Close } from '../../assets/images/x.svg'
|
||||
import { transparentize } from 'polished'
|
||||
@ -244,6 +244,10 @@ const TokenFullName = styled.div`
|
||||
color: ${({ theme }) => theme.chaliceGray};
|
||||
`
|
||||
|
||||
const FadedSpan = styled.span`
|
||||
color: ${({ theme }) => theme.royalBlue};
|
||||
`
|
||||
|
||||
const TokenRowBalance = styled.div`
|
||||
font-size: 1rem;
|
||||
line-height: 20px;
|
||||
@ -284,7 +288,8 @@ export default function CurrencyInputPanel({
|
||||
disableTokenSelect,
|
||||
selectedTokenAddress = '',
|
||||
showUnlock,
|
||||
value
|
||||
value,
|
||||
urlAddedTokens
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@ -431,6 +436,7 @@ export default function CurrencyInputPanel({
|
||||
onDismiss={() => {
|
||||
setModalIsOpen(false)
|
||||
}}
|
||||
urlAddedTokens={urlAddedTokens}
|
||||
onTokenSelect={onCurrencySelected}
|
||||
allBalances={allBalances}
|
||||
/>
|
||||
@ -439,7 +445,7 @@ export default function CurrencyInputPanel({
|
||||
)
|
||||
}
|
||||
|
||||
function CurrencySelectModal({ isOpen, onDismiss, onTokenSelect }) {
|
||||
function CurrencySelectModal({ isOpen, onDismiss, onTokenSelect, urlAddedTokens }) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
@ -447,7 +453,7 @@ function CurrencySelectModal({ isOpen, onDismiss, onTokenSelect }) {
|
||||
|
||||
const allTokens = useAllTokenDetails()
|
||||
|
||||
const { account } = useWeb3React()
|
||||
const { account, chainId } = useWeb3React()
|
||||
|
||||
// BigNumber.js instance
|
||||
const ethPrice = useETHPriceInUSD()
|
||||
@ -589,13 +595,25 @@ function CurrencySelectModal({ isOpen, onDismiss, onTokenSelect }) {
|
||||
}
|
||||
|
||||
return filteredTokenList.map(({ address, symbol, name, balance, usdBalance }) => {
|
||||
const urlAdded = urlAddedTokens && urlAddedTokens.hasOwnProperty(address)
|
||||
const customAdded =
|
||||
address !== 'ETH' &&
|
||||
INITIAL_TOKENS_CONTEXT[chainId] &&
|
||||
!INITIAL_TOKENS_CONTEXT[chainId].hasOwnProperty(address) &&
|
||||
!urlAdded
|
||||
|
||||
return (
|
||||
<TokenModalRow key={address} onClick={() => _onTokenSelect(address)}>
|
||||
<TokenRowLeft>
|
||||
<TokenLogo address={address} size={'2rem'} />
|
||||
<TokenSymbolGroup>
|
||||
<span id="symbol">{symbol}</span>
|
||||
<TokenFullName>{name}</TokenFullName>
|
||||
<div>
|
||||
<span id="symbol">{symbol}</span>
|
||||
<FadedSpan>
|
||||
{urlAdded && '(Added by URL)'} {customAdded && '(Added by user)'}
|
||||
</FadedSpan>
|
||||
</div>
|
||||
<TokenFullName> {name}</TokenFullName>
|
||||
</TokenSymbolGroup>
|
||||
</TokenRowLeft>
|
||||
<TokenRowRight>
|
||||
|
@ -10,7 +10,7 @@ import { brokenTokens } from '../../constants'
|
||||
import { amountFormatter, calculateGasMargin } from '../../utils'
|
||||
|
||||
import { useExchangeContract } from '../../hooks'
|
||||
import { useTokenDetails } from '../../contexts/Tokens'
|
||||
import { useTokenDetails, INITIAL_TOKENS_CONTEXT } from '../../contexts/Tokens'
|
||||
import { useTransactionAdder } from '../../contexts/Transactions'
|
||||
import { useAddressBalance, useExchangeReserves } from '../../contexts/Balances'
|
||||
import { useAddressAllowance } from '../../contexts/Allowances'
|
||||
@ -23,6 +23,7 @@ import AddressInputPanel from '../AddressInputPanel'
|
||||
import OversizedPanel from '../OversizedPanel'
|
||||
import TransactionDetails from '../TransactionDetails'
|
||||
import ArrowDown from '../../assets/svg/SVGArrowDown'
|
||||
import WarningCard from '../WarningCard'
|
||||
|
||||
const INPUT = 0
|
||||
const OUTPUT = 1
|
||||
@ -248,7 +249,15 @@ function getMarketRate(
|
||||
|
||||
export default function ExchangePage({ initialCurrency, sending = false, params }) {
|
||||
const { t } = useTranslation()
|
||||
const { account, error } = useWeb3React()
|
||||
const { account, chainId, error } = useWeb3React()
|
||||
|
||||
const urlAddedTokens = {}
|
||||
if (params.inputCurrency) {
|
||||
urlAddedTokens[params.inputCurrency] = true
|
||||
}
|
||||
if (params.outputCurrency) {
|
||||
urlAddedTokens[params.outputCurrency] = true
|
||||
}
|
||||
|
||||
// BigNumber.js instance
|
||||
const ethPrice = useETHPriceInUSD()
|
||||
@ -710,10 +719,54 @@ export default function ExchangePage({ initialCurrency, sending = false, params
|
||||
|
||||
const toggleWalletModal = useWalletModalToggle()
|
||||
|
||||
const newInputDetected =
|
||||
inputCurrency !== 'ETH' && inputCurrency && !INITIAL_TOKENS_CONTEXT[chainId].hasOwnProperty(inputCurrency)
|
||||
|
||||
const newOutputDetected =
|
||||
outputCurrency !== 'ETH' && outputCurrency && !INITIAL_TOKENS_CONTEXT[chainId].hasOwnProperty(outputCurrency)
|
||||
|
||||
const [showInputWarning, setShowInputWarning] = useState(false)
|
||||
const [showOutputWarning, setShowOutputWarning] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (newInputDetected) {
|
||||
setShowInputWarning(true)
|
||||
} else {
|
||||
setShowInputWarning(false)
|
||||
}
|
||||
}, [newInputDetected, setShowInputWarning])
|
||||
|
||||
useEffect(() => {
|
||||
if (newOutputDetected) {
|
||||
setShowOutputWarning(true)
|
||||
} else {
|
||||
setShowOutputWarning(false)
|
||||
}
|
||||
}, [newOutputDetected, setShowOutputWarning])
|
||||
|
||||
return (
|
||||
<>
|
||||
{showInputWarning && (
|
||||
<WarningCard
|
||||
onDismiss={() => {
|
||||
setShowInputWarning(false)
|
||||
}}
|
||||
urlAddedTokens={urlAddedTokens}
|
||||
currency={inputCurrency}
|
||||
/>
|
||||
)}
|
||||
{showOutputWarning && (
|
||||
<WarningCard
|
||||
onDismiss={() => {
|
||||
setShowOutputWarning(false)
|
||||
}}
|
||||
urlAddedTokens={urlAddedTokens}
|
||||
currency={outputCurrency}
|
||||
/>
|
||||
)}
|
||||
<CurrencyInputPanel
|
||||
title={t('input')}
|
||||
urlAddedTokens={urlAddedTokens}
|
||||
description={inputValueFormatted && independentField === OUTPUT ? estimatedText : ''}
|
||||
extraText={inputBalanceFormatted && formatBalance(inputBalanceFormatted)}
|
||||
extraTextClickHander={() => {
|
||||
@ -764,6 +817,7 @@ export default function ExchangePage({ initialCurrency, sending = false, params
|
||||
title={t('output')}
|
||||
description={outputValueFormatted && independentField === INPUT ? estimatedText : ''}
|
||||
extraText={outputBalanceFormatted && formatBalance(outputBalanceFormatted)}
|
||||
urlAddedTokens={urlAddedTokens}
|
||||
onCurrencySelected={outputCurrency => {
|
||||
dispatchSwapState({
|
||||
type: 'SELECT_CURRENCY',
|
||||
|
180
src/components/WarningCard/index.js
Normal file
180
src/components/WarningCard/index.js
Normal file
@ -0,0 +1,180 @@
|
||||
import React, { useState } from 'react'
|
||||
import styled, { keyframes } from 'styled-components'
|
||||
|
||||
import { useWeb3React } from '../../hooks'
|
||||
import { useTokenDetails } from '../../contexts/Tokens'
|
||||
import { getEtherscanLink } from '../../utils'
|
||||
|
||||
import { Link } from '../../theme'
|
||||
import TokenLogo from '../TokenLogo'
|
||||
import { ReactComponent as Close } from '../../assets/images/x.svg'
|
||||
import question from '../../assets/images/question.svg'
|
||||
|
||||
const Flex = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 2rem;
|
||||
|
||||
button {
|
||||
max-width: 20rem;
|
||||
}
|
||||
`
|
||||
|
||||
const Wrapper = styled.div`
|
||||
background: rgba(243, 190, 30, 0.1);
|
||||
position: relative;
|
||||
padding: 1rem;
|
||||
border: 0.5px solid #f3be1e;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 20px;
|
||||
display: grid;
|
||||
grid-template-rows: 1fr 1fr 1fr;
|
||||
grid-row-gap: 10px;
|
||||
`
|
||||
|
||||
const Row = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-items: flex-start;
|
||||
& > * {
|
||||
margin-right: 6px;
|
||||
}
|
||||
`
|
||||
|
||||
const CloseColor = styled(Close)`
|
||||
color: #aeaeae;
|
||||
`
|
||||
|
||||
const CloseIcon = styled.div`
|
||||
position: absolute;
|
||||
right: 1rem;
|
||||
top: 14px;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
& > * {
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
}
|
||||
`
|
||||
|
||||
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;
|
||||
right: 110px;
|
||||
top: 4px;
|
||||
z-index: 10;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 0.6rem 1rem;
|
||||
line-height: 150%;
|
||||
background: ${({ theme }) => theme.backgroundColor};
|
||||
border: 1px solid ${({ theme }) => theme.mercuryGray};
|
||||
border-radius: 8px;
|
||||
animation: ${fadeIn} 0.15s linear;
|
||||
color: ${({ theme }) => theme.textColor};
|
||||
font-style: italic;
|
||||
|
||||
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);
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
left: 2px;
|
||||
top: 50px;
|
||||
`}
|
||||
`
|
||||
|
||||
const Text = styled.div`
|
||||
color: ${({ theme }) => theme.textColor};
|
||||
`
|
||||
|
||||
function WarningCard({ onDismiss, urlAddedTokens, currency }) {
|
||||
const [showPopup, setPopup] = useState()
|
||||
const { chainId } = useWeb3React()
|
||||
const { symbol: inputSymbol, name: inputName } = useTokenDetails(currency)
|
||||
const fromURL = urlAddedTokens.hasOwnProperty(currency)
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<CloseIcon onClick={() => onDismiss()}>
|
||||
<CloseColor alt={'close icon'} />
|
||||
</CloseIcon>
|
||||
<Row style={{ fontSize: '12px' }}>
|
||||
<Text>{fromURL ? 'Token imported by URL ' : 'Token imported by user'}</Text>
|
||||
<QuestionWrapper
|
||||
onClick={() => {
|
||||
setPopup(!showPopup)
|
||||
}}
|
||||
onMouseEnter={() => {
|
||||
setPopup(true)
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setPopup(false)
|
||||
}}
|
||||
>
|
||||
<HelpCircleStyled src={question} alt="popup" />
|
||||
</QuestionWrapper>
|
||||
{showPopup ? (
|
||||
<Popup>
|
||||
<Text>
|
||||
The Uniswap smart contracts are designed to support any ERC20 token on Ethereum. Any token can be loaded
|
||||
into the interface by entering its Ethereum address into the search field or passing it as a URL
|
||||
parameter. Be careful when interacting with imported tokens as they have not been verified.
|
||||
</Text>
|
||||
</Popup>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</Row>
|
||||
<Row>
|
||||
<TokenLogo address={currency} />
|
||||
<div style={{ fontWeight: 500 }}>{inputName && inputSymbol ? inputName + ' (' + inputSymbol + ')' : ''}</div>
|
||||
<Link style={{ fontWeight: 400 }} href={getEtherscanLink(chainId, currency, 'address')}>
|
||||
(View on Etherscan)
|
||||
</Link>
|
||||
</Row>
|
||||
<Row style={{ fontSize: '12px', fontStyle: 'italic' }}>
|
||||
<Text>Please verify the legitimacy of this token before making any transactions.</Text>
|
||||
</Row>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default WarningCard
|
@ -26,7 +26,7 @@ const ETH = {
|
||||
}
|
||||
}
|
||||
|
||||
const INITIAL_TOKENS_CONTEXT = {
|
||||
export const INITIAL_TOKENS_CONTEXT = {
|
||||
1: {
|
||||
'0xB6eD7644C69416d67B522e20bC294A9a9B405B31': {
|
||||
[NAME]: '0xBitcoin Token',
|
||||
@ -662,7 +662,6 @@ export function useTokenDetails(tokenAddress) {
|
||||
library
|
||||
) {
|
||||
let stale = false
|
||||
|
||||
const namePromise = getTokenName(tokenAddress, library).catch(() => null)
|
||||
const symbolPromise = getTokenSymbol(tokenAddress, library).catch(() => null)
|
||||
const decimalsPromise = getTokenDecimals(tokenAddress, library).catch(() => null)
|
||||
|
@ -10,11 +10,13 @@ import CurrencyInputPanel from '../../components/CurrencyInputPanel'
|
||||
import OversizedPanel from '../../components/OversizedPanel'
|
||||
import ContextualInfo from '../../components/ContextualInfo'
|
||||
import { ReactComponent as Plus } from '../../assets/images/plus-blue.svg'
|
||||
import WarningCard from '../../components/WarningCard'
|
||||
|
||||
import { useWeb3React, useExchangeContract } from '../../hooks'
|
||||
import { brokenTokens } from '../../constants'
|
||||
import { amountFormatter, calculateGasMargin } from '../../utils'
|
||||
import { useTransactionAdder } from '../../contexts/Transactions'
|
||||
import { useTokenDetails } from '../../contexts/Tokens'
|
||||
import { useTokenDetails, INITIAL_TOKENS_CONTEXT } from '../../contexts/Tokens'
|
||||
import { useAddressBalance, useExchangeReserves, useETHPriceInUSD } from '../../contexts/Balances'
|
||||
import { useAddressAllowance } from '../../contexts/Allowances'
|
||||
|
||||
@ -198,11 +200,16 @@ function getMarketRate(reserveETH, reserveToken, decimals, invert = false) {
|
||||
|
||||
export default function AddLiquidity({ params }) {
|
||||
const { t } = useTranslation()
|
||||
const { library, account, active } = useWeb3React()
|
||||
const { library, account, active, chainId } = useWeb3React()
|
||||
|
||||
// BigNumber.js instance
|
||||
const ethPrice = useETHPriceInUSD()
|
||||
|
||||
const urlAddedTokens = {}
|
||||
if (params.token) {
|
||||
urlAddedTokens[params.token] = true
|
||||
}
|
||||
|
||||
// clear url of query
|
||||
useEffect(() => {
|
||||
const history = createBrowserHistory()
|
||||
@ -578,6 +585,18 @@ export default function AddLiquidity({ params }) {
|
||||
const isValid =
|
||||
(inputError === null || outputError === null) && !zeroDecimalError && !showUnlock && !brokenTokenWarning
|
||||
|
||||
const newOutputDetected =
|
||||
outputCurrency !== 'ETH' && outputCurrency && !INITIAL_TOKENS_CONTEXT[chainId].hasOwnProperty(outputCurrency)
|
||||
|
||||
const [showOutputWarning, setShowOutputWarning] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (newOutputDetected) {
|
||||
setShowOutputWarning(true)
|
||||
} else {
|
||||
setShowOutputWarning(false)
|
||||
}
|
||||
}, [newOutputDetected, setShowOutputWarning])
|
||||
return (
|
||||
<>
|
||||
{isNewExchange ? (
|
||||
@ -591,7 +610,15 @@ export default function AddLiquidity({ params }) {
|
||||
<NewExchangeWarningText>{t('initialExchangeRate', { symbol })}</NewExchangeWarningText>
|
||||
</NewExchangeWarning>
|
||||
) : null}
|
||||
|
||||
{showOutputWarning && (
|
||||
<WarningCard
|
||||
onDismiss={() => {
|
||||
setShowOutputWarning(false)
|
||||
}}
|
||||
urlAddedTokens={urlAddedTokens}
|
||||
currency={outputCurrency}
|
||||
/>
|
||||
)}
|
||||
<CurrencyInputPanel
|
||||
title={t('deposit')}
|
||||
extraText={inputBalance && formatBalance(amountFormatter(inputBalance, 18, 4))}
|
||||
@ -625,6 +652,7 @@ export default function AddLiquidity({ params }) {
|
||||
extraText={
|
||||
outputBalance && decimals && formatBalance(amountFormatter(outputBalance, decimals, Math.min(decimals, 4)))
|
||||
}
|
||||
urlAddedTokens={urlAddedTokens}
|
||||
selectedTokenAddress={outputCurrency}
|
||||
onCurrencySelected={outputCurrency => {
|
||||
dispatchAddLiquidityState({ type: 'SELECT_CURRENCY', payload: outputCurrency })
|
||||
|
@ -7,7 +7,7 @@ import styled from 'styled-components'
|
||||
|
||||
import { useWeb3React, useExchangeContract } from '../../hooks'
|
||||
import { useTransactionAdder } from '../../contexts/Transactions'
|
||||
import { useTokenDetails } from '../../contexts/Tokens'
|
||||
import { useTokenDetails, INITIAL_TOKENS_CONTEXT } from '../../contexts/Tokens'
|
||||
import { useAddressBalance, useETHPriceInUSD } from '../../contexts/Balances'
|
||||
|
||||
import { calculateGasMargin, amountFormatter } from '../../utils'
|
||||
@ -18,6 +18,7 @@ import CurrencyInputPanel from '../../components/CurrencyInputPanel'
|
||||
import ContextualInfo from '../../components/ContextualInfo'
|
||||
import OversizedPanel from '../../components/OversizedPanel'
|
||||
import ArrowDown from '../../assets/svg/SVGArrowDown'
|
||||
import WarningCard from '../../components/WarningCard'
|
||||
|
||||
// denominated in bips
|
||||
const ALLOWED_SLIPPAGE = ethers.utils.bigNumberify(200)
|
||||
@ -144,7 +145,7 @@ function calculateSlippageBounds(value) {
|
||||
|
||||
export default function RemoveLiquidity({ params }) {
|
||||
const { t } = useTranslation()
|
||||
const { library, account, active } = useWeb3React()
|
||||
const { library, account, active, chainId } = useWeb3React()
|
||||
|
||||
const addTransaction = useTransactionAdder()
|
||||
|
||||
@ -194,6 +195,11 @@ export default function RemoveLiquidity({ params }) {
|
||||
const exchangeETHBalance = useAddressBalance(exchangeAddress, 'ETH')
|
||||
const exchangeTokenBalance = useAddressBalance(exchangeAddress, outputCurrency)
|
||||
|
||||
const urlAddedTokens = {}
|
||||
if (params.poolTokenAddress) {
|
||||
urlAddedTokens[params.poolTokenAddress] = true
|
||||
}
|
||||
|
||||
// input validation
|
||||
useEffect(() => {
|
||||
if (valueParsed && poolTokenBalance) {
|
||||
@ -355,8 +361,30 @@ export default function RemoveLiquidity({ params }) {
|
||||
|
||||
const marketRate = getMarketRate(exchangeETHBalance, exchangeTokenBalance, decimals)
|
||||
|
||||
const newOutputDetected =
|
||||
outputCurrency !== 'ETH' && outputCurrency && !INITIAL_TOKENS_CONTEXT[chainId].hasOwnProperty(outputCurrency)
|
||||
|
||||
const [showCustomTokenWarning, setShowCustomTokenWarning] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (newOutputDetected) {
|
||||
setShowCustomTokenWarning(true)
|
||||
} else {
|
||||
setShowCustomTokenWarning(false)
|
||||
}
|
||||
}, [newOutputDetected])
|
||||
|
||||
return (
|
||||
<>
|
||||
{showCustomTokenWarning && (
|
||||
<WarningCard
|
||||
onDismiss={() => {
|
||||
setShowCustomTokenWarning(false)
|
||||
}}
|
||||
urlAddedTokens={urlAddedTokens}
|
||||
currency={outputCurrency}
|
||||
/>
|
||||
)}
|
||||
<CurrencyInputPanel
|
||||
title={t('poolTokens')}
|
||||
extraText={poolTokenBalance && formatBalance(amountFormatter(poolTokenBalance, 18, 4))}
|
||||
@ -368,6 +396,7 @@ export default function RemoveLiquidity({ params }) {
|
||||
}
|
||||
}
|
||||
}}
|
||||
urlAddedTokens={urlAddedTokens}
|
||||
onCurrencySelected={setOutputCurrency}
|
||||
onValueChange={setValue}
|
||||
value={value}
|
||||
|
Loading…
Reference in New Issue
Block a user