Commit 30887ef1 authored by Ian Lapham's avatar Ian Lapham Committed by Noah Zinsmeister

Add slippage option (#369)

* add custom slippage to /swap
parent 12344f74
......@@ -80,5 +80,6 @@
"symbol": "Symbol",
"decimals": "Decimals",
"enterTokenCont": "Enter a token address to continue",
"priceChange": "This trade will cause the price to change by"
"priceChange": "Expected price slippage",
"forAtLeast" : "for at least "
}
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="9" cy="9" r="9" fill="#E1E1E1"/>
<path d="M8.06493 10.8317H9.15706V10.7592C9.17233 9.88089 9.42436 9.48757 10.0735 9.08662C10.7571 8.67421 11.1771 8.09378 11.1771 7.23459C11.1771 5.99354 10.2377 5.15344 8.83629 5.15344C7.54942 5.15344 6.51839 5.90571 6.46875 7.28041H7.62961C7.67543 6.47086 8.25204 6.11573 8.83629 6.11573C9.48546 6.11573 10.0124 6.54724 10.0124 7.22313C10.0124 7.79211 9.65729 8.19306 9.20288 8.47564C8.49262 8.91096 8.07257 9.34246 8.06493 10.7592V10.8317ZM8.64154 13.1534C9.05777 13.1534 9.40527 12.8136 9.40527 12.3897C9.40527 11.9735 9.05777 11.6298 8.64154 11.6298C8.22149 11.6298 7.87782 11.9735 7.87782 12.3897C7.87782 12.8136 8.22149 13.1534 8.64154 13.1534Z" fill="#737373"/>
</svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="8" cy="8" r="8" fill="#E1E1E1"/>
<path d="M7.09618 9.67828H8.18831V9.60573C8.20358 8.72745 8.45561 8.33413 9.10477 7.93317C9.78831 7.52076 10.2084 6.94033 10.2084 6.08115C10.2084 4.8401 9.26897 4 7.86754 4C6.58067 4 5.54964 4.75227 5.5 6.12697H6.66086C6.70668 5.31742 7.28329 4.96229 7.86754 4.96229C8.51671 4.96229 9.04368 5.39379 9.04368 6.06969C9.04368 6.63866 8.68854 7.03962 8.23413 7.3222C7.52387 7.75752 7.10382 8.18902 7.09618 9.60573V9.67828ZM7.67279 12C8.08902 12 8.43652 11.6601 8.43652 11.2363C8.43652 10.82 8.08902 10.4764 7.67279 10.4764C7.25274 10.4764 6.90907 10.82 6.90907 11.2363C6.90907 11.6601 7.25274 12 7.67279 12Z" fill="#737373"/>
</svg>
......@@ -21,7 +21,7 @@ const ContainerRow = styled.div`
justify-content: center;
align-items: center;
border-radius: 1.25rem;
box-shadow: 0 0 0 0.5px ${({ error, theme }) => (error ? theme.salmonRed : theme.mercuryGray)};
box-shadow: 0 0 0 1px ${({ error, theme }) => (error ? theme.salmonRed : theme.mercuryGray)};
background-color: ${({ theme }) => theme.white};
transition: box-shadow 200ms ease-in-out;
`
......
......@@ -32,10 +32,10 @@ const SummaryWrapperContainer = styled.div`
const Details = styled.div`
background-color: ${({ theme }) => theme.concreteGray};
padding: 1.5rem;
/* padding: 1.25rem 1.25rem 1rem 1.25rem; */
border-radius: 1rem;
font-size: 0.75rem;
margin-top: 1rem;
margin: 1rem 0.5rem 0 0.5rem;
`
const ErrorSpan = styled.span`
......@@ -89,13 +89,12 @@ export default function ContextualInfo({
closeDetailsText = 'Hide Details',
contextualInfo = '',
allowExpand = false,
renderTransactionDetails = () => {},
isError = false,
slippageWarning,
highSlippageWarning
highSlippageWarning,
dropDownContent
}) {
const [showDetails, setShowDetails] = useState(false)
return !allowExpand ? (
<SummaryWrapper>{contextualInfo}</SummaryWrapper>
) : (
......@@ -117,7 +116,7 @@ export default function ContextualInfo({
)}
</>
</SummaryWrapperContainer>
{showDetails && <Details>{renderTransactionDetails()}</Details>}
{showDetails && <Details>{dropDownContent()}</Details>}
</>
)
}
......@@ -70,7 +70,7 @@ const CurrencySelect = styled.button`
}
:focus {
box-shadow: 0 0 0.5px 0.5px ${({ theme }) => theme.malibuBlue};
box-shadow: 0 0 1px 1px ${({ theme }) => theme.malibuBlue};
}
:active {
......@@ -104,12 +104,12 @@ const InputPanel = styled.div`
const Container = styled.div`
border-radius: 1.25rem;
box-shadow: 0 0 0 0.5px ${({ error, theme }) => (error ? theme.salmonRed : theme.mercuryGray)};
box-shadow: 0 0 0 1px ${({ error, theme }) => (error ? theme.salmonRed : theme.mercuryGray)};
background-color: ${({ theme }) => theme.white};
transition: box-shadow 200ms ease-in-out;
:focus-within {
box-shadow: 0 0 0.5px 0.5px ${({ theme }) => theme.malibuBlue};
box-shadow: 0 0 1px 1px ${({ theme }) => theme.malibuBlue};
}
`
......
......@@ -84,11 +84,11 @@ const StyledNavLink = styled(NavLink).attrs({
&.${activeClassName} {
background-color: ${({ theme }) => theme.white};
border-radius: 3rem;
box-shadow: 0 0 0.5px 1px ${({ theme }) => theme.mercuryGray};
box-shadow: 0 0 1px 1px ${({ theme }) => theme.mercuryGray};
font-weight: 500;
color: ${({ theme }) => theme.royalBlue};
:hover {
box-shadow: 0 0 0.5px 1px ${({ theme }) => darken(0.1, theme.mercuryGray)};
box-shadow: 0 0 1px 1px ${({ theme }) => darken(0.1, theme.mercuryGray)};
}
}
......
import React, { useState, useEffect, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import styled, { css, keyframes } from 'styled-components'
import { transparentize, darken } from 'polished'
import { amountFormatter } from '../../utils'
import { useDebounce } from '../../hooks'
import question from '../../assets/images/question.svg'
import NewContextualInfo from '../../components/ContextualInfoNew'
const WARNING_TYPE = Object.freeze({
none: 'none',
emptyInput: 'emptyInput',
invalidEntryBound: 'invalidEntryBound',
riskyEntryHigh: 'riskyEntryHigh',
riskyEntryLow: 'riskyEntryLow'
})
const b = text => <Bold>{text}</Bold>
const Flex = styled.div`
display: flex;
justify-content: center;
`
const FlexBetween = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
height: 100%;
`
const WrappedSlippageRow = ({ wrap, ...rest }) => <Flex {...rest} />
const SlippageRow = styled(WrappedSlippageRow)`
position: relative;
flex-wrap: ${({ wrap }) => wrap && 'wrap'};
flex-direction: row;
justify-content: flex-start;
align-items: center;
width: 100%;
padding: 0;
padding-top: ${({ wrap }) => wrap && '0.25rem'};
`
const QuestionWrapper = styled.button`
display: flex;
align-items: center;
justify-content: center;
margin: 0;
padding: 0;
margin-left: 0.4rem;
padding: 0.2rem;
border: none;
background: none;
outline: none;
cursor: default;
border-radius: 36px;
:hover,
:focus {
opacity: 0.7;
}
`
const HelpCircleStyled = styled.img`
height: 18px;
width: 18px;
`
const fadeIn = keyframes`
from {
opacity : 0;
}
to {
opacity : 1;
}
`
const Popup = styled(Flex)`
position: absolute;
width: 228px;
left: -78px;
top: -124px;
flex-direction: column;
align-items: center;
padding: 0.6rem 1rem;
line-height: 150%;
background: ${({ theme }) => theme.charcoalBlack};
border-radius: 8px;
animation: ${fadeIn} 0.15s linear;
color: white;
font-style: italic;
${({ theme }) => theme.mediaWidth.upToSmall`
left: -20px;
`}
`
const FancyButton = styled.button`
align-items: center;
min-width: 55px;
height: 2rem;
border-radius: 36px;
border: 1px solid ${({ theme }) => theme.mercuryGray};
outline: none;
background: ${({ theme }) => theme.white};
:hover {
cursor: inherit;
border: 1px solid ${({ theme }) => theme.royalBlue};
box-shadow: ${({ theme }) => transparentize(0.6, theme.royalBlue)} 0px 0px 0px 2px;
}
:focus {
box-shadow: ${({ theme }) => transparentize(0.6, theme.royalBlue)} 0px 0px 0px 2px;
}
`
const Option = styled(FancyButton)`
margin-right: 8px;
margin-top: 6px;
${({ active, theme }) =>
active &&
css`
background-color: ${({ theme }) => theme.royalBlue};
color: ${({ theme }) => theme.white};
border: none;
`}
`
const OptionLarge = styled(Option)`
width: 120px;
`
const Input = styled.input`
background: ${({ theme }) => theme.white};
flex-grow: 1;
outline: none;
box-sizing: border-box;
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
-webkit-appearance: none;
}
cursor: inherit;
color: ${({ theme }) => theme.doveGray};
text-align: left;
${({ active }) =>
active &&
css`
color: initial;
cursor: initial;
text-align: right;
`}
${({ placeholder }) =>
placeholder !== 'Custom' &&
css`
text-align: right;
`}
${({ color }) =>
color === 'red' &&
css`
color: ${({ theme }) => theme.salmonRed};
`}
`
const BottomError = styled.div`
${({ show }) =>
show &&
css`
padding-top: 12px;
`}
color: ${({ theme }) => theme.doveGray};
${({ color }) =>
color === 'red' &&
css`
color: ${({ theme }) => theme.salmonRed};
`}
`
const OptionCustom = styled(FancyButton)`
height: 2rem;
position: relative;
width: 120px;
margin-top: 6px;
padding: 0 0.75rem;
${({ active }) =>
active &&
css`
border: 1px solid ${({ theme }) => theme.royalBlue};
`}
${({ color }) =>
color === 'red' &&
css`
border: 1px solid ${({ theme }) => theme.salmonRed};
`}
input {
width: 100%;
height: 100%;
border: 0px;
border-radius: 2rem;
}
`
const Bold = styled.span`
font-weight: 500;
`
const LastSummaryText = styled.div`
margin-top: 0.6rem;
`
const SlippageSelector = styled.div`
background-color: ${({ theme }) => darken(0.04, theme.concreteGray)};
padding: 1rem 1.25rem 1rem 1.25rem;
border-radius: 12px;
`
const Percent = styled.div`
color: inherit;
font-size: 0, 8rem;
flex-grow: 0;
${({ color, theme }) =>
(color === 'faded' &&
css`
color: ${theme.doveGray};
`) ||
(color === 'red' &&
css`
color: ${theme.salmonRed};
`)};
`
const Faded = styled.span`
opacity: 0.7;
`
const TransactionInfo = styled.div`
padding: 1.25rem 1.25rem 1rem 1.25rem;
`
export default function TransactionDetails(props) {
const { t } = useTranslation()
const [activeIndex, setActiveIndex] = useState(3)
const [warningType, setWarningType] = useState(WARNING_TYPE.none)
const inputRef = useRef()
const [showPopup, setPopup] = useState(false)
const [userInput, setUserInput] = useState('')
const debouncedInput = useDebounce(userInput, 150)
useEffect(() => {
if (activeIndex === 4) {
checkBounds(debouncedInput)
}
})
function renderSummary() {
let contextualInfo = ''
let isError = false
if (props.inputError || props.independentError) {
contextualInfo = props.inputError || props.independentError
isError = true
} else if (!props.inputCurrency || !props.outputCurrency) {
contextualInfo = t('selectTokenCont')
} else if (!props.independentValue) {
contextualInfo = t('enterValueCont')
} else if (!props.account) {
contextualInfo = t('noWallet')
isError = true
}
const slippageWarningText = props.highSlippageWarning
? t('highSlippageWarning')
: props.slippageWarning
? t('slippageWarning')
: ''
return (
<NewContextualInfo
openDetailsText={t('transactionDetails')}
closeDetailsText={t('hideDetails')}
contextualInfo={contextualInfo ? contextualInfo : slippageWarningText}
allowExpand={
!!(props.inputCurrency && props.outputCurrency && props.inputValueParsed && props.outputValueParsed)
}
isError={isError}
slippageWarning={props.slippageWarning && !contextualInfo}
highSlippageWarning={props.highSlippageWarning && !contextualInfo}
renderTransactionDetails={renderTransactionDetails}
dropDownContent={dropDownContent}
/>
)
}
const dropDownContent = () => {
return (
<>
{renderTransactionDetails()}
<SlippageSelector>
<SlippageRow>
Limit additional price slippage
<QuestionWrapper
onClick={() => {
setPopup(!showPopup)
}}
onMouseEnter={() => {
setPopup(true)
}}
onMouseLeave={() => {
setPopup(false)
}}
>
<HelpCircleStyled src={question} alt="popup" />
</QuestionWrapper>
{showPopup ? (
<Popup>
Lowering this limit decreases your risk of frontrunning. However, this makes it more likely that your
transaction will fail due to normal price movements.
</Popup>
) : (
''
)}
</SlippageRow>
<SlippageRow wrap>
<Option
onClick={() => {
setFromFixed(1, 0.1)
}}
active={activeIndex === 1}
>
0.1%
</Option>
<Option
onClick={() => {
setFromFixed(2, 0.5)
}}
active={activeIndex === 2}
>
0.5%
</Option>
<OptionLarge
onClick={() => {
setFromFixed(3, 1)
}}
active={activeIndex === 3}
>
1% <Faded>(suggested)</Faded>
</OptionLarge>
<OptionCustom
active={activeIndex === 4}
color={
warningType === WARNING_TYPE.emptyInput
? ''
: warningType !== WARNING_TYPE.none && warningType !== WARNING_TYPE.riskyEntryLow
? 'red'
: ''
}
onClick={() => {
setFromCustom()
}}
>
<FlexBetween>
{!(warningType === WARNING_TYPE.none || warningType === WARNING_TYPE.emptyInput) && (
<span role="img" aria-label="warning">
⚠️
</span>
)}
<Input
tabIndex={-1}
ref={inputRef}
active={activeIndex === 4}
placeholder={
activeIndex === 4
? !!userInput
? ''
: '0'
: activeIndex !== 4 && userInput !== ''
? userInput
: 'Custom'
}
value={activeIndex === 4 ? userInput : ''}
onChange={parseInput}
color={
warningType === WARNING_TYPE.emptyInput
? ''
: warningType !== WARNING_TYPE.none && warningType !== WARNING_TYPE.riskyEntryLow
? 'red'
: ''
}
/>
<Percent
color={
activeIndex !== 4
? 'faded'
: warningType === WARNING_TYPE.riskyEntryHigh || warningType === WARNING_TYPE.invalidEntryBound
? 'red'
: ''
}
>
%
</Percent>
</FlexBetween>
</OptionCustom>
</SlippageRow>
<SlippageRow>
<BottomError
show={activeIndex === 4}
color={
warningType === WARNING_TYPE.emptyInput
? ''
: warningType !== WARNING_TYPE.none && warningType !== WARNING_TYPE.riskyEntryLow
? 'red'
: ''
}
>
{activeIndex === 4 && warningType.toString() === 'none' && 'Custom slippage value entered'}
{warningType === WARNING_TYPE.emptyInput && 'Enter a slippage percentage.'}
{warningType === WARNING_TYPE.invalidEntryBound && 'Please select value less than 50%'}
{warningType === WARNING_TYPE.riskyEntryHigh && 'Your transaction may be frontrun.'}
{warningType === WARNING_TYPE.riskyEntryLow && 'Your transaction may fail.'}
</BottomError>
</SlippageRow>
</SlippageSelector>
</>
)
}
const setFromCustom = () => {
setActiveIndex(4)
inputRef.current.focus()
// if there's a value, evaluate the bounds
checkBounds(userInput)
}
// used for slippage presets
const setFromFixed = (index, slippage) => {
// update slippage in parent, reset errors and input state
updateSlippage(slippage)
setWarningType(WARNING_TYPE.none)
setActiveIndex(index)
props.setcustomSlippageError('valid`')
}
const checkBounds = slippageValue => {
setWarningType(WARNING_TYPE.none)
props.setcustomSlippageError('valid')
if (slippageValue === '') {
props.setcustomSlippageError('invalid')
return setWarningType(WARNING_TYPE.emptyInput)
}
// check bounds and set errors
if (slippageValue < 0 || slippageValue > 50) {
props.setcustomSlippageError('invalid')
return setWarningType(WARNING_TYPE.invalidEntryBound)
}
if (slippageValue >= 0 && slippageValue < 0.1) {
props.setcustomSlippageError('valid')
setWarningType(WARNING_TYPE.riskyEntryLow)
}
if (slippageValue > 5) {
props.setcustomSlippageError('warning')
setWarningType(WARNING_TYPE.riskyEntryHigh)
}
//update the actual slippage value in parent
updateSlippage(slippageValue)
}
// check that the theyve entered number and correct decimal
const parseInput = e => {
let input = e.target.value
// restrict to 2 decimal places
let acceptableValues = [/^$/, /^\d{1,2}$/, /^\d{0,2}\.\d{0,2}$/]
// if its within accepted decimal limit, update the input state
if (acceptableValues.some(a => a.test(input))) {
setUserInput(input)
}
}
const updateSlippage = newSlippage => {
// round to 2 decimals to prevent ethers error
let numParsed = parseFloat((newSlippage * 100).toFixed(2))
// set both slippage values in parents
props.setRawSlippage(numParsed)
props.setRawTokenSlippage(numParsed)
}
const renderTransactionDetails = () => {
if (props.independentField === props.INPUT) {
return (
<TransactionInfo>
<div>
{t('youAreSelling')}{' '}
{b(
`${amountFormatter(
props.independentValueParsed,
props.independentDecimals,
Math.min(4, props.independentDecimals)
)} ${props.inputSymbol}`
)}{' '}
{t('forAtLeast')}
{b(
`${amountFormatter(
props.dependentValueMinumum,
props.dependentDecimals,
Math.min(4, props.dependentDecimals)
)} ${props.outputSymbol}`
)}
.
</div>
<LastSummaryText>
{t('priceChange')} {b(`${props.percentSlippageFormatted}%`)}.
</LastSummaryText>
</TransactionInfo>
)
} else {
return (
<TransactionInfo>
<div>
{t('youAreBuying')}{' '}
{b(
`${amountFormatter(
props.independentValueParsed,
props.independentDecimals,
Math.min(4, props.independentDecimals)
)} ${props.outputSymbol}`
)}
.
</div>
<LastSummaryText>
{t('itWillCost')}{' '}
{b(
`${amountFormatter(
props.dependentValueMaximum,
props.dependentDecimals,
Math.min(4, props.dependentDecimals)
)} ${props.inputSymbol}`
)}{' '}
{t('orTransFail')}
</LastSummaryText>
<LastSummaryText>
{t('priceChange')} {b(`${props.percentSlippageFormatted}%`)}.
</LastSummaryText>
</TransactionInfo>
)
}
}
return <>{renderSummary()}</>
}
......@@ -64,7 +64,7 @@ const StyledNavLink = styled(NavLink).attrs({
&.${activeClassName} {
background-color: ${({ theme }) => theme.white};
border-radius: 3rem;
box-shadow: 0 0 0.5px 0.5px ${({ theme }) => theme.mercuryGray};
box-shadow: 0 0 1px 1px ${({ theme }) => theme.mercuryGray};
font-weight: 500;
color: ${({ theme }) => theme.royalBlue};
}
......
......@@ -612,7 +612,7 @@ export default function Swap({ initialCurrency }) {
isError={isError}
slippageWarning={slippageWarning && slippageWarningText}
highSlippageWarning={highSlippageWarning && slippageWarningText}
renderTransactionDetails={renderTransactionDetails}
dropDownContent={renderTransactionDetails}
/>
)
}
......
......@@ -7,8 +7,8 @@ import styled from 'styled-components'
import { Button } from '../../theme'
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import NewContextualInfo from '../../components/ContextualInfoNew'
import OversizedPanel from '../../components/OversizedPanel'
import TransactionDetails from '../../components/TransactionDetails'
import ArrowDownBlue from '../../assets/images/arrow-down-blue.svg'
import ArrowDownGrey from '../../assets/images/arrow-down-grey.svg'
import { amountFormatter, calculateGasMargin } from '../../utils'
......@@ -26,8 +26,8 @@ const TOKEN_TO_ETH = 1
const TOKEN_TO_TOKEN = 2
// denominated in bips
const ALLOWED_SLIPPAGE = ethers.utils.bigNumberify(200)
const TOKEN_ALLOWED_SLIPPAGE = ethers.utils.bigNumberify(400)
const ALLOWED_SLIPPAGE_DEFAULT = 100
const TOKEN_ALLOWED_SLIPPAGE_DEFAULT = 100
// denominated in seconds
const DEADLINE_FROM_NOW = 60 * 15
......@@ -35,14 +35,6 @@ const DEADLINE_FROM_NOW = 60 * 15
// denominated in bips
const GAS_MARGIN = ethers.utils.bigNumberify(1000)
const BlueSpan = styled.span`
color: ${({ theme }) => theme.royalBlue};
`
const LastSummaryText = styled.div`
margin-top: 1rem;
`
const DownArrowBackground = styled.div`
${({ theme }) => theme.flexRowNoWrap}
justify-content: center;
......@@ -81,9 +73,9 @@ const Flex = styled.div`
}
`
function calculateSlippageBounds(value, token = false) {
function calculateSlippageBounds(value, token = false, tokenAllowedSlippage, allowedSlippage) {
if (value) {
const offset = value.mul(token ? TOKEN_ALLOWED_SLIPPAGE : ALLOWED_SLIPPAGE).div(ethers.utils.bigNumberify(10000))
const offset = value.mul(token ? tokenAllowedSlippage : allowedSlippage).div(ethers.utils.bigNumberify(10000))
const minimum = value.sub(offset)
const maximum = value.add(offset)
return {
......@@ -244,12 +236,18 @@ export default function Swap({ initialCurrency }) {
const addTransaction = useTransactionAdder()
const [rawSlippage, setRawSlippage] = useState(ALLOWED_SLIPPAGE_DEFAULT)
const [rawTokenSlippage, setRawTokenSlippage] = useState(TOKEN_ALLOWED_SLIPPAGE_DEFAULT)
let allowedSlippageBig = ethers.utils.bigNumberify(rawSlippage)
let tokenAllowedSlippageBig = ethers.utils.bigNumberify(rawTokenSlippage)
// analytics
useEffect(() => {
ReactGA.pageview(window.location.pathname + window.location.search)
}, [])
// core swap state
// core swap state-
const [swapState, dispatchSwapState] = useReducer(swapStateReducer, initialCurrency, getInitialSwapState)
const { independentValue, dependentValue, independentField, inputCurrency, outputCurrency } = swapState
......@@ -326,7 +324,9 @@ export default function Swap({ initialCurrency }) {
// calculate slippage from target rate
const { minimum: dependentValueMinumum, maximum: dependentValueMaximum } = calculateSlippageBounds(
dependentValue,
swapType === TOKEN_TO_TOKEN
swapType === TOKEN_TO_TOKEN,
tokenAllowedSlippageBig,
allowedSlippageBig
)
// validate input allowance + balance
......@@ -496,118 +496,6 @@ export default function Swap({ initialCurrency }) {
return `Balance: ${value}`
}
function renderTransactionDetails() {
ReactGA.event({
category: 'TransactionDetail',
action: 'Open'
})
const b = text => <BlueSpan>{text}</BlueSpan>
if (independentField === INPUT) {
return (
<div>
<div>
{t('youAreSelling')}{' '}
{b(
`${amountFormatter(
independentValueParsed,
independentDecimals,
Math.min(4, independentDecimals)
)} ${inputSymbol}`
)}
.
</div>
<LastSummaryText>
{t('youWillReceive')}{' '}
{b(
`${amountFormatter(
dependentValueMinumum,
dependentDecimals,
Math.min(4, dependentDecimals)
)} ${outputSymbol}`
)}{' '}
{t('orTransFail')}
</LastSummaryText>
<LastSummaryText>
{(slippageWarning || highSlippageWarning) && (
<span role="img" aria-label="warning">
⚠️
</span>
)}
{t('priceChange')} {b(`${percentSlippageFormatted}%`)}.
</LastSummaryText>
</div>
)
} else {
return (
<div>
<div>
{t('youAreBuying')}{' '}
{b(
`${amountFormatter(
independentValueParsed,
independentDecimals,
Math.min(4, independentDecimals)
)} ${outputSymbol}`
)}
.
</div>
<LastSummaryText>
{t('itWillCost')}{' '}
{b(
`${amountFormatter(
dependentValueMaximum,
dependentDecimals,
Math.min(4, dependentDecimals)
)} ${inputSymbol}`
)}{' '}
{t('orTransFail')}
</LastSummaryText>
<LastSummaryText>
{t('priceChange')} {b(`${percentSlippageFormatted}%`)}.
</LastSummaryText>
</div>
)
}
}
function renderSummary() {
let contextualInfo = ''
let isError = false
if (inputError || independentError) {
contextualInfo = inputError || independentError
isError = true
} else if (!inputCurrency || !outputCurrency) {
contextualInfo = t('selectTokenCont')
} else if (!independentValue) {
contextualInfo = t('enterValueCont')
} else if (!account) {
contextualInfo = t('noWallet')
isError = true
}
const slippageWarningText = highSlippageWarning
? t('highSlippageWarning')
: slippageWarning
? t('slippageWarning')
: ''
return (
<NewContextualInfo
openDetailsText={t('transactionDetails')}
closeDetailsText={t('hideDetails')}
contextualInfo={contextualInfo ? contextualInfo : slippageWarningText}
allowExpand={!!(inputCurrency && outputCurrency && inputValueParsed && outputValueParsed)}
isError={isError}
slippageWarning={slippageWarning && !contextualInfo}
highSlippageWarning={highSlippageWarning && !contextualInfo}
renderTransactionDetails={renderTransactionDetails}
/>
)
}
async function onSwap() {
const deadline = Math.ceil(Date.now() / 1000) + DEADLINE_FROM_NOW
......@@ -664,6 +552,8 @@ export default function Swap({ initialCurrency }) {
})
}
const [customSlippageError, setcustomSlippageError] = useState('')
return (
<>
<CurrencyInputPanel
......@@ -743,10 +633,39 @@ export default function Swap({ initialCurrency }) {
)}
</ExchangeRateWrapper>
</OversizedPanel>
{renderSummary()}
<TransactionDetails
account={account}
setRawSlippage={setRawSlippage}
setRawTokenSlippage={setRawTokenSlippage}
rawSlippage={rawSlippage}
slippageWarning={slippageWarning}
highSlippageWarning={highSlippageWarning}
inputError={inputError}
independentError={independentError}
inputCurrency={inputCurrency}
outputCurrency={outputCurrency}
independentValue={independentValue}
independentValueParsed={independentValueParsed}
independentField={independentField}
INPUT={INPUT}
inputValueParsed={inputValueParsed}
outputValueParsed={outputValueParsed}
inputSymbol={inputSymbol}
outputSymbol={outputSymbol}
dependentValueMinumum={dependentValueMinumum}
dependentValueMaximum={dependentValueMaximum}
dependentDecimals={dependentDecimals}
independentDecimals={independentDecimals}
percentSlippageFormatted={percentSlippageFormatted}
setcustomSlippageError={setcustomSlippageError}
/>
<Flex>
<Button disabled={!isValid} onClick={onSwap} warning={highSlippageWarning}>
{highSlippageWarning ? t('swapAnyway') : t('swap')}
<Button
disabled={!isValid || customSlippageError === 'invalid'}
onClick={onSwap}
warning={highSlippageWarning || customSlippageError === 'warning'}
>
{highSlippageWarning || customSlippageError === 'warning' ? t('swapAnyway') : t('swap')}
</Button>
</Flex>
</>
......
......@@ -38,6 +38,9 @@ const theme = {
chaliceGray: '#AEAEAE',
doveGray: '#737373',
mineshaftGray: '#2B2B2B',
buttonOutlineGrey: '#f2f2f2',
//blacks
charcoalBlack: '#404040',
// blues
zumthorBlue: '#EBF4FF',
malibuBlue: '#5CA2FF',
......@@ -53,6 +56,7 @@ const theme = {
// pink
uniswapPink: '#DC6BE5',
connectedGreen: '#27AE60',
// media queries
mediaWidth: mediaWidthTemplates,
// css snippets
......
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