Commit 4e2c5c1e authored by ianlapham's avatar ianlapham

slippage without micro warnings on swap

parent e43d9e03
...@@ -13,6 +13,7 @@ const Base = styled(RebassButton)` ...@@ -13,6 +13,7 @@ const Base = styled(RebassButton)`
font-weight: 500; font-weight: 500;
text-align: center; text-align: center;
border-radius: 20px; border-radius: 20px;
border-radius: ${({ borderRadius }) => borderRadius && borderRadius};
outline: none; outline: none;
border: 1px solid transparent; border: 1px solid transparent;
color: white; color: white;
...@@ -40,8 +41,6 @@ export const ButtonPrimary = styled(Base)` ...@@ -40,8 +41,6 @@ export const ButtonPrimary = styled(Base)`
background-color: ${({ theme }) => theme.outlineGrey}; background-color: ${({ theme }) => theme.outlineGrey};
color: ${({ theme }) => theme.darkGrey} color: ${({ theme }) => theme.darkGrey}
cursor: auto; cursor: auto;
outline: none;
border: none;
box-shadow: none; box-shadow: none;
} }
` `
...@@ -100,6 +99,27 @@ const ButtonConfirmedStyle = styled(Base)` ...@@ -100,6 +99,27 @@ const ButtonConfirmedStyle = styled(Base)`
} }
` `
const ButtonErrorStyle = styled(Base)`
background-color: ${({ theme }) => theme.salmonRed};
border: 1px solid ${({ theme }) => theme.salmonRed};
&:focus {
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.05, theme.salmonRed)};
background-color: ${({ theme }) => darken(0.05, theme.salmonRed)};
}
&:hover {
background-color: ${({ theme }) => darken(0.05, theme.salmonRed)};
}
&:active {
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.1, theme.salmonRed)};
background-color: ${({ theme }) => darken(0.1, theme.salmonRed)};
}
&:disabled {
opacity: 50%;
cursor: auto;
}
`
export function ButtonConfirmed({ children, confirmed, ...rest }) { export function ButtonConfirmed({ children, confirmed, ...rest }) {
if (confirmed) { if (confirmed) {
return <ButtonConfirmedStyle {...rest}>{children}</ButtonConfirmedStyle> return <ButtonConfirmedStyle {...rest}>{children}</ButtonConfirmedStyle>
...@@ -108,6 +128,14 @@ export function ButtonConfirmed({ children, confirmed, ...rest }) { ...@@ -108,6 +128,14 @@ export function ButtonConfirmed({ children, confirmed, ...rest }) {
} }
} }
export function ButtonError({ children, error, ...rest }) {
if (error) {
return <ButtonErrorStyle {...rest}>{children}</ButtonErrorStyle>
} else {
return <ButtonPrimary {...rest}>{children}</ButtonPrimary>
}
}
export function ButtonDropwdown({ disabled, children, ...rest }) { export function ButtonDropwdown({ disabled, children, ...rest }) {
return ( return (
<ButtonPrimary {...rest}> <ButtonPrimary {...rest}>
...@@ -129,3 +157,11 @@ export function ButtonDropwdownLight({ disabled, children, ...rest }) { ...@@ -129,3 +157,11 @@ export function ButtonDropwdownLight({ disabled, children, ...rest }) {
</ButtonEmpty> </ButtonEmpty>
) )
} }
export function ButtonRadio({ active, children, ...rest }) {
if (!active) {
return <ButtonEmpty {...rest}>{children}</ButtonEmpty>
} else {
return <ButtonPrimary {...rest}>{children}</ButtonPrimary>
}
}
...@@ -3,7 +3,7 @@ import { Box } from 'rebass/styled-components' ...@@ -3,7 +3,7 @@ import { Box } from 'rebass/styled-components'
const Card = styled(Box)` const Card = styled(Box)`
width: 100%; width: 100%;
border-radius: 20px; border-radius: 8px;
padding: 1rem; padding: 1rem;
padding: ${({ padding }) => padding}; padding: ${({ padding }) => padding};
border: ${({ border }) => border}; border: ${({ border }) => border};
...@@ -16,5 +16,5 @@ export const LightCard = styled(Card)` ...@@ -16,5 +16,5 @@ export const LightCard = styled(Card)`
` `
export const GreyCard = styled(Card)` export const GreyCard = styled(Card)`
background-color: rgba(255, 255, 255, 0.6); background-color: rgba(255, 255, 255, 0.9);
` `
import React from 'react' import React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { ButtonPrimary } from '../Button'
import { AutoColumn, ColumnCenter } from '../Column'
import Row, { RowBetween, RowFlat, RowFixed } from '../Row'
import { ArrowDown } from 'react-feather'
import { ButtonConfirmed } from '../Button'
import { Text } from 'rebass'
import { LightCard } from '../Card'
import Modal from '../Modal' import Modal from '../Modal'
import { CheckCircle } from 'react-feather'
import DoubleTokenLogo from '../DoubleLogo'
import TokenLogo from '../TokenLogo'
import { CloseIcon } from '../../theme/components'
import Loader from '../Loader' import Loader from '../Loader'
import { Link } from '../../theme' import { Link } from '../../theme'
import { Text } from 'rebass'
import { CloseIcon } from '../../theme/components'
import { RowBetween } from '../Row'
import { CheckCircle } from 'react-feather'
import { ButtonPrimary } from '../Button'
import { AutoColumn, ColumnCenter } from '../Column'
import { useWeb3React } from '../../hooks' import { useWeb3React } from '../../hooks'
import { getEtherscanLink } from '../../utils' import { getEtherscanLink } from '../../utils'
import { TRANSACTION_TYPE } from '../../constants'
const Wrapper = styled.div` const Wrapper = styled.div`
width: 100%; width: 100%;
` `
const Section = styled(AutoColumn)` const Section = styled(AutoColumn)`
padding: 2rem; padding: 26px;
` `
const BottomSection = styled(Section)` const BottomSection = styled(Section)`
...@@ -36,29 +29,17 @@ const ConfirmedIcon = styled(ColumnCenter)` ...@@ -36,29 +29,17 @@ const ConfirmedIcon = styled(ColumnCenter)`
padding: 60px 0; padding: 60px 0;
` `
const ConfirmedText = styled(Text)`
color: ${({ theme, confirmed }) => (confirmed ? theme.connectedGreen : theme.white)};
`
export default function ConfirmationModal({ export default function ConfirmationModal({
isOpen, isOpen,
onDismiss, onDismiss,
liquidityAmount = undefined,
poolTokenPercentage = undefined,
amount0,
amount1,
price,
transactionType,
hash, hash,
signed = false, topContent,
contractCall, bottomContent,
attemptedRemoval = false, attemptingTxn,
pendingConfirmation, pendingConfirmation,
extraCall = undefined pendingText,
title = ''
}) { }) {
const { address: address0, symbol: symbol0 } = amount0?.token || {}
const { address: address1, symbol: symbol1 } = amount1?.token || {}
const { chainId } = useWeb3React() const { chainId } = useWeb3React()
function WrappedOnDismissed() { function WrappedOnDismissed() {
...@@ -66,208 +47,19 @@ export default function ConfirmationModal({ ...@@ -66,208 +47,19 @@ export default function ConfirmationModal({
} }
return ( return (
<Modal isOpen={isOpen} onDismiss={WrappedOnDismissed}> <Modal isOpen={isOpen} onDismiss={WrappedOnDismissed} maxHeight={90}>
{!attemptedRemoval ? ( {!attemptingTxn ? (
<Wrapper> <Wrapper>
<Section gap="40px"> <Section>
<RowBetween> <RowBetween>
<Text fontWeight={500} fontSize={'20px'}> <Text fontWeight={500} fontSize={20}>
{transactionType === TRANSACTION_TYPE.SWAP ? 'Confirm Swap' : 'You will receive'} {title}
</Text> </Text>
<CloseIcon onClick={WrappedOnDismissed} /> <CloseIcon onClick={WrappedOnDismissed} />
</RowBetween> </RowBetween>
{transactionType === TRANSACTION_TYPE.SWAP && ( {topContent()}
<AutoColumn gap={'20px'}>
<LightCard>
<RowBetween>
<Text fontSize={24} fontWeight={500}>
{!!amount0 && amount0?.toSignificant(6)}
</Text>
<RowFixed gap="10px">
<TokenLogo address={amount0?.token?.address} size={'24px'} />
<Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}>
{symbol0}
</Text>
</RowFixed>
</RowBetween>
</LightCard>
<ColumnCenter>
<ArrowDown size="16" color="#888D9B" />
</ColumnCenter>
<LightCard>
<RowBetween>
<Text fontSize={24} fontWeight={500}>
{!!amount1 && amount1?.toSignificant(6)}
</Text>
<RowFixed gap="10px">
<TokenLogo address={amount1?.token?.address} size={'24px'} />
<Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}>
{symbol1}
</Text>
</RowFixed>
</RowBetween>
</LightCard>
</AutoColumn>
)}
{transactionType === TRANSACTION_TYPE.ADD && (
<AutoColumn gap="16px">
<RowFlat>
<Text fontSize="48px" fontWeight={500} lineHeight="32px" marginRight={10}>
{liquidityAmount?.toFixed(6)}
</Text>
<DoubleTokenLogo a0={address0 || ''} a1={address1 || ''} size={20} />
</RowFlat>
<Row>
<Text fontSize="24px">{symbol0 + ':' + symbol1 + ' Pool Tokens'}</Text>
</Row>
</AutoColumn>
)}
{transactionType === TRANSACTION_TYPE.REMOVE && (
<AutoColumn gap="16px">
<Row>
<TokenLogo address={address0} size={'30px'} />
<Text fontSize="24px" marginLeft={10}>
{symbol0} {!!amount0 && amount0?.toSignificant(8)}
</Text>
</Row>
<Row>
<TokenLogo address={address1} size={'30px'} />
<Text fontSize="24px" marginLeft={10}>
{symbol1} {!!amount1 && amount1?.toSignificant(8)}
</Text>
</Row>
</AutoColumn>
)}
</Section> </Section>
<BottomSection gap="12px"> <BottomSection gap="12px">{bottomContent()}</BottomSection>
<AutoColumn gap="12px">
{transactionType === TRANSACTION_TYPE.ADD && (
<>
<RowBetween>
<Text color="#565A69" fontWeight={500} fontSize={16}>
{symbol0} Deposited
</Text>
<RowFixed>
<TokenLogo address={address0 || ''} style={{ marginRight: '8px' }} />
<Text fontWeight={500} fontSize={16}>
{!!amount0 && amount0?.toSignificant(6)}
</Text>
</RowFixed>
</RowBetween>
<RowBetween>
<Text color="#565A69" fontWeight={500} fontSize={16}>
{symbol1} Deposited
</Text>
<RowFixed>
<TokenLogo address={address1 || ''} style={{ marginRight: '8px' }} />
<Text fontWeight={500} fontSize={16}>
{amount1?.toSignificant(6)}
</Text>
</RowFixed>
</RowBetween>
</>
)}
{transactionType === TRANSACTION_TYPE.REMOVE && (
<RowBetween>
<Text color="#565A69" fontWeight={500} fontSize={16}>
{'UNI ' + symbol0 + ':' + symbol1} Burned
</Text>
<RowFixed>
<DoubleTokenLogo a0={address0 || ''} a1={address1 || ''} margin={true} />
<Text fontWeight={500} fontSize={16}>
{liquidityAmount?.toSignificant(6)}
</Text>
</RowFixed>
</RowBetween>
)}
{price && price?.adjusted && (
<RowBetween>
<Text color="#565A69" fontWeight={500} fontSize={16}>
Rate
</Text>
<Text fontWeight={500} fontSize={16}>
{`1 ${symbol0} = ${price.adjusted.toFixed(8)} ${symbol1}`}
</Text>
</RowBetween>
)}
{transactionType === TRANSACTION_TYPE.ADD && poolTokenPercentage && (
<RowBetween>
<Text color="#565A69" fontWeight={500} fontSize={16}>
Minted Pool Share:
</Text>
<Text fontWeight={500} fontSize={16}>
{poolTokenPercentage?.toFixed(6) + '%'}
</Text>
</RowBetween>
)}
{transactionType === TRANSACTION_TYPE.REMOVE ? (
<RowBetween gap="20px">
<ButtonConfirmed
style={{ margin: '20px 0' }}
width="48%"
onClick={() => {
extraCall()
}}
confirmed={signed}
disabled={signed}
>
<ConfirmedText fontWeight={500} fontSize={20} confirmed={signed}>
{signed ? 'Signed' : 'Sign'}
</ConfirmedText>
</ButtonConfirmed>
<ButtonPrimary
width="48%"
disabled={!signed}
style={{ margin: '20px 0' }}
onClick={() => {
contractCall()
}}
>
<Text fontWeight={500} fontSize={20}>
Confirm Remove
</Text>
</ButtonPrimary>
</RowBetween>
) : (
<ButtonPrimary
style={{ margin: '20px 0' }}
onClick={() => {
contractCall()
}}
>
<Text fontWeight={500} fontSize={20}>
Confirm{' '}
{transactionType === TRANSACTION_TYPE.ADD
? 'Supply'
: transactionType === TRANSACTION_TYPE.REMOVE
? 'Remove'
: 'Swap'}
</Text>
</ButtonPrimary>
)}
{transactionType === TRANSACTION_TYPE.ADD && (
<Text fontSize={12} color="#565A69" textAlign="center">
{`Output is estimated. You will receive at least ${liquidityAmount?.toFixed(
6
)} UNI ${symbol0}/${symbol1} or the transaction will revert.`}
</Text>
)}
{transactionType === TRANSACTION_TYPE.REMOVE && (
<Text fontSize={12} color="#565A69" textAlign="center">
{`Output is estimated. You will receive at least ${amount0?.toSignificant(
6
)} ${symbol0} at least ${amount1?.toSignificant(6)} ${symbol1} or the transaction will revert.`}
</Text>
)}
{transactionType === TRANSACTION_TYPE.SWAP && (
<Text fontSize={12} color="#565A69" textAlign="center">
{`Output is estimated. You will receive at least ${amount1?.toSignificant(
6
)} ${symbol1} or the transaction will revert.`}
</Text>
)}
</AutoColumn>
</BottomSection>
</Wrapper> </Wrapper>
) : ( ) : (
<Wrapper> <Wrapper>
...@@ -284,17 +76,8 @@ export default function ConfirmationModal({ ...@@ -284,17 +76,8 @@ export default function ConfirmationModal({
{!pendingConfirmation ? 'Transaction Submitted' : 'Waiting For Confirmation'} {!pendingConfirmation ? 'Transaction Submitted' : 'Waiting For Confirmation'}
</Text> </Text>
<AutoColumn gap="12px" justify={'center'}> <AutoColumn gap="12px" justify={'center'}>
<Text fontWeight={500} fontSize={16} color="#2172E5">
{transactionType === TRANSACTION_TYPE.ADD
? 'Supplied'
: transactionType === TRANSACTION_TYPE.REMOVE
? 'Removed'
: 'Swapped'}
</Text>
<Text fontWeight={600} fontSize={16} color="#2172E5"> <Text fontWeight={600} fontSize={16} color="#2172E5">
{`${amount0?.toSignificant(6)} ${symbol0} ${ {pendingText}
transactionType === TRANSACTION_TYPE.SWAP ? 'for' : 'and'
} ${amount1?.toSignificant(6)} ${symbol1}`}
</Text> </Text>
</AutoColumn> </AutoColumn>
{!pendingConfirmation && ( {!pendingConfirmation && (
......
...@@ -251,7 +251,12 @@ export default function CurrencyInputPanel({ ...@@ -251,7 +251,12 @@ export default function CurrencyInputPanel({
</LabelRow> </LabelRow>
)} )}
<InputRow> <InputRow>
<NumericalInput field={field} value={value} onUserInput={onUserInput} /> <NumericalInput
value={value}
onUserInput={val => {
onUserInput(field, val)
}}
/>
{!!token?.address && !atMax && <StyledBalanceMax onClick={onMax}>MAX</StyledBalanceMax>} {!!token?.address && !atMax && <StyledBalanceMax onClick={onMax}>MAX</StyledBalanceMax>}
{renderUnlockButton()} {renderUnlockButton()}
<CurrencySelect <CurrencySelect
......
...@@ -4,6 +4,21 @@ import { ethers } from 'ethers' ...@@ -4,6 +4,21 @@ import { ethers } from 'ethers'
import { parseUnits, parseEther } from '@ethersproject/units' import { parseUnits, parseEther } from '@ethersproject/units'
import { WETH, TradeType, Route, Trade, TokenAmount, JSBI } from '@uniswap/sdk' import { WETH, TradeType, Route, Trade, TokenAmount, JSBI } from '@uniswap/sdk'
import TokenLogo from '../TokenLogo'
import QuestionHelper from '../Question'
import NumericalInput from '../NumericalInput'
import ConfirmationModal from '../ConfirmationModal'
import CurrencyInputPanel from '../CurrencyInputPanel'
import { Link } from '../../theme/components'
import { Text } from 'rebass'
import ThemeProvider, { TYPE } from '../../theme'
import { GreyCard } from '../../components/Card'
import { ArrowDown, ArrowUp } from 'react-feather'
import { AutoColumn, ColumnCenter } from '../../components/Column'
import { ButtonError, ButtonRadio } from '../Button'
import Row, { RowBetween, RowFixed } from '../../components/Row'
import { usePopups } from '../../contexts/Application'
import { useToken } from '../../contexts/Tokens' import { useToken } from '../../contexts/Tokens'
import { useExchange } from '../../contexts/Exchanges' import { useExchange } from '../../contexts/Exchanges'
import { useWeb3React } from '../../hooks' import { useWeb3React } from '../../hooks'
...@@ -11,17 +26,13 @@ import { useAddressBalance } from '../../contexts/Balances' ...@@ -11,17 +26,13 @@ import { useAddressBalance } from '../../contexts/Balances'
import { useTransactionAdder } from '../../contexts/Transactions' import { useTransactionAdder } from '../../contexts/Transactions'
import { useAddressAllowance } from '../../contexts/Allowances' import { useAddressAllowance } from '../../contexts/Allowances'
import ConfirmationModal from '../ConfirmationModal' import { ROUTER_ADDRESSES } from '../../constants'
import CurrencyInputPanel from '../CurrencyInputPanel'
import { Text } from 'rebass'
import { RowBetween } from '../../components/Row'
import { ButtonPrimary } from '../Button'
import { ArrowDown, ArrowUp } from 'react-feather'
import { AutoColumn, ColumnCenter } from '../../components/Column'
import { TRANSACTION_TYPE, ROUTER_ADDRESSES } from '../../constants'
import { getRouterContract, calculateGasMargin } from '../../utils' import { getRouterContract, calculateGasMargin } from '../../utils'
const Wrapper = styled.div`
position: relative;
`
const ArrowWrapper = styled.div` const ArrowWrapper = styled.div`
padding: 4px; padding: 4px;
border: 1px solid ${({ theme }) => theme.malibuBlue}; border: 1px solid ${({ theme }) => theme.malibuBlue};
...@@ -36,8 +47,24 @@ const ArrowWrapper = styled.div` ...@@ -36,8 +47,24 @@ const ArrowWrapper = styled.div`
} }
` `
const FixedBottom = styled.div`
position: absolute;
bottom: -200px;
width: 100%;
`
const ErrorText = styled(Text)` const ErrorText = styled(Text)`
color: ${({ theme, error }) => (error ? theme.salmonRed : theme.chaliceGray)}; color: ${({ theme, warningMedium, warningHigh }) =>
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, theme }) => active && '1px solid ' + theme.royalBlue};
` `
enum Field { enum Field {
...@@ -136,25 +163,74 @@ function reducer( ...@@ -136,25 +163,74 @@ function reducer(
} }
} }
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',
EXACT_ETH_FOR_TOKENS: 'EXACT_ETH_FOR_TOKENS',
TOKENS_FOR_EXACT_TOKENS: 'TOKENS_FOR_EXACT_TOKENS',
TOKENS_FOR_EXACT_ETH: 'TOKENS_FOR_EXACT_ETH',
ETH_FOR_EXACT_TOKENS: 'ETH_FOR_EXACT_TOKENS'
}
const GAS_MARGIN = ethers.utils.bigNumberify(1000)
// default allowed slippage, in bips
const INITIAL_ALLOWED_SLIPPAGE = 200
// 15 minutes, denominated in seconds
const DEFAULT_DEADLINE_FROM_NOW = 60 * 15
// used for warning states based on slippage in bips
const ALLOWED_IMPACT_MEDIUM = 100
const ALLOWED_IMPACT_HIGH = 500
export default function ExchangePage() { export default function ExchangePage() {
const { chainId, account, library } = useWeb3React() const { chainId, account, library } = useWeb3React()
const routerAddress = ROUTER_ADDRESSES[chainId] const routerAddress = ROUTER_ADDRESSES[chainId]
// adding notifications on txns
const [, addPopup] = usePopups()
// input details
const [state, dispatch] = useReducer(reducer, WETH[chainId].address, initializeSwapState) const [state, dispatch] = useReducer(reducer, WETH[chainId].address, initializeSwapState)
const { independentField, typedValue, ...fieldData } = state const { independentField, typedValue, ...fieldData } = state
// get derived state
const dependentField = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT const dependentField = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
const tradeType = independentField === Field.INPUT ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT const tradeType = independentField === Field.INPUT ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT
const [tradeError, setTradeError] = useState('') // error for thinsg liek reserve sizes
// get basic SDK entities
const tokens = { const tokens = {
[Field.INPUT]: useToken(fieldData[Field.INPUT].address), [Field.INPUT]: useToken(fieldData[Field.INPUT].address),
[Field.OUTPUT]: useToken(fieldData[Field.OUTPUT].address) [Field.OUTPUT]: useToken(fieldData[Field.OUTPUT].address)
} }
const exchange = useExchange(tokens[Field.INPUT], tokens[Field.OUTPUT]) const exchange = useExchange(tokens[Field.INPUT], tokens[Field.OUTPUT])
const route = !!exchange ? new Route([exchange], tokens[Field.INPUT]) : undefined // no useRoute hook const route = !!exchange ? new Route([exchange], tokens[Field.INPUT]) : undefined
// modal state
const addTransaction = useTransactionAdder()
const [showConfirm, setShowConfirm] = useState(true)
const [pendingConfirmation, setPendingConfirmation] = useState(true) // waiting for user confirmation
const [attemptingTxn, setAttemptingTxn] = useState(false) // clicked confirmed
// txn values
const [txHash, setTxHash] = useState()
const [deadline, setDeadline] = useState(DEFAULT_DEADLINE_FROM_NOW)
const [allowedSlippage, setAllowedSlippage] = useState(INITIAL_ALLOWED_SLIPPAGE)
// approvals
const inputApproval = useAddressAllowance(account, tokens[Field.INPUT], routerAddress)
const outputApproval = useAddressAllowance(account, tokens[Field.OUTPUT], routerAddress)
// get user- and token-specific lookup data // get user- and token-specific lookup data
const userBalances = { const userBalances = {
...@@ -164,14 +240,18 @@ export default function ExchangePage() { ...@@ -164,14 +240,18 @@ export default function ExchangePage() {
const parsedAmounts: { [field: number]: TokenAmount } = {} const parsedAmounts: { [field: number]: TokenAmount } = {}
// try to parse typed value // try to parse typed value
if (typedValue !== '' && typedValue !== '.' && tokens[independentField]) { // if (typedValue !== '' && typedValue !== '.' && tokens[independentField]) {
if (tokens[independentField]) {
try { try {
const typedValueParsed = parseUnits(typedValue, tokens[independentField].decimals).toString() const typedValueParsed = parseUnits('0.0001', tokens[independentField].decimals).toString()
if (typedValueParsed !== '0') if (typedValueParsed !== '0')
parsedAmounts[independentField] = new TokenAmount(tokens[independentField], typedValueParsed) parsedAmounts[independentField] = new TokenAmount(tokens[independentField], typedValueParsed)
} catch (error) { } catch (error) {
// should only fail if the user specifies too many decimal places of precision (or maybe exceed max uint?) // should only fail if the user specifies too many decimal places of precision (or maybe exceed max uint?)
console.error(error) /**
* @todo reserve limit error here
*/
console.error('found error here ')
} }
} }
...@@ -182,9 +262,9 @@ export default function ExchangePage() { ...@@ -182,9 +262,9 @@ export default function ExchangePage() {
!!route && !!parsedAmounts[independentField] !!route && !!parsedAmounts[independentField]
? new Trade(route, parsedAmounts[independentField], tradeType) ? new Trade(route, parsedAmounts[independentField], tradeType)
: undefined : undefined
} catch (error) { } catch (error) {}
console.error(error)
} const slippageFromTrade = trade && trade.slippage
if (trade) if (trade)
parsedAmounts[dependentField] = tradeType === TradeType.EXACT_INPUT ? trade.outputAmount : trade.inputAmount parsedAmounts[dependentField] = tradeType === TradeType.EXACT_INPUT ? trade.outputAmount : trade.inputAmount
...@@ -250,14 +330,8 @@ export default function ExchangePage() { ...@@ -250,14 +330,8 @@ export default function ExchangePage() {
: undefined : undefined
const maxAmountOutput = const maxAmountOutput =
!!userBalances[Field.OUTPUT] && !!userBalances[Field.OUTPUT] && JSBI.greaterThan(userBalances[Field.OUTPUT].raw, JSBI.BigInt(0))
JSBI.greaterThan( ? userBalances[Field.OUTPUT]
userBalances[Field.OUTPUT].raw,
tokens[Field.OUTPUT].equals(WETH[chainId]) ? MIN_ETHER.raw : JSBI.BigInt(0)
)
? tokens[Field.OUTPUT].equals(WETH[chainId])
? userBalances[Field.OUTPUT].subtract(MIN_ETHER)
: userBalances[Field.OUTPUT]
: undefined : undefined
const atMaxAmountOutput = const atMaxAmountOutput =
...@@ -265,23 +339,6 @@ export default function ExchangePage() { ...@@ -265,23 +339,6 @@ export default function ExchangePage() {
? JSBI.equal(maxAmountOutput.raw, parsedAmounts[Field.OUTPUT].raw) ? JSBI.equal(maxAmountOutput.raw, parsedAmounts[Field.OUTPUT].raw)
: undefined : undefined
const [showConfirm, setShowConfirm] = useState(false)
const [pendingConfirmation, toggelPendingConfirmation] = useState(true)
// state for txn
const addTransaction = useTransactionAdder()
const [txHash, setTxHash] = useState()
const SWAP_TYPE = {
EXACT_TOKENS_FOR_TOKENS: 'EXACT_TOKENS_FOR_TOKENS',
EXACT_TOKENS_FOR_ETH: 'EXACT_TOKENS_FOR_ETH',
EXACT_ETH_FOR_TOKENS: 'EXACT_ETH_FOR_TOKENS',
TOKENS_FOR_EXACT_TOKENS: 'TOKENS_FOR_EXACT_TOKENS',
TOKENS_FOR_EXACT_ETH: 'TOKENS_FOR_EXACT_ETH',
ETH_FOR_EXACT_TOKENS: 'ETH_FOR_EXACT_TOKENS'
}
function getSwapType() { function getSwapType() {
if (tradeType === TradeType.EXACT_INPUT) { if (tradeType === TradeType.EXACT_INPUT) {
if (tokens[Field.INPUT] === WETH[chainId]) { if (tokens[Field.INPUT] === WETH[chainId]) {
...@@ -302,51 +359,62 @@ export default function ExchangePage() { ...@@ -302,51 +359,62 @@ export default function ExchangePage() {
} }
} }
const ALLOWED_SLIPPAGE = 100
function calculateSlippageAmount(value: TokenAmount): JSBI[] { function calculateSlippageAmount(value: TokenAmount): JSBI[] {
if (value && value.raw) { if (value && value.raw) {
const offset = JSBI.divide(JSBI.multiply(JSBI.BigInt(ALLOWED_SLIPPAGE), value.raw), JSBI.BigInt(10000)) const offset = JSBI.divide(JSBI.multiply(JSBI.BigInt(allowedSlippage), value.raw), JSBI.BigInt(10000))
return [JSBI.subtract(value.raw, offset), JSBI.add(value.raw, offset)] return [JSBI.subtract(value.raw, offset), JSBI.add(value.raw, offset)]
} }
return null return null
} }
function hex(value: JSBI) { const slippageAdjustedAmounts = {
return ethers.utils.bigNumberify(value.toString())
}
const slippageAdjustedAmountsRaw = {
[Field.INPUT]: [Field.INPUT]:
Field.INPUT === independentField Field.INPUT === independentField
? parsedAmounts[Field.INPUT]?.raw ? parsedAmounts[Field.INPUT]
: calculateSlippageAmount(parsedAmounts[Field.INPUT])?.[1], : calculateSlippageAmount(parsedAmounts[Field.INPUT])?.[0] &&
new TokenAmount(tokens[Field.INPUT], calculateSlippageAmount(parsedAmounts[Field.INPUT])?.[1]),
[Field.OUTPUT]: [Field.OUTPUT]:
Field.OUTPUT === independentField Field.OUTPUT === independentField
? parsedAmounts[Field.OUTPUT]?.raw ? parsedAmounts[Field.OUTPUT]
: calculateSlippageAmount(parsedAmounts[Field.OUTPUT])?.[0] : calculateSlippageAmount(parsedAmounts[Field.OUTPUT])?.[0] &&
new TokenAmount(tokens[Field.INPUT], calculateSlippageAmount(parsedAmounts[Field.OUTPUT])?.[0])
} }
const inputApproval = useAddressAllowance(account, tokens[Field.INPUT], routerAddress) const showInputUnlock =
const outputApproval = useAddressAllowance(account, tokens[Field.OUTPUT], routerAddress) parsedAmounts[Field.INPUT] && inputApproval && JSBI.greaterThan(parsedAmounts[Field.INPUT].raw, inputApproval.raw)
const [showInputUnlock, setShowInputUnlock] = useState(false) const showOutputUnlock =
parsedAmounts[Field.OUTPUT] &&
outputApproval &&
JSBI.greaterThan(parsedAmounts[Field.OUTPUT].raw, outputApproval.raw)
// monitor parsed amounts and update unlocked buttons // modal state
useEffect(() => { const [showAdvanced, setShowAdvanced] = useState(true)
if ( const [activeIndex, setActiveIndex] = useState(SLIPPAGE_INDEX[3])
parsedAmounts[Field.INPUT] && const [customSlippage, setCustomSlippage] = useState()
inputApproval && const [customDeadline, setCustomDeadline] = useState(DEFAULT_DEADLINE_FROM_NOW / 60)
JSBI.greaterThan(parsedAmounts[Field.INPUT].raw, inputApproval.raw)
) { const [slippageInputError, setSlippageInputError] = useState(null)
setShowInputUnlock(true)
} else { function parseCustomInput(val) {
setShowInputUnlock(false) const acceptableValues = [/^$/, /^\d{1,2}$/, /^\d{0,2}\.\d{0,2}$/]
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)
} }
}, [inputApproval, outputApproval, parsedAmounts]) }
async function onSwap() { async function onSwap() {
const routerContract = getRouterContract(chainId, library, account) const routerContract = getRouterContract(chainId, library, account)
setAttemptingTxn(true)
const path = Object.keys(route.path).map(key => { const path = Object.keys(route.path).map(key => {
return route.path[key].address return route.path[key].address
...@@ -354,7 +422,7 @@ export default function ExchangePage() { ...@@ -354,7 +422,7 @@ export default function ExchangePage() {
let estimate: Function, method: Function, args, value let estimate: Function, method: Function, args, value
const deadline = 1739591241 const deadlineFromNow = Math.ceil(Date.now() / 1000) + deadline
const swapType = getSwapType() const swapType = getSwapType()
switch (swapType) { switch (swapType) {
...@@ -362,11 +430,11 @@ export default function ExchangePage() { ...@@ -362,11 +430,11 @@ export default function ExchangePage() {
estimate = routerContract.estimate.swapExactTokensForTokens estimate = routerContract.estimate.swapExactTokensForTokens
method = routerContract.swapExactTokensForTokens method = routerContract.swapExactTokensForTokens
args = [ args = [
slippageAdjustedAmountsRaw[Field.INPUT].toString(), slippageAdjustedAmounts[Field.INPUT].raw.toString(),
slippageAdjustedAmountsRaw[Field.OUTPUT].toString(), slippageAdjustedAmounts[Field.OUTPUT].raw.toString(),
path, path,
account, account,
deadline deadlineFromNow
] ]
value = ethers.constants.Zero value = ethers.constants.Zero
break break
...@@ -374,29 +442,29 @@ export default function ExchangePage() { ...@@ -374,29 +442,29 @@ export default function ExchangePage() {
estimate = routerContract.estimate.swapTokensForExactTokens estimate = routerContract.estimate.swapTokensForExactTokens
method = routerContract.swapTokensForExactTokens method = routerContract.swapTokensForExactTokens
args = [ args = [
slippageAdjustedAmountsRaw[Field.OUTPUT].toString(), slippageAdjustedAmounts[Field.OUTPUT].raw.toString(),
slippageAdjustedAmountsRaw[Field.INPUT].toString(), slippageAdjustedAmounts[Field.INPUT].raw.toString(),
path, path,
account, account,
deadline deadlineFromNow
] ]
value = ethers.constants.Zero value = ethers.constants.Zero
break break
case SWAP_TYPE.EXACT_ETH_FOR_TOKENS: case SWAP_TYPE.EXACT_ETH_FOR_TOKENS:
estimate = routerContract.estimate.swapExactETHForTokens estimate = routerContract.estimate.swapExactETHForTokens
method = routerContract.swapExactETHForTokens method = routerContract.swapExactETHForTokens
args = [slippageAdjustedAmountsRaw[Field.OUTPUT].toString(), path, account, deadline] args = [slippageAdjustedAmounts[Field.OUTPUT].raw.toString(), path, account, deadlineFromNow]
value = hex(slippageAdjustedAmountsRaw[Field.INPUT]) value = hex(slippageAdjustedAmounts[Field.INPUT].raw)
break break
case SWAP_TYPE.TOKENS_FOR_EXACT_ETH: case SWAP_TYPE.TOKENS_FOR_EXACT_ETH:
estimate = routerContract.estimate.swapTokensForExactETH estimate = routerContract.estimate.swapTokensForExactETH
method = routerContract.swapTokensForExactETH method = routerContract.swapTokensForExactETH
args = [ args = [
slippageAdjustedAmountsRaw[Field.OUTPUT].toString(), slippageAdjustedAmounts[Field.OUTPUT].raw.toString(),
slippageAdjustedAmountsRaw[Field.INPUT].toString(), slippageAdjustedAmounts[Field.INPUT].raw.toString(),
path, path,
account, account,
deadline deadlineFromNow
] ]
value = ethers.constants.Zero value = ethers.constants.Zero
break break
...@@ -404,24 +472,22 @@ export default function ExchangePage() { ...@@ -404,24 +472,22 @@ export default function ExchangePage() {
estimate = routerContract.estimate.swapExactTokensForETH estimate = routerContract.estimate.swapExactTokensForETH
method = routerContract.swapExactTokensForETH method = routerContract.swapExactTokensForETH
args = [ args = [
slippageAdjustedAmountsRaw[Field.INPUT].toString(), slippageAdjustedAmounts[Field.INPUT].raw.toString(),
slippageAdjustedAmountsRaw[Field.OUTPUT].toString(), slippageAdjustedAmounts[Field.OUTPUT].raw.toString(),
path, path,
account, account,
deadline deadlineFromNow
] ]
value = ethers.constants.Zero value = ethers.constants.Zero
break break
case SWAP_TYPE.ETH_FOR_EXACT_TOKENS: case SWAP_TYPE.ETH_FOR_EXACT_TOKENS:
estimate = routerContract.estimate.swapETHForExactTokens estimate = routerContract.estimate.swapETHForExactTokens
method = routerContract.swapETHForExactTokens method = routerContract.swapETHForExactTokens
args = [slippageAdjustedAmountsRaw[Field.OUTPUT].toString(), path, account, deadline] args = [slippageAdjustedAmounts[Field.OUTPUT].raw.toString(), path, account, deadlineFromNow]
value = hex(slippageAdjustedAmountsRaw[Field.INPUT]) value = hex(slippageAdjustedAmounts[Field.INPUT].raw)
break break
} }
const GAS_MARGIN = ethers.utils.bigNumberify(1000)
const estimatedGasLimit = await estimate(...args, { value }).catch(e => { const estimatedGasLimit = await estimate(...args, { value }).catch(e => {
console.log('error getting gas limit') console.log('error getting gas limit')
}) })
...@@ -433,71 +499,267 @@ export default function ExchangePage() { ...@@ -433,71 +499,267 @@ export default function ExchangePage() {
.then(response => { .then(response => {
setTxHash(response) setTxHash(response)
addTransaction(response) addTransaction(response)
toggelPendingConfirmation(false) setPendingConfirmation(false)
}) })
.catch(e => { .catch(e => {
console.log('error when trying transaction') addPopup(
console.log(e) <AutoColumn gap="10px">
<Text>Transaction Failed: try again.</Text>
</AutoColumn>
)
resetModal()
setShowConfirm(false) setShowConfirm(false)
}) })
} }
// errors // errors
const [inputError, setInputError] = useState() const [inputError, setInputError] = useState('')
const [outputError, setOutputError] = useState() const [outputError, setOutputError] = useState('')
const [errorText, setErrorText] = useState(' ') const [isValid, setIsValid] = useState(false)
const [isError, setIsError] = useState(false)
// update errors live
useEffect(() => { useEffect(() => {
// reset errors // reset errors
setInputError(null) setInputError(null)
setOutputError(null) setOutputError(null)
setIsError(false) setTradeError(null)
setIsValid(true)
if (
parsedAmounts[Field.INPUT] &&
exchange &&
JSBI.greaterThan(parsedAmounts[Field.INPUT].raw, exchange.reserveOf(tokens[Field.INPUT]).raw)
) {
setTradeError('Low Liquidity Error')
setIsValid(false)
}
if (
parsedAmounts[Field.OUTPUT] &&
exchange &&
JSBI.greaterThan(parsedAmounts[Field.OUTPUT].raw, exchange.reserveOf(tokens[Field.OUTPUT]).raw)
) {
setTradeError('Low Liquidity Error')
setIsValid(false)
}
if (showInputUnlock) { if (showInputUnlock) {
setInputError('Need to approve amount on input.') setInputError('Approval Needed')
setIsValid(false)
}
if (showOutputUnlock) {
setOutputError('Approval Needed')
setIsValid(false)
} }
if ( if (
userBalances[Field.INPUT] && userBalances[Field.INPUT] &&
parsedAmounts[Field.INPUT] && parsedAmounts[Field.INPUT] &&
JSBI.lessThan(userBalances[Field.INPUT].raw, parsedAmounts[Field.INPUT]?.raw) JSBI.lessThan(userBalances[Field.INPUT].raw, parsedAmounts[Field.INPUT]?.raw)
) { ) {
setInputError('Insufficient balance.') setInputError('Insufficient balance.')
setIsError(true) setIsValid(false)
} }
}, [parsedAmounts, showInputUnlock, userBalances])
// set error text based on all errors if (
useEffect(() => { userBalances[Field.OUTPUT] &&
setErrorText(null) parsedAmounts[Field.OUTPUT] &&
if (!parsedAmounts[Field.INPUT]) { JSBI.lessThan(userBalances[Field.OUTPUT].raw, parsedAmounts[Field.OUTPUT]?.raw)
setErrorText('Enter an amount to continue') ) {
} else if (outputError) { setOutputError('Insufficient balance.')
setErrorText(outputError) setIsValid(false)
} else if (inputError) {
setErrorText(inputError)
return
} }
}, [inputError, outputError, parsedAmounts]) }, [exchange, parsedAmounts, showInputUnlock, showOutputUnlock, tokens, userBalances])
const warningMedium = slippageFromTrade && parseFloat(slippageFromTrade.toFixed(4)) > ALLOWED_IMPACT_MEDIUM / 100
const warningHigh = slippageFromTrade && parseFloat(slippageFromTrade.toFixed(4)) > ALLOWED_IMPACT_HIGH / 100
function resetModal() {
setPendingConfirmation(true)
setAttemptingTxn(false)
setShowAdvanced(false)
}
function modalHeader() {
return (
<AutoColumn gap={'20px'} style={{ marginTop: '40px' }}>
<RowBetween align="flex-end">
<Text fontSize={36} fontWeight={500}>
{!!slippageAdjustedAmounts[Field.INPUT] && slippageAdjustedAmounts[Field.INPUT].toSignificant(6)}
</Text>
<RowFixed gap="10px">
<TokenLogo address={tokens[Field.INPUT]?.address} size={'24px'} />
<Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}>
{tokens[Field.INPUT]?.symbol || ''}
</Text>
</RowFixed>
</RowBetween>
<RowFixed>
<ArrowDown size="16" color="#888D9B" />
</RowFixed>
<RowBetween align="flex-end">
<Text fontSize={36} fontWeight={500} color={warningHigh ? '#FF6871' : '#2172E5'}>
{!!slippageAdjustedAmounts[Field.OUTPUT] && slippageAdjustedAmounts[Field.OUTPUT].toSignificant(6)}
</Text>
<RowFixed gap="10px">
<TokenLogo address={tokens[Field.OUTPUT]?.address} size={'24px'} />
<Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}>
{tokens[Field.OUTPUT]?.symbol || ''}
</Text>
</RowFixed>
</RowBetween>
</AutoColumn>
)
}
// error state for button function modalBottom() {
const isValid = !errorText return showAdvanced ? (
<AutoColumn gap="20px">
<Link
onClick={() => {
setShowAdvanced(false)
}}
>
back
</Link>
<RowBetween>
<TYPE.main>Limit additional price slippage</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}>
<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>
</RowFixed>
<RowBetween>
<TYPE.main>Adjust deadline (minutes from now)</TYPE.main>
</RowBetween>
<RowFixed>
<NumericalInput
value={customDeadline}
onUserInput={val => {
parseCustomDeadline(val)
}}
/>
</RowFixed>
</AutoColumn>
) : (
<>
<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>
<RowBetween>
<Text color="#565A69" fontWeight={500} fontSize={16}>
Slippage <Link onClick={() => setShowAdvanced(true)}>(edit limits)</Link>
</Text>
<ErrorText warningHigh={warningHigh} fontWeight={500}>
{slippageFromTrade && slippageFromTrade.toFixed(4)}%
</ErrorText>
</RowBetween>
<ButtonError onClick={onSwap} error={!!warningHigh} style={{ margin: '10px 0' }}>
<Text fontSize={20} fontWeight={500}>
{warningHigh ? 'Swap Anyway' : 'Swap'}
</Text>
</ButtonError>
<Text fontSize={12} color="#565A69" textAlign="center">
{`Output is estimated. You will receive at least ${slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(6)} ${
tokens[Field.OUTPUT]?.symbol
} or the transaction will revert.`}
</Text>
<AutoColumn justify="center">
<Link
onClick={() => {
setShowAdvanced(true)
}}
>
Advanced Options
</Link>
</AutoColumn>
</>
)
}
const pendingText = ` Swapped ${parsedAmounts[Field.INPUT]?.toSignificant(6)} ${
tokens[Field.INPUT]?.symbol
} for ${parsedAmounts[Field.OUTPUT]?.toSignificant(6)} ${tokens[Field.OUTPUT]?.symbol}`
return ( return (
<> <Wrapper>
<ConfirmationModal <ConfirmationModal
isOpen={showConfirm} isOpen={showConfirm}
onDismiss={() => { onDismiss={() => {
setTxHash(null) resetModal()
setShowConfirm(false) setShowConfirm(false)
}} }}
amount0={parsedAmounts[Field.INPUT]} attemptingTxn={attemptingTxn}
amount1={parsedAmounts[Field.OUTPUT]}
price={route?.midPrice}
transactionType={TRANSACTION_TYPE.SWAP}
contractCall={onSwap}
pendingConfirmation={pendingConfirmation} pendingConfirmation={pendingConfirmation}
hash={txHash ? txHash.hash : ''} hash={txHash ? txHash : ''}
topContent={() => modalHeader()}
bottomContent={modalBottom}
pendingText={pendingText}
title="Confirm Swap"
/> />
<AutoColumn gap={'20px'}> <AutoColumn gap={'20px'}>
<CurrencyInputPanel <CurrencyInputPanel
...@@ -509,7 +771,7 @@ export default function ExchangePage() { ...@@ -509,7 +771,7 @@ export default function ExchangePage() {
}} }}
atMax={atMaxAmountInput} atMax={atMaxAmountInput}
token={tokens[Field.INPUT]} token={tokens[Field.INPUT]}
onTokenSelection={onTokenSelection} onTokenSelection={address => onTokenSelection(Field.INPUT, address)}
title={'Input'} title={'Input'}
error={inputError} error={inputError}
exchange={exchange} exchange={exchange}
...@@ -530,36 +792,69 @@ export default function ExchangePage() { ...@@ -530,36 +792,69 @@ export default function ExchangePage() {
}} }}
atMax={atMaxAmountOutput} atMax={atMaxAmountOutput}
token={tokens[Field.OUTPUT]} token={tokens[Field.OUTPUT]}
onTokenSelection={onTokenSelection} onTokenSelection={address => onTokenSelection(Field.OUTPUT, address)}
title={'Output'} title={'Output'}
error={outputError} error={outputError}
exchange={exchange} exchange={exchange}
disableUnlock showUnlock={showOutputUnlock}
/> />
<RowBetween> <RowBetween>
Rate: <Text fontWeight={500} color="#565A69">
<div> Price
</Text>
<Text fontWeight={500} color="#565A69">
{exchange {exchange
? `1 ${tokens[Field.INPUT].symbol} = ${route?.midPrice.toSignificant(6)} ${tokens[Field.OUTPUT].symbol}` ? `1 ${tokens[Field.INPUT].symbol} = ${route?.midPrice.toSignificant(6)} ${tokens[Field.OUTPUT].symbol}`
: '-'} : '-'}
</div> </Text>
</RowBetween> </RowBetween>
<ColumnCenter style={{ height: '20px' }}> {warningMedium && (
<ErrorText fontSize={12} error={isError}> <RowBetween>
{errorText && errorText} <Text fontWeight={500} color="#565A69">
</ErrorText> Slippage
</ColumnCenter> </Text>
<ButtonPrimary <ErrorText fontWeight={500} warningMedium={warningMedium} warningHigh={warningHigh}>
{slippageFromTrade.toFixed(4)}%
</ErrorText>
</RowBetween>
)}
<ButtonError
onClick={() => { onClick={() => {
setShowConfirm(true) setShowConfirm(true)
}} }}
disabled={!isValid} disabled={!isValid}
error={!!warningHigh}
> >
<Text fontSize={20} fontWeight={500}> <Text fontSize={20} fontWeight={500}>
Swap {inputError
? inputError
: outputError
? outputError
: tradeError
? tradeError
: warningHigh
? 'Swap Anyway'
: 'Swap'}
</Text> </Text>
</ButtonPrimary> </ButtonError>
</AutoColumn> </AutoColumn>
</> {warningHigh && (
<FixedBottom>
<GreyCard>
<AutoColumn gap="12px">
<RowBetween>
<Text fontWeight={500}>Slippage Warning</Text>
<QuestionHelper text="" />
</RowBetween>
<Text color="#565A69" lineHeight="145.23%;">
This trade will move the price by {slippageFromTrade.toFixed(2)}%. This pool probably doesn’t have
enough liquidity. Are you sure you want to continue this trade?
</Text>
</AutoColumn>
</GreyCard>
</FixedBottom>
)}
</Wrapper>
) )
} }
...@@ -17,6 +17,7 @@ import { isMobile } from 'react-device-detect' ...@@ -17,6 +17,7 @@ import { isMobile } from 'react-device-detect'
import { useWeb3React } from '../../hooks' import { useWeb3React } from '../../hooks'
import { useAddressBalance } from '../../contexts/Balances' import { useAddressBalance } from '../../contexts/Balances'
import { useWalletModalToggle, usePopups } from '../../contexts/Application' import { useWalletModalToggle, usePopups } from '../../contexts/Application'
import { AutoColumn } from '../Column'
const HeaderFrame = styled.div` const HeaderFrame = styled.div`
display: flex; display: flex;
...@@ -68,7 +69,7 @@ const AccountElement = styled.div` ...@@ -68,7 +69,7 @@ const AccountElement = styled.div`
/* width: 100%; */ /* width: 100%; */
` `
const FixedPopupColumn = styled.div` const FixedPopupColumn = styled(AutoColumn)`
position: absolute; position: absolute;
top: 80px; top: 80px;
right: 20px right: 20px
...@@ -84,6 +85,15 @@ const StyledClose = styled(X)` ...@@ -84,6 +85,15 @@ 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};
`
export default function Header() { export default function Header() {
const { account, chainId } = useWeb3React() const { account, chainId } = useWeb3React()
...@@ -117,13 +127,13 @@ export default function Header() { ...@@ -117,13 +127,13 @@ export default function Header() {
</AccountElement> </AccountElement>
<Menu /> <Menu />
</HeaderElement> </HeaderElement>
<FixedPopupColumn> <FixedPopupColumn gap="20px">
{activePopups.map(item => { {activePopups.map(item => {
return ( return (
<Card bg="white" padding={'16px'} key={item.key} borderRadius={'8px'}> <Popup key={item.key}>
<StyledClose color="#888D9B" onClick={() => removePopup(item.key)} /> <StyledClose color="#888D9B" onClick={() => removePopup(item.key)} />
{item.content} {item.content}
</Card> </Popup>
) )
})} })}
</FixedPopupColumn> </FixedPopupColumn>
......
...@@ -6,7 +6,6 @@ import { ReactComponent as MenuIcon } from '../../assets/images/menu.svg' ...@@ -6,7 +6,6 @@ import { ReactComponent as MenuIcon } from '../../assets/images/menu.svg'
import { Link } from '../../theme' import { Link } from '../../theme'
import { darken, transparentize } from 'polished' import { darken, transparentize } from 'polished'
import { useAdvancedManager } from '../../contexts/LocalStorage'
import Toggle from 'react-switch' import Toggle from 'react-switch'
...@@ -140,7 +139,7 @@ const EmojiToggle = styled.span` ...@@ -140,7 +139,7 @@ const EmojiToggle = styled.span`
export default function Menu() { export default function Menu() {
const [isDark, toggleDarkMode] = useDarkModeManager() const [isDark, toggleDarkMode] = useDarkModeManager()
const [isAdvanced, toggleAdvanced] = useState() const [isAdvanced, toggleAdvanced] = useState(false)
const node = useRef() const node = useRef()
const [open, toggle] = useToggle(false) const [open, toggle] = useToggle(false)
......
...@@ -11,6 +11,8 @@ const StyledInput = styled.input` ...@@ -11,6 +11,8 @@ const StyledInput = styled.input`
flex: 1 1 auto; flex: 1 1 auto;
width: 0; width: 0;
background-color: ${({ theme }) => theme.inputBackground}; background-color: ${({ theme }) => theme.inputBackground};
font-size: ${({ fontSize }) => fontSize && fontSize};
text-align: ${({ align }) => align && align};
[type='number'] { [type='number'] {
-moz-appearance: textfield; -moz-appearance: textfield;
...@@ -32,10 +34,10 @@ function escapeRegExp(string: string): string { ...@@ -32,10 +34,10 @@ function escapeRegExp(string: string): string {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // $& means the whole matched string return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // $& means the whole matched string
} }
export const Input = React.memo(({ field, value, onUserInput, ...rest }: any) => { export const Input = React.memo(({ value, onUserInput, placeHolder = null, ...rest }: any) => {
function enforcer(nextUserInput: string) { function enforcer(nextUserInput: string) {
if (nextUserInput === '' || inputRegex.test(escapeRegExp(nextUserInput))) { if (nextUserInput === '' || inputRegex.test(escapeRegExp(nextUserInput))) {
onUserInput(field, nextUserInput) onUserInput(nextUserInput)
} }
} }
...@@ -53,7 +55,7 @@ export const Input = React.memo(({ field, value, onUserInput, ...rest }: any) => ...@@ -53,7 +55,7 @@ export const Input = React.memo(({ field, value, onUserInput, ...rest }: any) =>
autoCorrect="off" autoCorrect="off"
// text-specific options // text-specific options
type="text" type="text"
placeholder="0.0" placeholder={placeHolder || '0.0'}
minLength={1} minLength={1}
maxLength={79} maxLength={79}
spellCheck="false" spellCheck="false"
......
...@@ -16,7 +16,7 @@ import { Link } from '../../theme' ...@@ -16,7 +16,7 @@ import { Link } from '../../theme'
import { Text } from 'rebass' import { Text } from 'rebass'
import { Plus } from 'react-feather' import { Plus } from 'react-feather'
import { LightCard } from '../Card' import { LightCard } from '../Card'
import { AutoColumn, ColumnCenter } from '../Column' import Column, { AutoColumn, ColumnCenter } from '../Column'
import { ButtonPrimary, ButtonDropwdown, ButtonDropwdownLight } from '../Button' import { ButtonPrimary, ButtonDropwdown, ButtonDropwdownLight } from '../Button'
import DoubleTokenLogo from '../DoubleLogo' import DoubleTokenLogo from '../DoubleLogo'
...@@ -112,6 +112,13 @@ function PoolFinder({ history }) { ...@@ -112,6 +112,13 @@ function PoolFinder({ history }) {
</Row> </Row>
</ButtonDropwdownLight> </ButtonDropwdownLight>
)} )}
{allowImport && (
<ColumnCenter justify="center" style={{ backgroundColor: '#EBF4FF', padding: '8px', borderRadius: '12px' }}>
<Text textAlign="center" fontWeight={500} color="#2172E5">
Liquidity Found!
</Text>
</ColumnCenter>
)}
{position ? ( {position ? (
!JSBI.equal(position.raw, JSBI.BigInt(0)) ? ( !JSBI.equal(position.raw, JSBI.BigInt(0)) ? (
<PositionCard <PositionCard
...@@ -155,11 +162,7 @@ function PoolFinder({ history }) { ...@@ -155,11 +162,7 @@ function PoolFinder({ history }) {
</Text> </Text>
</LightCard> </LightCard>
)} )}
{allowImport && (
<Text textAlign="center" fontWeight={500}>
Liquidity Found!
</Text>
)}
<ButtonPrimary disabled={!allowImport} onClick={endSearch}> <ButtonPrimary disabled={!allowImport} onClick={endSearch}>
<Text fontWeight={500} fontSize={20}> <Text fontWeight={500} fontSize={20}>
Import Import
......
...@@ -61,11 +61,6 @@ function PositionCard({ exchangeAddress, token0, token1, history, minimal = fals ...@@ -61,11 +61,6 @@ function PositionCard({ exchangeAddress, token0, token1, history, minimal = fals
return ( return (
<DynamicCard {...rest}> <DynamicCard {...rest}>
<AutoColumn gap="20px"> <AutoColumn gap="20px">
<FixedHeightRow>
<Text fontWeight={500} fontSize={16}>
Current Position
</Text>
</FixedHeightRow>
<FixedHeightRow> <FixedHeightRow>
<RowFixed> <RowFixed>
<DoubleLogo a0={token0?.address || ''} a1={token1?.address || ''} margin={true} size={24} /> <DoubleLogo a0={token0?.address || ''} a1={token1?.address || ''} margin={true} size={24} />
......
...@@ -4,6 +4,7 @@ const Row = styled.div` ...@@ -4,6 +4,7 @@ const Row = styled.div`
width: 100%; width: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
align-items: ${({ align }) => align && align};
` `
export const RowBetween = styled(Row)` export const RowBetween = styled(Row)`
......
...@@ -411,16 +411,18 @@ function SearchModal({ history, isOpen, onDismiss, onTokenSelect, urlAddedTokens ...@@ -411,16 +411,18 @@ function SearchModal({ history, isOpen, onDismiss, onTokenSelect, urlAddedTokens
/> />
<RowBetween> <RowBetween>
<div> <div>
<Text> {filterType !== 'tokens' && (
Don't see a pool?{' '} <Text>
<StyledLink Don't see a pool?{' '}
onClick={() => { <StyledLink
history.push('/find') onClick={() => {
}} history.push('/find')
> }}
Import it. >
</StyledLink> Import it.
</Text> </StyledLink>
</Text>
)}
</div> </div>
<div /> <div />
<Filter title="Your Balances" filter={FILTERS.BALANCES} /> <Filter title="Your Balances" filter={FILTERS.BALANCES} />
......
...@@ -19,13 +19,6 @@ export const SUPPORTED_THEMES = { ...@@ -19,13 +19,6 @@ export const SUPPORTED_THEMES = {
LIGHT: 'LIGHT' LIGHT: 'LIGHT'
} }
export enum TRANSACTION_TYPE {
SWAP,
SEND,
ADD,
REMOVE
}
const MAINNET_WALLETS = { const MAINNET_WALLETS = {
INJECTED: { INJECTED: {
connector: injected, connector: injected,
......
...@@ -9,14 +9,14 @@ import { ChainId, WETH, Token, TokenAmount, Exchange, JSBI } from '@uniswap/sdk' ...@@ -9,14 +9,14 @@ import { ChainId, WETH, Token, TokenAmount, Exchange, JSBI } from '@uniswap/sdk'
const UPDATE = 'UPDATE' const UPDATE = 'UPDATE'
const ALL_EXCHANGES: [Token, Token][] = [ const ALL_EXCHANGES: [Token, Token][] = [
// [ [
// INITIAL_TOKENS_CONTEXT[ChainId.RINKEBY][WETH[ChainId.RINKEBY].address], INITIAL_TOKENS_CONTEXT[ChainId.RINKEBY][WETH[ChainId.RINKEBY].address],
// INITIAL_TOKENS_CONTEXT[ChainId.RINKEBY]['0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735'] INITIAL_TOKENS_CONTEXT[ChainId.RINKEBY]['0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735']
// ] ],
// [ [
// INITIAL_TOKENS_CONTEXT[ChainId.RINKEBY]['0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735'], INITIAL_TOKENS_CONTEXT[ChainId.RINKEBY]['0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735'],
// INITIAL_TOKENS_CONTEXT[ChainId.RINKEBY]['0x8ab15C890E5C03B5F240f2D146e3DF54bEf3Df44'] INITIAL_TOKENS_CONTEXT[ChainId.RINKEBY]['0x8ab15C890E5C03B5F240f2D146e3DF54bEf3Df44']
// ] ]
] ]
const EXCHANGE_MAP: { const EXCHANGE_MAP: {
......
...@@ -27,11 +27,6 @@ const HeaderWrapper = styled.div` ...@@ -27,11 +27,6 @@ const HeaderWrapper = styled.div`
width: 100%; width: 100%;
justify-content: space-between; justify-content: space-between;
` `
const FooterWrapper = styled.div`
width: 100%;
min-height: 30px;
align-self: flex-end;
`
const BodyWrapper = styled.div` const BodyWrapper = styled.div`
display: flex; display: flex;
...@@ -50,7 +45,7 @@ const Body = styled.div` ...@@ -50,7 +45,7 @@ const Body = styled.div`
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04), box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 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.01); 0px 24px 32px rgba(0, 0, 0, 0.01);
border-radius: 20px; border-radius: 20px;
padding: 2rem 2rem; padding: 2rem 2rem 1rem 2rem;
` `
export default function App() { export default function App() {
......
...@@ -5,26 +5,27 @@ import { parseUnits, parseEther } from '@ethersproject/units' ...@@ -5,26 +5,27 @@ import { parseUnits, parseEther } from '@ethersproject/units'
import { WETH, TokenAmount, JSBI, Percent, Route } from '@uniswap/sdk' import { WETH, TokenAmount, JSBI, Percent, Route } from '@uniswap/sdk'
import DoubleLogo from '../../components/DoubleLogo' import DoubleLogo from '../../components/DoubleLogo'
import TokenLogo from '../../components/TokenLogo'
import SearchModal from '../../components/SearchModal' import SearchModal from '../../components/SearchModal'
import PositionCard from '../../components/PositionCard' import PositionCard from '../../components/PositionCard'
import ConfirmationModal from '../../components/ConfirmationModal' import ConfirmationModal from '../../components/ConfirmationModal'
import CurrencyInputPanel from '../../components/CurrencyInputPanel' import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import { Text } from 'rebass' import { Text } from 'rebass'
import { Plus } from 'react-feather' import { Plus } from 'react-feather'
import { RowBetween } from '../../components/Row' import { ButtonPrimary } from '../../components/Button'
import { ChevronDown } from 'react-feather'
import { AutoColumn, ColumnCenter } from '../../components/Column' import { AutoColumn, ColumnCenter } from '../../components/Column'
import { ButtonPrimary, ButtonEmpty } from '../../components/Button' import Row, { RowBetween, RowFlat, RowFixed } from '../../components/Row'
import { useToken } from '../../contexts/Tokens' import { useToken } from '../../contexts/Tokens'
import { useWeb3React } from '../../hooks' import { useWeb3React } from '../../hooks'
import { usePopups } from '../../contexts/Application'
import { useAddressBalance } from '../../contexts/Balances' import { useAddressBalance } from '../../contexts/Balances'
import { useAddressAllowance } from '../../contexts/Allowances' import { useAddressAllowance } from '../../contexts/Allowances'
import { useTransactionAdder } from '../../contexts/Transactions' import { useTransactionAdder } from '../../contexts/Transactions'
import { useExchange, useTotalSupply } from '../../contexts/Exchanges' import { useExchange, useTotalSupply } from '../../contexts/Exchanges'
import { BigNumber } from 'ethers/utils' import { BigNumber } from 'ethers/utils'
import { TRANSACTION_TYPE, ROUTER_ADDRESSES } from '../../constants' import { ROUTER_ADDRESSES } from '../../constants'
import { getRouterContract, calculateGasMargin } from '../../utils' import { getRouterContract, calculateGasMargin } from '../../utils'
// denominated in bips // denominated in bips
...@@ -41,7 +42,7 @@ const Wrapper = styled.div` ...@@ -41,7 +42,7 @@ const Wrapper = styled.div`
const FixedBottom = styled.div` const FixedBottom = styled.div`
position: absolute; position: absolute;
bottom: -240px; bottom: -200px;
width: 100%; width: 100%;
` `
...@@ -145,6 +146,7 @@ export default function AddLiquidity({ token0, token1 }) { ...@@ -145,6 +146,7 @@ export default function AddLiquidity({ token0, token1 }) {
// modal states // modal states
const [showSearch, setShowSearch] = useState<boolean>(false) const [showSearch, setShowSearch] = useState<boolean>(false)
const [showConfirm, setShowConfirm] = useState<boolean>(false) const [showConfirm, setShowConfirm] = useState<boolean>(false)
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // clicke confirm
const [pendingConfirmation, setPendingConfirmation] = useState<boolean>(true) const [pendingConfirmation, setPendingConfirmation] = useState<boolean>(true)
// input state // input state
...@@ -369,7 +371,10 @@ export default function AddLiquidity({ token0, token1 }) { ...@@ -369,7 +371,10 @@ export default function AddLiquidity({ token0, token1 }) {
return null return null
} }
const [, addPopup] = usePopups()
async function onAdd() { async function onAdd() {
setAttemptingTxn(true)
const router = getRouterContract(chainId, library, account) const router = getRouterContract(chainId, library, account)
const minTokenInput = calculateSlippageAmount(parsedAmounts[Field.INPUT])[0] const minTokenInput = calculateSlippageAmount(parsedAmounts[Field.INPUT])[0]
...@@ -428,26 +433,112 @@ export default function AddLiquidity({ token0, token1 }) { ...@@ -428,26 +433,112 @@ export default function AddLiquidity({ token0, token1 }) {
}) })
.catch((e: Error) => { .catch((e: Error) => {
console.log(e) console.log(e)
addPopup(
<AutoColumn gap="10px">
<Text>Transaction Failed: try again.</Text>
</AutoColumn>
)
setPendingConfirmation(true)
setAttemptingTxn(false)
setShowConfirm(false) setShowConfirm(false)
}) })
} }
const modalHeader = () => {
return (
<AutoColumn gap="20px">
<RowFlat style={{ marginTop: '60px' }}>
<Text fontSize="48px" fontWeight={500} lineHeight="32px" marginRight={10}>
{liquidityMinted?.toFixed(6)}
</Text>
<DoubleLogo a0={tokens[Field.INPUT]?.symbol || ''} a1={tokens[Field.OUTPUT]?.symbol || ''} size={30} />
</RowFlat>
<Row>
<Text fontSize="24px">
{tokens[Field.INPUT]?.symbol + ':' + tokens[Field.OUTPUT]?.symbol + ' Pool Tokens'}
</Text>
</Row>
</AutoColumn>
)
}
const modalBottom = () => {
return (
<>
<RowBetween>
<Text color="#565A69" fontWeight={500} fontSize={16}>
{tokens[Field.INPUT]?.symbol} Deposited
</Text>
<RowFixed>
<TokenLogo address={tokens[Field.INPUT]?.address || ''} style={{ marginRight: '8px' }} />
<Text fontWeight={500} fontSize={16}>
{!!parsedAmounts[Field.INPUT] && parsedAmounts[Field.INPUT].toSignificant(6)}
</Text>
</RowFixed>
</RowBetween>
<RowBetween>
<Text color="#565A69" fontWeight={500} fontSize={16}>
{tokens[Field.OUTPUT]?.symbol} Deposited
</Text>
<RowFixed>
<TokenLogo address={tokens[Field.OUTPUT]?.address || ''} style={{ marginRight: '8px' }} />
<Text fontWeight={500} fontSize={16}>
{!!parsedAmounts[Field.OUTPUT] && parsedAmounts[Field.OUTPUT].toSignificant(6)}
</Text>
</RowFixed>
</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>
</RowBetween>
<ButtonPrimary style={{ margin: '20px 0' }} onClick={onAdd}>
<Text fontWeight={500} fontSize={20}>
Confirm Supply
</Text>
</ButtonPrimary>
<Text 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>
</>
)
}
const pendingText = `Supplied ${parsedAmounts[Field.INPUT]?.toSignificant(6)} ${
tokens[Field.INPUT]?.symbol
} ${'and'} ${parsedAmounts[Field.OUTPUT]?.toSignificant(6)} ${tokens[Field.OUTPUT]?.symbol}`
return ( return (
<Wrapper> <Wrapper>
<ConfirmationModal <ConfirmationModal
isOpen={showConfirm} isOpen={showConfirm}
onDismiss={() => { onDismiss={() => {
setPendingConfirmation(true)
setAttemptingTxn(false)
setShowConfirm(false) setShowConfirm(false)
}} }}
liquidityAmount={liquidityMinted} attemptingTxn={attemptingTxn}
amount0={parsedAmounts[Field.INPUT]}
amount1={parsedAmounts[Field.OUTPUT]}
poolTokenPercentage={poolTokenPercentage}
price={route?.midPrice && route?.midPrice?.raw?.denominator}
transactionType={TRANSACTION_TYPE.ADD}
contractCall={onAdd}
pendingConfirmation={pendingConfirmation} pendingConfirmation={pendingConfirmation}
hash={txHash ? txHash : ''} hash={txHash ? txHash : ''}
topContent={() => modalHeader()}
bottomContent={modalBottom}
pendingText={pendingText}
title="You will receive"
/> />
<SearchModal <SearchModal
isOpen={showSearch} isOpen={showSearch}
...@@ -456,22 +547,6 @@ export default function AddLiquidity({ token0, token1 }) { ...@@ -456,22 +547,6 @@ export default function AddLiquidity({ token0, token1 }) {
}} }}
/> />
<AutoColumn gap="20px"> <AutoColumn gap="20px">
<ButtonEmpty
padding={'1rem'}
onClick={() => {
setShowSearch(true)
}}
>
<RowBetween>
<DoubleLogo a0={exchange?.token0?.address || ''} a1={exchange?.token1?.address || ''} size={24} />
<Text fontSize={20}>
{exchange?.token0 && exchange?.token1
? exchange.token0.symbol + ' / ' + exchange.token1.symbol + ' Pool'
: ''}
</Text>
<ChevronDown size={24} />
</RowBetween>
</ButtonEmpty>
{noLiquidity && ( {noLiquidity && (
<ColumnCenter> <ColumnCenter>
<Text fontWeight={500} style={{ textAlign: 'center' }}> <Text fontWeight={500} style={{ textAlign: 'center' }}>
......
...@@ -6,15 +6,17 @@ import { TokenAmount, JSBI, Route, WETH, Percent } from '@uniswap/sdk' ...@@ -6,15 +6,17 @@ import { TokenAmount, JSBI, Route, WETH, Percent } from '@uniswap/sdk'
import Slider from '../../components/Slider' import Slider from '../../components/Slider'
import TokenLogo from '../../components/TokenLogo' import TokenLogo from '../../components/TokenLogo'
import DoubleLogo from '../../components/DoubleLogo'
import PositionCard from '../../components/PositionCard' import PositionCard from '../../components/PositionCard'
import ConfirmationModal from '../../components/ConfirmationModal' import ConfirmationModal from '../../components/ConfirmationModal'
import CurrencyInputPanel from '../../components/CurrencyInputPanel' import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import { Text } from 'rebass' import { Text } from 'rebass'
import { LightCard } from '../../components/Card' import { LightCard } from '../../components/Card'
import { ButtonPrimary } from '../../components/Button' import { ButtonPrimary } from '../../components/Button'
import { ButtonConfirmed } from '../../components/Button'
import { ArrowDown, Plus } from 'react-feather' import { ArrowDown, Plus } from 'react-feather'
import { RowBetween, RowFixed } from '../../components/Row'
import { AutoColumn, ColumnCenter } from '../../components/Column' import { AutoColumn, ColumnCenter } from '../../components/Column'
import Row, { RowBetween, RowFixed } from '../../components/Row'
import { useToken } from '../../contexts/Tokens' import { useToken } from '../../contexts/Tokens'
import { useWeb3React } from '../../hooks' import { useWeb3React } from '../../hooks'
...@@ -25,7 +27,6 @@ import { useExchange, useTotalSupply } from '../../contexts/Exchanges' ...@@ -25,7 +27,6 @@ import { useExchange, useTotalSupply } from '../../contexts/Exchanges'
import { BigNumber } from 'ethers/utils' import { BigNumber } from 'ethers/utils'
import { splitSignature } from '@ethersproject/bytes' import { splitSignature } from '@ethersproject/bytes'
import { TRANSACTION_TYPE } from '../../constants'
import { ROUTER_ADDRESSES } from '../../constants' import { ROUTER_ADDRESSES } from '../../constants'
import { getRouterContract, calculateGasMargin } from '../../utils' import { getRouterContract, calculateGasMargin } from '../../utils'
...@@ -138,6 +139,10 @@ function reducer( ...@@ -138,6 +139,10 @@ function reducer(
} }
} }
const ConfirmedText = styled(Text)`
color: ${({ theme, confirmed }) => (confirmed ? theme.connectedGreen : theme.white)};
`
export default function RemoveLiquidity({ token0, token1 }) { export default function RemoveLiquidity({ token0, token1 }) {
const { account, chainId, library } = useWeb3React() const { account, chainId, library } = useWeb3React()
const routerAddress = ROUTER_ADDRESSES[chainId] const routerAddress = ROUTER_ADDRESSES[chainId]
...@@ -302,19 +307,19 @@ export default function RemoveLiquidity({ token0, token1 }) { ...@@ -302,19 +307,19 @@ export default function RemoveLiquidity({ token0, token1 }) {
: false : false
// errors // errors
const [generalError, setGeneralError] = useState() const [generalError, setGeneralError] = useState('')
const [inputError, setInputError] = useState() const [inputError, setInputError] = useState('')
const [outputError, setOutputError] = useState() const [outputError, setOutputError] = useState('')
const [poolTokenError, setPoolTokenError] = useState() const [poolTokenError, setPoolTokenError] = useState('')
const [isValid, setIsValid] = useState(false) const [isValid, setIsValid] = useState(false)
// update errors live // update errors live
useEffect(() => { useEffect(() => {
// reset errors // reset errors
setGeneralError(null) setGeneralError('')
setInputError(null) setInputError('')
setOutputError(null) setOutputError('')
setPoolTokenError(null) setPoolTokenError('')
setIsValid(true) setIsValid(true)
if (formattedAmounts[Field.TOKEN0] === '') { if (formattedAmounts[Field.TOKEN0] === '') {
...@@ -480,28 +485,103 @@ export default function RemoveLiquidity({ token0, token1 }) { ...@@ -480,28 +485,103 @@ export default function RemoveLiquidity({ token0, token1 }) {
setPendingConfirmation(true) setPendingConfirmation(true)
} }
function modalHeader() {
return (
<AutoColumn gap="16px">
<Row style={{ marginTop: '40px' }}>
<TokenLogo address={tokens[Field.TOKEN0]?.symbol} size={'30px'} />
<Text fontSize="24px" marginLeft={10}>
{tokens[Field.TOKEN0]?.symbol}{' '}
{!!parsedAmounts[Field.TOKEN0] && parsedAmounts[Field.TOKEN0].toSignificant(8)}
</Text>
</Row>
<Row>
<TokenLogo address={tokens[Field.TOKEN1]?.symbol} size={'30px'} />
<Text fontSize="24px" marginLeft={10}>
{tokens[Field.TOKEN1]?.symbol}{' '}
{!!parsedAmounts[Field.TOKEN1] && parsedAmounts[Field.TOKEN1].toSignificant(8)}
</Text>
</Row>
</AutoColumn>
)
}
function modalBottom() {
return (
<>
<RowBetween>
<Text color="#565A69" fontWeight={500} fontSize={16}>
{'UNI ' + tokens[Field.TOKEN0]?.symbol + ':' + tokens[Field.TOKEN1]?.symbol} Burned
</Text>
<RowFixed>
<DoubleLogo
a0={tokens[Field.TOKEN0]?.address || ''}
a1={tokens[Field.TOKEN1]?.address || ''}
margin={true}
/>
<Text fontWeight={500} fontSize={16}>
{parsedAmounts[Field.LIQUIDITY]?.toSignificant(6)}
</Text>
</RowFixed>
</RowBetween>
<RowBetween>
<Text color="#565A69" fontWeight={500} fontSize={16}>
Rate
</Text>
<Text fontWeight={500} fontSize={16}>
{`1 ${tokens[Field.TOKEN0]?.symbol} = ${route?.midPrice && route.midPrice.adjusted.toFixed(8)} ${
tokens[Field.TOKEN1]?.symbol
}`}
</Text>
</RowBetween>
<RowBetween gap="20px">
<ButtonConfirmed
style={{ margin: '20px 0' }}
width="48%"
onClick={onSign}
confirmed={signed}
disabled={signed}
>
<ConfirmedText fontWeight={500} fontSize={20} confirmed={signed}>
{signed ? 'Signed' : 'Sign'}
</ConfirmedText>
</ButtonConfirmed>
<ButtonPrimary width="48%" disabled={!signed} style={{ margin: '20px 0' }} onClick={onRemove}>
<Text fontWeight={500} fontSize={20}>
Confirm Remove
</Text>
</ButtonPrimary>
</RowBetween>
<Text fontSize={12} color="#565A69" textAlign="center">
{`Output is estimated. You will receive at least ${parsedAmounts[Field.TOKEN0]?.toFixed(6)} ${
tokens[Field.TOKEN0]?.symbol
} and at least ${parsedAmounts[Field.TOKEN1]?.toFixed(6)} ${
tokens[Field.TOKEN1]?.symbol
} or the transaction will revert.`}
</Text>
</>
)
}
const pendingText = `Removed ${parsedAmounts[Field.TOKEN0]?.toSignificant(6)} ${
tokens[Field.TOKEN0]?.symbol
} and ${parsedAmounts[Field.TOKEN1]?.toSignificant(6)} ${tokens[Field.TOKEN1]?.symbol}`
return ( return (
<Wrapper> <Wrapper>
{!!parsedAmounts[Field.TOKEN0] && !!parsedAmounts[Field.TOKEN1] && !!parsedAmounts[Field.LIQUIDITY] && ( <ConfirmationModal
<ConfirmationModal isOpen={showConfirm}
isOpen={showConfirm} onDismiss={() => {
onDismiss={() => { resetModalState()
resetModalState() setShowConfirm(false)
setShowConfirm(false) }}
}} attemptingTxn={attemptedRemoval}
amount0={parsedAmounts[Field.TOKEN0]} pendingConfirmation={pendingConfirmation}
amount1={parsedAmounts[Field.TOKEN1]} hash={txHash ? txHash : ''}
price={route?.midPrice} topContent={modalHeader}
liquidityAmount={parsedAmounts[Field.LIQUIDITY]} bottomContent={modalBottom}
transactionType={TRANSACTION_TYPE.REMOVE} pendingText={pendingText}
contractCall={onRemove} title="You will remove"
extraCall={onSign} />
signed={signed}
attemptedRemoval={attemptedRemoval}
pendingConfirmation={pendingConfirmation}
hash={txHash ? txHash : ''}
/>
)}
<AutoColumn gap="20px"> <AutoColumn gap="20px">
<LightCard> <LightCard>
<AutoColumn gap="20px"> <AutoColumn gap="20px">
......
...@@ -3,22 +3,22 @@ import styled from 'styled-components' ...@@ -3,22 +3,22 @@ import styled from 'styled-components'
import { JSBI } from '@uniswap/sdk' import { JSBI } from '@uniswap/sdk'
import { withRouter } from 'react-router-dom' import { withRouter } from 'react-router-dom'
import { useWeb3React } from '@web3-react/core' import Card from '../../components/Card'
import { useAllTokens } from '../../contexts/Tokens'
import { useAllExchanges } from '../../contexts/Exchanges'
import { useAllBalances, useAccountLPBalances } from '../../contexts/Balances'
import Question from '../../components/Question' import Question from '../../components/Question'
import SearchModal from '../../components/SearchModal' import SearchModal from '../../components/SearchModal'
import PositionCard from '../../components/PositionCard' import PositionCard from '../../components/PositionCard'
import Row, { RowBetween } from '../../components/Row' import Row, { RowBetween } from '../../components/Row'
import Card, { LightCard } from '../../components/Card'
import { Link } from '../../theme' import { Link } from '../../theme'
import { Text } from 'rebass' import { Text } from 'rebass'
import { AutoColumn } from '../../components/Column' import { AutoColumn } from '../../components/Column'
import { ArrowRight } from 'react-feather' import { ArrowRight } from 'react-feather'
import { ButtonPrimary } from '../../components/Button' import { ButtonPrimary } from '../../components/Button'
import { useWeb3React } from '@web3-react/core'
import { useAllTokens } from '../../contexts/Tokens'
import { useAllExchanges } from '../../contexts/Exchanges'
import { useAllBalances, useAccountLPBalances } from '../../contexts/Balances'
const Positions = styled.div` const Positions = styled.div`
position: relative; position: relative;
margin-top: 38px; margin-top: 38px;
...@@ -71,19 +71,16 @@ function Supply({ history }) { ...@@ -71,19 +71,16 @@ function Supply({ history }) {
</ButtonPrimary> </ButtonPrimary>
<Positions> <Positions>
<AutoColumn gap="20px"> <AutoColumn gap="20px">
<RowBetween> {filteredExchangeList?.length !== 0 && (
<Text fontWeight={500}>Your Pooled Liquidity</Text> <RowBetween>
<Question text="filler text" /> <Text fontWeight={500}>Your Pooled Liquidity</Text>
</RowBetween> <Question text="filler text" />
{filteredExchangeList} </RowBetween>
{filteredExchangeList?.length === 0 && (
<LightCard bg="rgba(255, 255, 255, 0.6)" padding={'45px'}>
<Text color="#C3C5CB">Add liquidity to see your positions</Text>
</LightCard>
)} )}
{filteredExchangeList}
<AutoColumn justify="center"> <AutoColumn justify="center">
<Text color="#AEAEAE"> <Text color="#AEAEAE">
Already have liquidity?{' '} {filteredExchangeList?.length !== 0 ? `Don't see your ` : 'Already have '} liquidity?{' '}
<Link <Link
onClick={() => { onClick={() => {
history.push('/find') history.push('/find')
......
...@@ -45,6 +45,7 @@ export const Link = styled.a.attrs({ ...@@ -45,6 +45,7 @@ export const Link = styled.a.attrs({
text-decoration: none; text-decoration: none;
cursor: pointer; cursor: pointer;
color: ${({ theme }) => theme.royalBlue}; color: ${({ theme }) => theme.royalBlue};
font-weight: 500;
:focus { :focus {
outline: none; outline: none;
......
...@@ -3,6 +3,7 @@ import { ThemeProvider as StyledComponentsThemeProvider, createGlobalStyle, css ...@@ -3,6 +3,7 @@ import { ThemeProvider as StyledComponentsThemeProvider, createGlobalStyle, css
import { getQueryParam, checkSupportedTheme } from '../utils' import { getQueryParam, checkSupportedTheme } from '../utils'
import { SUPPORTED_THEMES } from '../constants' import { SUPPORTED_THEMES } from '../constants'
import { useDarkModeManager } from '../contexts/LocalStorage' import { useDarkModeManager } from '../contexts/LocalStorage'
import { Text } from 'rebass'
export * from './components' export * from './components'
...@@ -117,6 +118,19 @@ const theme = darkMode => ({ ...@@ -117,6 +118,19 @@ const theme = darkMode => ({
` `
}) })
export const TYPE = {
main: ({ children, ...rest }) => (
<Text fontWeight={500} color={theme().mineshaftGray} {...rest}>
{children}
</Text>
),
blue: ({ children, ...rest }) => (
<Text fontWeight={500} color={theme().royalBlue} {...rest}>
{children}
</Text>
)
}
export const GlobalStyle = createGlobalStyle` export const GlobalStyle = createGlobalStyle`
@import url('https://rsms.me/inter/inter.css'); @import url('https://rsms.me/inter/inter.css');
html { font-family: 'Inter', sans-serif; } html { font-family: 'Inter', sans-serif; }
......
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