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 @@ ...@@ -80,5 +80,6 @@
"symbol": "Symbol", "symbol": "Symbol",
"decimals": "Decimals", "decimals": "Decimals",
"enterTokenCont": "Enter a token address to continue", "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` ...@@ -21,7 +21,7 @@ const ContainerRow = styled.div`
justify-content: center; justify-content: center;
align-items: center; align-items: center;
border-radius: 1.25rem; 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}; background-color: ${({ theme }) => theme.white};
transition: box-shadow 200ms ease-in-out; transition: box-shadow 200ms ease-in-out;
` `
......
...@@ -32,10 +32,10 @@ const SummaryWrapperContainer = styled.div` ...@@ -32,10 +32,10 @@ const SummaryWrapperContainer = styled.div`
const Details = styled.div` const Details = styled.div`
background-color: ${({ theme }) => theme.concreteGray}; background-color: ${({ theme }) => theme.concreteGray};
padding: 1.5rem; /* padding: 1.25rem 1.25rem 1rem 1.25rem; */
border-radius: 1rem; border-radius: 1rem;
font-size: 0.75rem; font-size: 0.75rem;
margin-top: 1rem; margin: 1rem 0.5rem 0 0.5rem;
` `
const ErrorSpan = styled.span` const ErrorSpan = styled.span`
...@@ -89,13 +89,12 @@ export default function ContextualInfo({ ...@@ -89,13 +89,12 @@ export default function ContextualInfo({
closeDetailsText = 'Hide Details', closeDetailsText = 'Hide Details',
contextualInfo = '', contextualInfo = '',
allowExpand = false, allowExpand = false,
renderTransactionDetails = () => {},
isError = false, isError = false,
slippageWarning, slippageWarning,
highSlippageWarning highSlippageWarning,
dropDownContent
}) { }) {
const [showDetails, setShowDetails] = useState(false) const [showDetails, setShowDetails] = useState(false)
return !allowExpand ? ( return !allowExpand ? (
<SummaryWrapper>{contextualInfo}</SummaryWrapper> <SummaryWrapper>{contextualInfo}</SummaryWrapper>
) : ( ) : (
...@@ -117,7 +116,7 @@ export default function ContextualInfo({ ...@@ -117,7 +116,7 @@ export default function ContextualInfo({
)} )}
</> </>
</SummaryWrapperContainer> </SummaryWrapperContainer>
{showDetails && <Details>{renderTransactionDetails()}</Details>} {showDetails && <Details>{dropDownContent()}</Details>}
</> </>
) )
} }
...@@ -70,7 +70,7 @@ const CurrencySelect = styled.button` ...@@ -70,7 +70,7 @@ const CurrencySelect = styled.button`
} }
:focus { :focus {
box-shadow: 0 0 0.5px 0.5px ${({ theme }) => theme.malibuBlue}; box-shadow: 0 0 1px 1px ${({ theme }) => theme.malibuBlue};
} }
:active { :active {
...@@ -104,12 +104,12 @@ const InputPanel = styled.div` ...@@ -104,12 +104,12 @@ const InputPanel = styled.div`
const Container = styled.div` const Container = styled.div`
border-radius: 1.25rem; 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}; background-color: ${({ theme }) => theme.white};
transition: box-shadow 200ms ease-in-out; transition: box-shadow 200ms ease-in-out;
:focus-within { :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({ ...@@ -84,11 +84,11 @@ const StyledNavLink = styled(NavLink).attrs({
&.${activeClassName} { &.${activeClassName} {
background-color: ${({ theme }) => theme.white}; background-color: ${({ theme }) => theme.white};
border-radius: 3rem; 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; font-weight: 500;
color: ${({ theme }) => theme.royalBlue}; color: ${({ theme }) => theme.royalBlue};
:hover { :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)};
} }
} }
......
This diff is collapsed.
...@@ -64,7 +64,7 @@ const StyledNavLink = styled(NavLink).attrs({ ...@@ -64,7 +64,7 @@ const StyledNavLink = styled(NavLink).attrs({
&.${activeClassName} { &.${activeClassName} {
background-color: ${({ theme }) => theme.white}; background-color: ${({ theme }) => theme.white};
border-radius: 3rem; 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; font-weight: 500;
color: ${({ theme }) => theme.royalBlue}; color: ${({ theme }) => theme.royalBlue};
} }
......
...@@ -612,7 +612,7 @@ export default function Swap({ initialCurrency }) { ...@@ -612,7 +612,7 @@ export default function Swap({ initialCurrency }) {
isError={isError} isError={isError}
slippageWarning={slippageWarning && slippageWarningText} slippageWarning={slippageWarning && slippageWarningText}
highSlippageWarning={highSlippageWarning && slippageWarningText} highSlippageWarning={highSlippageWarning && slippageWarningText}
renderTransactionDetails={renderTransactionDetails} dropDownContent={renderTransactionDetails}
/> />
) )
} }
......
...@@ -7,8 +7,8 @@ import styled from 'styled-components' ...@@ -7,8 +7,8 @@ import styled from 'styled-components'
import { Button } from '../../theme' import { Button } from '../../theme'
import CurrencyInputPanel from '../../components/CurrencyInputPanel' import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import NewContextualInfo from '../../components/ContextualInfoNew'
import OversizedPanel from '../../components/OversizedPanel' import OversizedPanel from '../../components/OversizedPanel'
import TransactionDetails from '../../components/TransactionDetails'
import ArrowDownBlue from '../../assets/images/arrow-down-blue.svg' import ArrowDownBlue from '../../assets/images/arrow-down-blue.svg'
import ArrowDownGrey from '../../assets/images/arrow-down-grey.svg' import ArrowDownGrey from '../../assets/images/arrow-down-grey.svg'
import { amountFormatter, calculateGasMargin } from '../../utils' import { amountFormatter, calculateGasMargin } from '../../utils'
...@@ -26,8 +26,8 @@ const TOKEN_TO_ETH = 1 ...@@ -26,8 +26,8 @@ const TOKEN_TO_ETH = 1
const TOKEN_TO_TOKEN = 2 const TOKEN_TO_TOKEN = 2
// denominated in bips // denominated in bips
const ALLOWED_SLIPPAGE = ethers.utils.bigNumberify(200) const ALLOWED_SLIPPAGE_DEFAULT = 100
const TOKEN_ALLOWED_SLIPPAGE = ethers.utils.bigNumberify(400) const TOKEN_ALLOWED_SLIPPAGE_DEFAULT = 100
// denominated in seconds // denominated in seconds
const DEADLINE_FROM_NOW = 60 * 15 const DEADLINE_FROM_NOW = 60 * 15
...@@ -35,14 +35,6 @@ const DEADLINE_FROM_NOW = 60 * 15 ...@@ -35,14 +35,6 @@ const DEADLINE_FROM_NOW = 60 * 15
// denominated in bips // denominated in bips
const GAS_MARGIN = ethers.utils.bigNumberify(1000) 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` const DownArrowBackground = styled.div`
${({ theme }) => theme.flexRowNoWrap} ${({ theme }) => theme.flexRowNoWrap}
justify-content: center; justify-content: center;
...@@ -81,9 +73,9 @@ const Flex = styled.div` ...@@ -81,9 +73,9 @@ const Flex = styled.div`
} }
` `
function calculateSlippageBounds(value, token = false) { function calculateSlippageBounds(value, token = false, tokenAllowedSlippage, allowedSlippage) {
if (value) { 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 minimum = value.sub(offset)
const maximum = value.add(offset) const maximum = value.add(offset)
return { return {
...@@ -244,12 +236,18 @@ export default function Swap({ initialCurrency }) { ...@@ -244,12 +236,18 @@ export default function Swap({ initialCurrency }) {
const addTransaction = useTransactionAdder() 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 // analytics
useEffect(() => { useEffect(() => {
ReactGA.pageview(window.location.pathname + window.location.search) ReactGA.pageview(window.location.pathname + window.location.search)
}, []) }, [])
// core swap state // core swap state-
const [swapState, dispatchSwapState] = useReducer(swapStateReducer, initialCurrency, getInitialSwapState) const [swapState, dispatchSwapState] = useReducer(swapStateReducer, initialCurrency, getInitialSwapState)
const { independentValue, dependentValue, independentField, inputCurrency, outputCurrency } = swapState const { independentValue, dependentValue, independentField, inputCurrency, outputCurrency } = swapState
...@@ -326,7 +324,9 @@ export default function Swap({ initialCurrency }) { ...@@ -326,7 +324,9 @@ export default function Swap({ initialCurrency }) {
// calculate slippage from target rate // calculate slippage from target rate
const { minimum: dependentValueMinumum, maximum: dependentValueMaximum } = calculateSlippageBounds( const { minimum: dependentValueMinumum, maximum: dependentValueMaximum } = calculateSlippageBounds(
dependentValue, dependentValue,
swapType === TOKEN_TO_TOKEN swapType === TOKEN_TO_TOKEN,
tokenAllowedSlippageBig,
allowedSlippageBig
) )
// validate input allowance + balance // validate input allowance + balance
...@@ -496,118 +496,6 @@ export default function Swap({ initialCurrency }) { ...@@ -496,118 +496,6 @@ export default function Swap({ initialCurrency }) {
return `Balance: ${value}` 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() { async function onSwap() {
const deadline = Math.ceil(Date.now() / 1000) + DEADLINE_FROM_NOW const deadline = Math.ceil(Date.now() / 1000) + DEADLINE_FROM_NOW
...@@ -664,6 +552,8 @@ export default function Swap({ initialCurrency }) { ...@@ -664,6 +552,8 @@ export default function Swap({ initialCurrency }) {
}) })
} }
const [customSlippageError, setcustomSlippageError] = useState('')
return ( return (
<> <>
<CurrencyInputPanel <CurrencyInputPanel
...@@ -743,10 +633,39 @@ export default function Swap({ initialCurrency }) { ...@@ -743,10 +633,39 @@ export default function Swap({ initialCurrency }) {
)} )}
</ExchangeRateWrapper> </ExchangeRateWrapper>
</OversizedPanel> </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> <Flex>
<Button disabled={!isValid} onClick={onSwap} warning={highSlippageWarning}> <Button
{highSlippageWarning ? t('swapAnyway') : t('swap')} disabled={!isValid || customSlippageError === 'invalid'}
onClick={onSwap}
warning={highSlippageWarning || customSlippageError === 'warning'}
>
{highSlippageWarning || customSlippageError === 'warning' ? t('swapAnyway') : t('swap')}
</Button> </Button>
</Flex> </Flex>
</> </>
......
...@@ -38,6 +38,9 @@ const theme = { ...@@ -38,6 +38,9 @@ const theme = {
chaliceGray: '#AEAEAE', chaliceGray: '#AEAEAE',
doveGray: '#737373', doveGray: '#737373',
mineshaftGray: '#2B2B2B', mineshaftGray: '#2B2B2B',
buttonOutlineGrey: '#f2f2f2',
//blacks
charcoalBlack: '#404040',
// blues // blues
zumthorBlue: '#EBF4FF', zumthorBlue: '#EBF4FF',
malibuBlue: '#5CA2FF', malibuBlue: '#5CA2FF',
...@@ -53,6 +56,7 @@ const theme = { ...@@ -53,6 +56,7 @@ const theme = {
// pink // pink
uniswapPink: '#DC6BE5', uniswapPink: '#DC6BE5',
connectedGreen: '#27AE60', connectedGreen: '#27AE60',
// media queries // media queries
mediaWidth: mediaWidthTemplates, mediaWidth: mediaWidthTemplates,
// css snippets // 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