Commit 7ee761a5 authored by Moody Salem's avatar Moody Salem Committed by GitHub

feat: automatic slippage tolerance (#1463)

* automatic slippage tolerance start

* get it compiling

* out of range/in range behavior of slippage tolerance in add

* small useDerivedSwapInfo refactor

* improve useSwapSlippageTolerance

* fix unit test

* thread placeholder slippage through

* small improvement to slippage input behavior

* fix the display bug

* fix tx settings modal ux

* don't pass props unnecessarily

* switch back to static swap slippage for now

bump migrate slippage to .75%

* fix font size

* add flag for auto slippage migration

validate version updates even more
Co-authored-by: default avatarNoah Zinsmeister <noahwz@gmail.com>
parent 78e95f60
...@@ -3,6 +3,7 @@ import styled from 'styled-components' ...@@ -3,6 +3,7 @@ import styled from 'styled-components'
import { darken } from 'polished' import { darken } from 'polished'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { NavLink, Link as HistoryLink } from 'react-router-dom' import { NavLink, Link as HistoryLink } from 'react-router-dom'
import { Percent } from '@uniswap/sdk-core'
import { ArrowLeft } from 'react-feather' import { ArrowLeft } from 'react-feather'
import { RowBetween } from '../Row' import { RowBetween } from '../Row'
...@@ -80,7 +81,6 @@ export function FindPoolTabs({ origin }: { origin: string }) { ...@@ -80,7 +81,6 @@ export function FindPoolTabs({ origin }: { origin: string }) {
<StyledArrowLeft /> <StyledArrowLeft />
</HistoryLink> </HistoryLink>
<ActiveText>Import Pool</ActiveText> <ActiveText>Import Pool</ActiveText>
<SettingsTab />
</RowBetween> </RowBetween>
</Tabs> </Tabs>
) )
...@@ -90,10 +90,12 @@ export function AddRemoveTabs({ ...@@ -90,10 +90,12 @@ export function AddRemoveTabs({
adding, adding,
creating, creating,
positionID, positionID,
defaultSlippage,
}: { }: {
adding: boolean adding: boolean
creating: boolean creating: boolean
positionID?: string | undefined positionID?: string | undefined
defaultSlippage: Percent
}) { }) {
const theme = useTheme() const theme = useTheme()
...@@ -118,7 +120,7 @@ export function AddRemoveTabs({ ...@@ -118,7 +120,7 @@ export function AddRemoveTabs({
<TYPE.mediumHeader fontWeight={500} fontSize={20}> <TYPE.mediumHeader fontWeight={500} fontSize={20}>
{creating ? 'Create a pair' : adding ? 'Add Liquidity' : 'Remove Liquidity'} {creating ? 'Create a pair' : adding ? 'Add Liquidity' : 'Remove Liquidity'}
</TYPE.mediumHeader> </TYPE.mediumHeader>
<SettingsTab /> <SettingsTab placeholderSlippage={defaultSlippage} />
</RowBetween> </RowBetween>
</Tabs> </Tabs>
) )
......
import JSBI from 'jsbi'
import React, { useContext, useRef, useState } from 'react' import React, { useContext, useRef, useState } from 'react'
import { Settings, X } from 'react-feather' import { Settings, X } from 'react-feather'
import ReactGA from 'react-ga' import ReactGA from 'react-ga'
...@@ -7,12 +6,7 @@ import styled, { ThemeContext } from 'styled-components' ...@@ -7,12 +6,7 @@ import styled, { ThemeContext } from 'styled-components'
import { useOnClickOutside } from '../../hooks/useOnClickOutside' import { useOnClickOutside } from '../../hooks/useOnClickOutside'
import { ApplicationModal } from '../../state/application/actions' import { ApplicationModal } from '../../state/application/actions'
import { useModalOpen, useToggleSettingsMenu } from '../../state/application/hooks' import { useModalOpen, useToggleSettingsMenu } from '../../state/application/hooks'
import { import { useExpertModeManager, useUserSingleHopOnly } from '../../state/user/hooks'
useExpertModeManager,
useUserTransactionTTL,
useUserSlippageTolerance,
useUserSingleHopOnly,
} from '../../state/user/hooks'
import { TYPE } from '../../theme' import { TYPE } from '../../theme'
import { ButtonError } from '../Button' import { ButtonError } from '../Button'
import { AutoColumn } from '../Column' import { AutoColumn } from '../Column'
...@@ -21,6 +15,7 @@ import QuestionHelper from '../QuestionHelper' ...@@ -21,6 +15,7 @@ import QuestionHelper from '../QuestionHelper'
import { RowBetween, RowFixed } from '../Row' import { RowBetween, RowFixed } from '../Row'
import Toggle from '../Toggle' import Toggle from '../Toggle'
import TransactionSettings from '../TransactionSettings' import TransactionSettings from '../TransactionSettings'
import { Percent } from '@uniswap/sdk-core'
const StyledMenuIcon = styled(Settings)` const StyledMenuIcon = styled(Settings)`
height: 20px; height: 20px;
...@@ -116,15 +111,12 @@ const ModalContentWrapper = styled.div` ...@@ -116,15 +111,12 @@ const ModalContentWrapper = styled.div`
border-radius: 20px; border-radius: 20px;
` `
export default function SettingsTab() { export default function SettingsTab({ placeholderSlippage }: { placeholderSlippage: Percent }) {
const node = useRef<HTMLDivElement>() const node = useRef<HTMLDivElement>()
const open = useModalOpen(ApplicationModal.SETTINGS) const open = useModalOpen(ApplicationModal.SETTINGS)
const toggle = useToggleSettingsMenu() const toggle = useToggleSettingsMenu()
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
const [userSlippageTolerance, setUserslippageTolerance] = useUserSlippageTolerance()
const [ttl, setTtl] = useUserTransactionTTL()
const [expertMode, toggleExpertMode] = useExpertModeManager() const [expertMode, toggleExpertMode] = useExpertModeManager()
...@@ -191,12 +183,7 @@ export default function SettingsTab() { ...@@ -191,12 +183,7 @@ export default function SettingsTab() {
<Text fontWeight={600} fontSize={14}> <Text fontWeight={600} fontSize={14}>
Transaction Settings Transaction Settings
</Text> </Text>
<TransactionSettings <TransactionSettings placeholderSlippage={placeholderSlippage} />
rawSlippage={JSBI.toNumber(userSlippageTolerance.numerator)}
setRawSlippage={setUserslippageTolerance}
deadline={ttl}
setDeadline={setTtl}
/>
<Text fontWeight={600} fontSize={14}> <Text fontWeight={600} fontSize={14}>
Interface Settings Interface Settings
</Text> </Text>
......
import React, { useState, useRef, useContext } from 'react' import React, { useState, useContext } from 'react'
import { Percent } from '@uniswap/sdk-core'
import styled, { ThemeContext } from 'styled-components' import styled, { ThemeContext } from 'styled-components'
import QuestionHelper from '../QuestionHelper' import QuestionHelper from '../QuestionHelper'
import { TYPE } from '../../theme' import { TYPE } from '../../theme'
import { AutoColumn } from '../Column' import { AutoColumn } from '../Column'
import { RowBetween, RowFixed } from '../Row' import { RowBetween, RowFixed } from '../Row'
import { DEFAULT_DEADLINE_FROM_NOW } from 'constants/index'
import { darken } from 'polished' import { darken } from 'polished'
import { useSetUserSlippageTolerance, useUserSlippageTolerance, useUserTransactionTTL } from 'state/user/hooks'
enum SlippageError { enum SlippageError {
InvalidInput = 'InvalidInput', InvalidInput = 'InvalidInput',
RiskyLow = 'RiskyLow',
RiskyHigh = 'RiskyHigh',
} }
enum DeadlineError { enum DeadlineError {
...@@ -64,7 +64,8 @@ const OptionCustom = styled(FancyButton)<{ active?: boolean; warning?: boolean } ...@@ -64,7 +64,8 @@ const OptionCustom = styled(FancyButton)<{ active?: boolean; warning?: boolean }
position: relative; position: relative;
padding: 0 0.75rem; padding: 0 0.75rem;
flex: 1; flex: 1;
border: ${({ theme, active, warning }) => active && `1px solid ${warning ? theme.red1 : theme.primary1}`}; border: ${({ theme, active, warning }) =>
active ? `1px solid ${warning ? theme.red1 : theme.primary1}` : warning && `1px solid ${theme.red1}`};
:hover { :hover {
border: ${({ theme, active, warning }) => border: ${({ theme, active, warning }) =>
active && `1px solid ${warning ? darken(0.1, theme.red1) : darken(0.1, theme.primary1)}`}; active && `1px solid ${warning ? darken(0.1, theme.red1) : darken(0.1, theme.primary1)}`};
...@@ -85,63 +86,63 @@ const SlippageEmojiContainer = styled.span` ...@@ -85,63 +86,63 @@ const SlippageEmojiContainer = styled.span`
`} `}
` `
export interface SlippageTabsProps { export interface TransactionSettingsProps {
rawSlippage: number placeholderSlippage: Percent // varies according to the context in which the settings dialog is placed
setRawSlippage: (rawSlippage: number) => void
deadline: number
setDeadline: (deadline: number) => void
} }
export default function SlippageTabs({ rawSlippage, setRawSlippage, deadline, setDeadline }: SlippageTabsProps) { export default function TransactionSettings({ placeholderSlippage }: TransactionSettingsProps) {
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
const inputRef = useRef<HTMLInputElement>() const userSlippageTolerance = useUserSlippageTolerance()
const setUserSlippageTolerance = useSetUserSlippageTolerance()
const [deadline, setDeadline] = useUserTransactionTTL()
const [slippageInput, setSlippageInput] = useState('') const [slippageInput, setSlippageInput] = useState('')
const [slippageError, setSlippageError] = useState<SlippageError | false>(false)
const [deadlineInput, setDeadlineInput] = useState('') const [deadlineInput, setDeadlineInput] = useState('')
const [deadlineError, setDeadlineError] = useState<DeadlineError | false>(false)
const slippageInputIsValid = function parseSlippageInput(value: string) {
slippageInput === '' || (rawSlippage / 100).toFixed(2) === Number.parseFloat(slippageInput).toFixed(2) // populate what the user typed and clear the error
const deadlineInputIsValid = deadlineInput === '' || (deadline / 60).toString() === deadlineInput setSlippageInput(value)
setSlippageError(false)
let slippageError: SlippageError | undefined
if (slippageInput !== '' && !slippageInputIsValid) { if (value.length === 0) {
slippageError = SlippageError.InvalidInput setUserSlippageTolerance('auto')
} else if (slippageInputIsValid && rawSlippage < 5) {
slippageError = SlippageError.RiskyLow
} else if (slippageInputIsValid && rawSlippage > 100) {
slippageError = SlippageError.RiskyHigh
} else { } else {
slippageError = undefined const parsed = Math.floor(Number.parseFloat(value) * 100)
}
let deadlineError: DeadlineError | undefined if (!Number.isInteger(parsed) || parsed < 0 || parsed > 5000) {
if (deadlineInput !== '' && !deadlineInputIsValid) { setUserSlippageTolerance('auto')
deadlineError = DeadlineError.InvalidInput if (value !== '.') {
setSlippageError(SlippageError.InvalidInput)
}
} else { } else {
deadlineError = undefined setUserSlippageTolerance(new Percent(parsed, 10_000))
} }
function parseCustomSlippage(value: string) {
setSlippageInput(value)
try {
const valueAsIntFromRoundedFloat = Number.parseInt((Number.parseFloat(value) * 100).toString())
if (!Number.isNaN(valueAsIntFromRoundedFloat) && valueAsIntFromRoundedFloat < 5000) {
setRawSlippage(valueAsIntFromRoundedFloat)
} }
} catch {}
} }
const tooLow = userSlippageTolerance !== 'auto' && userSlippageTolerance.lessThan(new Percent(5, 10_000))
const tooHigh = userSlippageTolerance !== 'auto' && userSlippageTolerance.greaterThan(new Percent(1, 100))
function parseCustomDeadline(value: string) { function parseCustomDeadline(value: string) {
// populate what the user typed and clear the error
setDeadlineInput(value) setDeadlineInput(value)
setDeadlineError(false)
try { if (value.length === 0) {
const valueAsInt: number = Number.parseInt(value) * 60 setDeadline(DEFAULT_DEADLINE_FROM_NOW)
if (!Number.isNaN(valueAsInt) && valueAsInt > 0) { } else {
setDeadline(valueAsInt) const parsed: number = Math.floor(Number.parseFloat(value) * 60)
if (!Number.isInteger(parsed) || parsed < 60) {
setDeadlineError(DeadlineError.InvalidInput)
} else {
setDeadline(parsed)
}
} }
} catch {}
} }
return ( return (
...@@ -156,71 +157,56 @@ export default function SlippageTabs({ rawSlippage, setRawSlippage, deadline, se ...@@ -156,71 +157,56 @@ export default function SlippageTabs({ rawSlippage, setRawSlippage, deadline, se
<RowBetween> <RowBetween>
<Option <Option
onClick={() => { onClick={() => {
setSlippageInput('') parseSlippageInput('')
setRawSlippage(10)
}}
active={rawSlippage === 10}
>
0.1%
</Option>
<Option
onClick={() => {
setSlippageInput('')
setRawSlippage(50)
}} }}
active={rawSlippage === 50} active={userSlippageTolerance === 'auto'}
> >
0.5% Auto
</Option> </Option>
<Option <OptionCustom active={userSlippageTolerance !== 'auto'} warning={!!slippageError} tabIndex={-1}>
onClick={() => {
setSlippageInput('')
setRawSlippage(100)
}}
active={rawSlippage === 100}
>
1%
</Option>
<OptionCustom active={![10, 50, 100].includes(rawSlippage)} warning={!slippageInputIsValid} tabIndex={-1}>
<RowBetween> <RowBetween>
{!!slippageInput && {tooLow || tooHigh ? (
(slippageError === SlippageError.RiskyLow || slippageError === SlippageError.RiskyHigh) ? (
<SlippageEmojiContainer> <SlippageEmojiContainer>
<span role="img" aria-label="warning"> <span role="img" aria-label="warning">
⚠️ ⚠️
</span> </span>
</SlippageEmojiContainer> </SlippageEmojiContainer>
) : null} ) : null}
{/* https://github.com/DefinitelyTyped/DefinitelyTyped/issues/30451 */}
<Input <Input
ref={inputRef as any} placeholder={placeholderSlippage.toFixed(2)}
placeholder={(rawSlippage / 100).toFixed(2)} value={
value={slippageInput} slippageInput.length > 0
? slippageInput
: userSlippageTolerance === 'auto'
? ''
: userSlippageTolerance.toFixed(2)
}
onChange={(e) => parseSlippageInput(e.target.value)}
onBlur={() => { onBlur={() => {
parseCustomSlippage((rawSlippage / 100).toFixed(2)) setSlippageInput('')
setSlippageError(false)
}} }}
onChange={(e) => parseCustomSlippage(e.target.value)} color={slippageError ? 'red' : ''}
color={!slippageInputIsValid ? 'red' : ''}
/> />
% %
</RowBetween> </RowBetween>
</OptionCustom> </OptionCustom>
</RowBetween> </RowBetween>
{!!slippageError && ( {slippageError || tooLow || tooHigh ? (
<RowBetween <RowBetween
style={{ style={{
fontSize: '14px', fontSize: '14px',
paddingTop: '7px', paddingTop: '7px',
color: slippageError === SlippageError.InvalidInput ? 'red' : '#F3841E', color: slippageError ? 'red' : '#F3841E',
}} }}
> >
{slippageError === SlippageError.InvalidInput {slippageError
? 'Enter a valid slippage percentage' ? 'Enter a valid slippage percentage'
: slippageError === SlippageError.RiskyLow : tooLow
? 'Your transaction may fail' ? 'Your transaction may fail'
: 'Your transaction may be frontrun'} : 'Your transaction may be frontrun'}
</RowBetween> </RowBetween>
)} ) : null}
</AutoColumn> </AutoColumn>
<AutoColumn gap="sm"> <AutoColumn gap="sm">
...@@ -231,15 +217,22 @@ export default function SlippageTabs({ rawSlippage, setRawSlippage, deadline, se ...@@ -231,15 +217,22 @@ export default function SlippageTabs({ rawSlippage, setRawSlippage, deadline, se
<QuestionHelper text="Your transaction will revert if it is pending for more than this period of time." /> <QuestionHelper text="Your transaction will revert if it is pending for more than this period of time." />
</RowFixed> </RowFixed>
<RowFixed> <RowFixed>
<OptionCustom style={{ width: '80px' }} tabIndex={-1}> <OptionCustom style={{ width: '80px' }} warning={!!deadlineError} tabIndex={-1}>
<Input <Input
color={!!deadlineError ? 'red' : undefined} placeholder={(DEFAULT_DEADLINE_FROM_NOW / 60).toString()}
value={
deadlineInput.length > 0
? deadlineInput
: deadline === DEFAULT_DEADLINE_FROM_NOW
? ''
: (deadline / 60).toString()
}
onChange={(e) => parseCustomDeadline(e.target.value)}
onBlur={() => { onBlur={() => {
parseCustomDeadline((deadline / 60).toString()) setDeadlineInput('')
setDeadlineError(false)
}} }}
placeholder={(deadline / 60).toString()} color={deadlineError ? 'red' : ''}
value={deadlineInput}
onChange={(e) => parseCustomDeadline(e.target.value)}
/> />
</OptionCustom> </OptionCustom>
<TYPE.body style={{ paddingLeft: '8px' }} fontSize={14}> <TYPE.body style={{ paddingLeft: '8px' }} fontSize={14}>
......
import { Percent } from '@uniswap/sdk-core'
import { Trade as V2Trade } from '@uniswap/v2-sdk' import { Trade as V2Trade } from '@uniswap/v2-sdk'
import { Trade as V3Trade } from '@uniswap/v3-sdk' import { Trade as V3Trade } from '@uniswap/v3-sdk'
import React, { useContext } from 'react' import React, { useContext } from 'react'
import { ThemeContext } from 'styled-components' import { ThemeContext } from 'styled-components'
import { useUserSlippageTolerance } from '../../state/user/hooks'
import { TYPE } from '../../theme' import { TYPE } from '../../theme'
import { computePriceImpactWithMaximumSlippage } from '../../utils/computePriceImpactWithMaximumSlippage' import { computePriceImpactWithMaximumSlippage } from '../../utils/computePriceImpactWithMaximumSlippage'
import { computeRealizedLPFeeAmount } from '../../utils/prices' import { computeRealizedLPFeeAmount } from '../../utils/prices'
...@@ -13,13 +13,13 @@ import SwapRoute from './SwapRoute' ...@@ -13,13 +13,13 @@ import SwapRoute from './SwapRoute'
export interface AdvancedSwapDetailsProps { export interface AdvancedSwapDetailsProps {
trade?: V2Trade | V3Trade trade?: V2Trade | V3Trade
allowedSlippage: Percent
} }
export function AdvancedSwapDetails({ trade }: AdvancedSwapDetailsProps) { export function AdvancedSwapDetails({ trade, allowedSlippage }: AdvancedSwapDetailsProps) {
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
const realizedLPFee = computeRealizedLPFeeAmount(trade) const realizedLPFee = computeRealizedLPFeeAmount(trade)
const [allowedSlippage] = useUserSlippageTolerance()
return !trade ? null : ( return !trade ? null : (
<AutoColumn gap="8px"> <AutoColumn gap="8px">
...@@ -55,6 +55,17 @@ export function AdvancedSwapDetails({ trade }: AdvancedSwapDetailsProps) { ...@@ -55,6 +55,17 @@ export function AdvancedSwapDetails({ trade }: AdvancedSwapDetailsProps) {
<FormattedPriceImpact priceImpact={computePriceImpactWithMaximumSlippage(trade, allowedSlippage)} /> <FormattedPriceImpact priceImpact={computePriceImpactWithMaximumSlippage(trade, allowedSlippage)} />
</TYPE.black> </TYPE.black>
</RowBetween> </RowBetween>
<RowBetween>
<RowFixed>
<TYPE.black fontSize={12} fontWeight={400} color={theme.text2}>
Slippage tolerance
</TYPE.black>
</RowFixed>
<TYPE.black textAlign="right" fontSize={12} color={theme.text1}>
{allowedSlippage.toFixed(2)}%
</TYPE.black>
</RowBetween>
</AutoColumn> </AutoColumn>
) )
} }
import React from 'react' import React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import SettingsTab from '../Settings' import SettingsTab from '../Settings'
import { Percent } from '@uniswap/sdk-core'
import { RowBetween, RowFixed } from '../Row' import { RowBetween, RowFixed } from '../Row'
import { TYPE } from '../../theme' import { TYPE } from '../../theme'
...@@ -11,7 +12,7 @@ const StyledSwapHeader = styled.div` ...@@ -11,7 +12,7 @@ const StyledSwapHeader = styled.div`
color: ${({ theme }) => theme.text2}; color: ${({ theme }) => theme.text2};
` `
export default function SwapHeader() { export default function SwapHeader({ allowedSlippage }: { allowedSlippage: Percent }) {
return ( return (
<StyledSwapHeader> <StyledSwapHeader>
<RowBetween> <RowBetween>
...@@ -21,9 +22,7 @@ export default function SwapHeader() { ...@@ -21,9 +22,7 @@ export default function SwapHeader() {
</TYPE.black> </TYPE.black>
</RowFixed> </RowFixed>
<RowFixed> <RowFixed>
{/* <TradeInfo disabled={!trade} trade={trade} /> */} <SettingsTab placeholderSlippage={allowedSlippage} />
{/* <div style={{ width: '8px' }}></div> */}
<SettingsTab />
</RowFixed> </RowFixed>
</RowBetween> </RowBetween>
</StyledSwapHeader> </StyledSwapHeader>
......
...@@ -135,7 +135,7 @@ export default function SwapModalHeader({ ...@@ -135,7 +135,7 @@ export default function SwapModalHeader({
</RowBetween> </RowBetween>
<LightCard style={{ padding: '.75rem', marginTop: '0.5rem' }}> <LightCard style={{ padding: '.75rem', marginTop: '0.5rem' }}>
<AdvancedSwapDetails trade={trade} /> <AdvancedSwapDetails trade={trade} allowedSlippage={allowedSlippage} />
</LightCard> </LightCard>
{showAcceptChanges ? ( {showAcceptChanges ? (
......
...@@ -25,7 +25,7 @@ export default memo(function SwapRoute({ trade }: { trade: V2Trade | V3Trade }) ...@@ -25,7 +25,7 @@ export default memo(function SwapRoute({ trade }: { trade: V2Trade | V3Trade })
return ( return (
<Fragment key={i}> <Fragment key={i}>
<Flex alignItems="end"> <Flex alignItems="end">
<TYPE.black fontSize={14} color={theme.text1} ml="0.145rem" mr="0.145rem"> <TYPE.black color={theme.text1} ml="0.145rem" mr="0.145rem">
{currency.symbol} {currency.symbol}
</TYPE.black> </TYPE.black>
</Flex> </Flex>
......
...@@ -205,10 +205,8 @@ export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = { ...@@ -205,10 +205,8 @@ export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = {
export const NetworkContextName = 'NETWORK' export const NetworkContextName = 'NETWORK'
// default allowed slippage, in bips // 30 minutes, denominated in seconds
export const INITIAL_ALLOWED_SLIPPAGE = new Percent(10, 10_000) export const DEFAULT_DEADLINE_FROM_NOW = 60 * 30
// 20 minutes, denominated in seconds
export const DEFAULT_DEADLINE_FROM_NOW = 60 * 20
// used for rewards deadlines // used for rewards deadlines
export const BIG_INT_SECONDS_IN_WEEK = JSBI.BigInt(60 * 60 * 24 * 7) export const BIG_INT_SECONDS_IN_WEEK = JSBI.BigInt(60 * 60 * 24 * 7)
......
...@@ -3,7 +3,6 @@ import { Router, Trade as V2Trade } from '@uniswap/v2-sdk' ...@@ -3,7 +3,6 @@ import { Router, Trade as V2Trade } from '@uniswap/v2-sdk'
import { SwapRouter, Trade as V3Trade } from '@uniswap/v3-sdk' import { SwapRouter, Trade as V3Trade } from '@uniswap/v3-sdk'
import { ChainId, Percent, TradeType } from '@uniswap/sdk-core' import { ChainId, Percent, TradeType } from '@uniswap/sdk-core'
import { useMemo } from 'react' import { useMemo } from 'react'
import { INITIAL_ALLOWED_SLIPPAGE } from '../constants'
import { SWAP_ROUTER_ADDRESSES } from '../constants/v3' import { SWAP_ROUTER_ADDRESSES } from '../constants/v3'
import { getTradeVersion } from '../utils/getTradeVersion' import { getTradeVersion } from '../utils/getTradeVersion'
import { useTransactionAdder } from '../state/transactions/hooks' import { useTransactionAdder } from '../state/transactions/hooks'
...@@ -51,7 +50,7 @@ interface FailedCall extends SwapCallEstimate { ...@@ -51,7 +50,7 @@ interface FailedCall extends SwapCallEstimate {
*/ */
function useSwapCallArguments( function useSwapCallArguments(
trade: V2Trade | V3Trade | undefined, // trade to execute, required trade: V2Trade | V3Trade | undefined, // trade to execute, required
allowedSlippage: Percent = INITIAL_ALLOWED_SLIPPAGE, // in bips allowedSlippage: Percent, // in bips
recipientAddressOrName: string | null, // the ENS name or address of the recipient of the trade, or null if swap should be returned to sender recipientAddressOrName: string | null, // the ENS name or address of the recipient of the trade, or null if swap should be returned to sender
signatureData: SignatureData | null | undefined signatureData: SignatureData | null | undefined
): SwapCall[] { ): SwapCall[] {
...@@ -138,7 +137,7 @@ function useSwapCallArguments( ...@@ -138,7 +137,7 @@ function useSwapCallArguments(
// and the user has approved the slippage adjusted input amount for the trade // and the user has approved the slippage adjusted input amount for the trade
export function useSwapCallback( export function useSwapCallback(
trade: V2Trade | V3Trade | undefined, // trade to execute, required trade: V2Trade | V3Trade | undefined, // trade to execute, required
allowedSlippage: Percent = INITIAL_ALLOWED_SLIPPAGE, // in bips allowedSlippage: Percent, // in bips
recipientAddressOrName: string | null, // the ENS name or address of the recipient of the trade, or null if swap should be returned to sender recipientAddressOrName: string | null, // the ENS name or address of the recipient of the trade, or null if swap should be returned to sender
signatureData: SignatureData | undefined | null signatureData: SignatureData | undefined | null
): { state: SwapCallbackState; callback: null | (() => Promise<string>); error: string | null } { ): { state: SwapCallbackState; callback: null | (() => Promise<string>); error: string | null } {
......
import { Percent } from '@uniswap/sdk-core'
import { Trade as V3Trade } from '@uniswap/v3-sdk'
import { Trade as V2Trade } from '@uniswap/v2-sdk'
import { useMemo } from 'react'
import { useUserSlippageToleranceWithDefault } from '../state/user/hooks'
const V2_SWAP_DEFAULT_SLIPPAGE = new Percent(45, 10_000) // .45%
const V3_SWAP_DEFAULT_SLIPPAGE = new Percent(25, 10_000) // .25%
const ONE_TENTHS_PERCENT = new Percent(10, 10_000) // .10%
export default function useSwapSlippageTolerance(trade: V2Trade | V3Trade | undefined): Percent {
const defaultSlippageTolerance = useMemo(() => {
if (!trade) return ONE_TENTHS_PERCENT
if (trade instanceof V2Trade) return V2_SWAP_DEFAULT_SLIPPAGE
return V3_SWAP_DEFAULT_SLIPPAGE
}, [trade])
return useUserSlippageToleranceWithDefault(defaultSlippageTolerance)
}
import React, { useCallback, useContext, useMemo, useState, useEffect } from 'react' import React, { useCallback, useContext, useMemo, useState, useEffect } from 'react'
import { TransactionResponse } from '@ethersproject/providers' import { TransactionResponse } from '@ethersproject/providers'
import { Currency, TokenAmount, ETHER, currencyEquals } from '@uniswap/sdk-core' import { Currency, TokenAmount, ETHER, currencyEquals, Percent } from '@uniswap/sdk-core'
import { WETH9 } from '@uniswap/sdk-core' import { WETH9 } from '@uniswap/sdk-core'
import { AlertTriangle, AlertCircle } from 'react-feather' import { AlertTriangle, AlertCircle } from 'react-feather'
import ReactGA from 'react-ga' import ReactGA from 'react-ga'
import { ZERO_PERCENT } from '../../constants'
import { useV3NFTPositionManagerContract } from '../../hooks/useContract' import { useV3NFTPositionManagerContract } from '../../hooks/useContract'
import { RouteComponentProps } from 'react-router-dom' import { RouteComponentProps } from 'react-router-dom'
import { Text } from 'rebass' import { Text } from 'rebass'
...@@ -25,7 +26,7 @@ import { useWalletModalToggle } from '../../state/application/hooks' ...@@ -25,7 +26,7 @@ import { useWalletModalToggle } from '../../state/application/hooks'
import { Field, Bound } from '../../state/mint/v3/actions' import { Field, Bound } from '../../state/mint/v3/actions'
import { useTransactionAdder } from '../../state/transactions/hooks' import { useTransactionAdder } from '../../state/transactions/hooks'
import { useIsExpertMode, useUserSlippageTolerance } from '../../state/user/hooks' import { useIsExpertMode, useUserSlippageToleranceWithDefault } from '../../state/user/hooks'
import { TYPE, ExternalLink } from '../../theme' import { TYPE, ExternalLink } from '../../theme'
import { maxAmountSpend } from '../../utils/maxAmountSpend' import { maxAmountSpend } from '../../utils/maxAmountSpend'
import AppBody from '../AppBody' import AppBody from '../AppBody'
...@@ -52,6 +53,8 @@ import { BigNumber } from '@ethersproject/bignumber' ...@@ -52,6 +53,8 @@ import { BigNumber } from '@ethersproject/bignumber'
import { calculateGasMargin } from 'utils' import { calculateGasMargin } from 'utils'
import { AddRemoveTabs } from 'components/NavigationTabs' import { AddRemoveTabs } from 'components/NavigationTabs'
const DEFAULT_ADD_IN_RANGE_SLIPPAGE_TOLERANCE = new Percent(50, 10_000)
export default function AddLiquidity({ export default function AddLiquidity({
match: { match: {
params: { currencyIdA, currencyIdB, feeAmount: feeAmountFromUrl, tokenId }, params: { currencyIdA, currencyIdB, feeAmount: feeAmountFromUrl, tokenId },
...@@ -148,7 +151,7 @@ export default function AddLiquidity({ ...@@ -148,7 +151,7 @@ export default function AddLiquidity({
// txn values // txn values
const deadline = useTransactionDeadline() // custom from users settings const deadline = useTransactionDeadline() // custom from users settings
const [allowedSlippage] = useUserSlippageTolerance() // custom from users
const [txHash, setTxHash] = useState<string>('') const [txHash, setTxHash] = useState<string>('')
// get formatted amounts // get formatted amounts
...@@ -193,6 +196,10 @@ export default function AddLiquidity({ ...@@ -193,6 +196,10 @@ export default function AddLiquidity({
chainId ? NONFUNGIBLE_POSITION_MANAGER_ADDRESSES[chainId] : undefined chainId ? NONFUNGIBLE_POSITION_MANAGER_ADDRESSES[chainId] : undefined
) )
const allowedSlippage = useUserSlippageToleranceWithDefault(
outOfRange ? ZERO_PERCENT : DEFAULT_ADD_IN_RANGE_SLIPPAGE_TOLERANCE
)
async function onAdd() { async function onAdd() {
if (!chainId || !library || !account) return if (!chainId || !library || !account) return
...@@ -396,7 +403,12 @@ export default function AddLiquidity({ ...@@ -396,7 +403,12 @@ export default function AddLiquidity({
pendingText={pendingText} pendingText={pendingText}
/> />
<AppBody> <AppBody>
<AddRemoveTabs creating={false} adding={true} positionID={tokenId} /> <AddRemoveTabs
creating={false}
adding={true}
positionID={tokenId}
defaultSlippage={DEFAULT_ADD_IN_RANGE_SLIPPAGE_TOLERANCE}
/>
<Wrapper> <Wrapper>
<AutoColumn gap="32px"> <AutoColumn gap="32px">
{!hasExistingPosition && ( {!hasExistingPosition && (
......
import { BigNumber } from '@ethersproject/bignumber' import { BigNumber } from '@ethersproject/bignumber'
import { TransactionResponse } from '@ethersproject/providers' import { TransactionResponse } from '@ethersproject/providers'
import { Currency, currencyEquals, ETHER, TokenAmount, WETH9 } from '@uniswap/sdk-core' import { Currency, currencyEquals, ETHER, Percent, TokenAmount, WETH9 } from '@uniswap/sdk-core'
import React, { useCallback, useContext, useState } from 'react' import React, { useCallback, useContext, useState } from 'react'
import { Plus } from 'react-feather' import { Plus } from 'react-feather'
import ReactGA from 'react-ga' import ReactGA from 'react-ga'
...@@ -30,7 +30,7 @@ import { Field } from '../../state/mint/actions' ...@@ -30,7 +30,7 @@ import { Field } from '../../state/mint/actions'
import { useDerivedMintInfo, useMintActionHandlers, useMintState } from '../../state/mint/hooks' import { useDerivedMintInfo, useMintActionHandlers, useMintState } from '../../state/mint/hooks'
import { useTransactionAdder } from '../../state/transactions/hooks' import { useTransactionAdder } from '../../state/transactions/hooks'
import { useIsExpertMode, useUserSlippageTolerance } from '../../state/user/hooks' import { useIsExpertMode, useUserSlippageToleranceWithDefault } from '../../state/user/hooks'
import { TYPE } from '../../theme' import { TYPE } from '../../theme'
import { calculateGasMargin, calculateSlippageAmount } from '../../utils' import { calculateGasMargin, calculateSlippageAmount } from '../../utils'
import { maxAmountSpend } from '../../utils/maxAmountSpend' import { maxAmountSpend } from '../../utils/maxAmountSpend'
...@@ -42,6 +42,8 @@ import { currencyId } from '../../utils/currencyId' ...@@ -42,6 +42,8 @@ import { currencyId } from '../../utils/currencyId'
import { PoolPriceBar } from './PoolPriceBar' import { PoolPriceBar } from './PoolPriceBar'
import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter' import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter'
const DEFAULT_ADD_V2_SLIPPAGE_TOLERANCE = new Percent(50, 10_000)
export default function AddLiquidity({ export default function AddLiquidity({
match: { match: {
params: { currencyIdA, currencyIdB }, params: { currencyIdA, currencyIdB },
...@@ -90,7 +92,7 @@ export default function AddLiquidity({ ...@@ -90,7 +92,7 @@ export default function AddLiquidity({
// txn values // txn values
const deadline = useTransactionDeadline() // custom from users settings const deadline = useTransactionDeadline() // custom from users settings
const [allowedSlippage] = useUserSlippageTolerance() // custom from users const allowedSlippage = useUserSlippageToleranceWithDefault(DEFAULT_ADD_V2_SLIPPAGE_TOLERANCE) // custom from users
const [txHash, setTxHash] = useState<string>('') const [txHash, setTxHash] = useState<string>('')
// get formatted amounts // get formatted amounts
...@@ -315,7 +317,7 @@ export default function AddLiquidity({ ...@@ -315,7 +317,7 @@ export default function AddLiquidity({
return ( return (
<> <>
<AppBody> <AppBody>
<AddRemoveTabs creating={isCreate} adding={true} /> <AddRemoveTabs creating={isCreate} adding={true} defaultSlippage={DEFAULT_ADD_V2_SLIPPAGE_TOLERANCE} />
<Wrapper> <Wrapper>
<TransactionConfirmationModal <TransactionConfirmationModal
isOpen={showConfirm} isOpen={showConfirm}
......
import React, { useCallback, useMemo, useState, useEffect } from 'react' import React, { useCallback, useMemo, useState, useEffect } from 'react'
import { Fraction, Price, Token, TokenAmount, WETH9 } from '@uniswap/sdk-core' import { Fraction, Percent, Price, Token, TokenAmount, WETH9 } from '@uniswap/sdk-core'
import { FACTORY_ADDRESS, JSBI } from '@uniswap/v2-sdk' import { FACTORY_ADDRESS, JSBI } from '@uniswap/v2-sdk'
import { Redirect, RouteComponentProps } from 'react-router' import { Redirect, RouteComponentProps } from 'react-router'
import { Text } from 'rebass' import { Text } from 'rebass'
...@@ -25,7 +25,7 @@ import { ApprovalState, useApproveCallback } from 'hooks/useApproveCallback' ...@@ -25,7 +25,7 @@ import { ApprovalState, useApproveCallback } from 'hooks/useApproveCallback'
import { Dots } from 'components/swap/styleds' import { Dots } from 'components/swap/styleds'
import { ButtonConfirmed } from 'components/Button' import { ButtonConfirmed } from 'components/Button'
import useTransactionDeadline from 'hooks/useTransactionDeadline' import useTransactionDeadline from 'hooks/useTransactionDeadline'
import { useUserSlippageTolerance } from 'state/user/hooks' import { useUserSlippageToleranceWithDefault } from 'state/user/hooks'
import ReactGA from 'react-ga' import ReactGA from 'react-ga'
import { TransactionResponse } from '@ethersproject/providers' import { TransactionResponse } from '@ethersproject/providers'
import { useIsTransactionPending, useTransactionAdder } from 'state/transactions/hooks' import { useIsTransactionPending, useTransactionAdder } from 'state/transactions/hooks'
...@@ -49,6 +49,8 @@ import SettingsTab from 'components/Settings' ...@@ -49,6 +49,8 @@ import SettingsTab from 'components/Settings'
const ZERO = JSBI.BigInt(0) const ZERO = JSBI.BigInt(0)
const DEFAULT_MIGRATE_SLIPPAGE_TOLERANCE = new Percent(75, 10_000)
function EmptyState({ message }: { message: string }) { function EmptyState({ message }: { message: string }) {
return ( return (
<AutoColumn style={{ minHeight: 200, justifyContent: 'center', alignItems: 'center' }}> <AutoColumn style={{ minHeight: 200, justifyContent: 'center', alignItems: 'center' }}>
...@@ -119,7 +121,7 @@ function V2PairMigration({ ...@@ -119,7 +121,7 @@ function V2PairMigration({
const deadline = useTransactionDeadline() // custom from users settings const deadline = useTransactionDeadline() // custom from users settings
const blockTimestamp = useCurrentBlockTimestamp() const blockTimestamp = useCurrentBlockTimestamp()
const [allowedSlippage] = useUserSlippageTolerance() // custom from users const allowedSlippage = useUserSlippageToleranceWithDefault(DEFAULT_MIGRATE_SLIPPAGE_TOLERANCE) // custom from users
const currency0 = unwrappedToken(token0) const currency0 = unwrappedToken(token0)
const currency1 = unwrappedToken(token1) const currency1 = unwrappedToken(token1)
...@@ -673,7 +675,7 @@ export default function MigrateV2Pair({ ...@@ -673,7 +675,7 @@ export default function MigrateV2Pair({
<AutoRow style={{ alignItems: 'center', justifyContent: 'space-between' }} gap="8px"> <AutoRow style={{ alignItems: 'center', justifyContent: 'space-between' }} gap="8px">
<BackArrow to="/migrate/v2" /> <BackArrow to="/migrate/v2" />
<TYPE.mediumHeader>Migrate V2 Liquidity</TYPE.mediumHeader> <TYPE.mediumHeader>Migrate V2 Liquidity</TYPE.mediumHeader>
<SettingsTab /> <SettingsTab placeholderSlippage={DEFAULT_MIGRATE_SLIPPAGE_TOLERANCE} />
</AutoRow> </AutoRow>
{!account ? ( {!account ? (
......
...@@ -15,13 +15,13 @@ import { Text } from 'rebass' ...@@ -15,13 +15,13 @@ import { Text } from 'rebass'
import CurrencyLogo from 'components/CurrencyLogo' import CurrencyLogo from 'components/CurrencyLogo'
import FormattedCurrencyAmount from 'components/FormattedCurrencyAmount' import FormattedCurrencyAmount from 'components/FormattedCurrencyAmount'
import { useV3NFTPositionManagerContract } from 'hooks/useContract' import { useV3NFTPositionManagerContract } from 'hooks/useContract'
import { useUserSlippageTolerance } from 'state/user/hooks' import { useUserSlippageToleranceWithDefault } from 'state/user/hooks'
import useTransactionDeadline from 'hooks/useTransactionDeadline' import useTransactionDeadline from 'hooks/useTransactionDeadline'
import ReactGA from 'react-ga' import ReactGA from 'react-ga'
import { useActiveWeb3React } from 'hooks' import { useActiveWeb3React } from 'hooks'
import { TransactionResponse } from '@ethersproject/providers' import { TransactionResponse } from '@ethersproject/providers'
import { useTransactionAdder } from 'state/transactions/hooks' import { useTransactionAdder } from 'state/transactions/hooks'
import { WETH9, CurrencyAmount } from '@uniswap/sdk-core' import { WETH9, CurrencyAmount, Percent } from '@uniswap/sdk-core'
import { TYPE } from 'theme' import { TYPE } from 'theme'
import { Wrapper, SmallMaxButton, ResponsiveHeaderText } from './styled' import { Wrapper, SmallMaxButton, ResponsiveHeaderText } from './styled'
import Loader from 'components/Loader' import Loader from 'components/Loader'
...@@ -37,6 +37,8 @@ import RangeBadge from 'components/Badge/RangeBadge' ...@@ -37,6 +37,8 @@ import RangeBadge from 'components/Badge/RangeBadge'
export const UINT128MAX = BigNumber.from(2).pow(128).sub(1) export const UINT128MAX = BigNumber.from(2).pow(128).sub(1)
const DEFAULT_REMOVE_V3_LIQUIDITY_SLIPPAGE_TOLERANCE = new Percent(5, 100)
// redirect invalid tokenIds // redirect invalid tokenIds
export default function RemoveLiquidityV3({ export default function RemoveLiquidityV3({
location, location,
...@@ -89,7 +91,7 @@ function Remove({ tokenId }: { tokenId: BigNumber }) { ...@@ -89,7 +91,7 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
const [percentForSlider, onPercentSelectForSlider] = useDebouncedChangeHandler(percent, onPercentSelect) const [percentForSlider, onPercentSelectForSlider] = useDebouncedChangeHandler(percent, onPercentSelect)
const deadline = useTransactionDeadline() // custom from users settings const deadline = useTransactionDeadline() // custom from users settings
const [allowedSlippage] = useUserSlippageTolerance() // custom from users const allowedSlippage = useUserSlippageToleranceWithDefault(DEFAULT_REMOVE_V3_LIQUIDITY_SLIPPAGE_TOLERANCE) // custom from users
const [showConfirm, setShowConfirm] = useState(false) const [showConfirm, setShowConfirm] = useState(false)
const [attemptingTxn, setAttemptingTxn] = useState(false) const [attemptingTxn, setAttemptingTxn] = useState(false)
...@@ -274,7 +276,12 @@ function Remove({ tokenId }: { tokenId: BigNumber }) { ...@@ -274,7 +276,12 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
pendingText={pendingText} pendingText={pendingText}
/> />
<AppBody> <AppBody>
<AddRemoveTabs creating={false} adding={false} positionID={tokenId.toString()} /> <AddRemoveTabs
creating={false}
adding={false}
positionID={tokenId.toString()}
defaultSlippage={DEFAULT_REMOVE_V3_LIQUIDITY_SLIPPAGE_TOLERANCE}
/>
<Wrapper> <Wrapper>
{position ? ( {position ? (
<AutoColumn gap="lg"> <AutoColumn gap="lg">
......
...@@ -40,9 +40,11 @@ import { useBurnActionHandlers } from '../../state/burn/hooks' ...@@ -40,9 +40,11 @@ import { useBurnActionHandlers } from '../../state/burn/hooks'
import { useDerivedBurnInfo, useBurnState } from '../../state/burn/hooks' import { useDerivedBurnInfo, useBurnState } from '../../state/burn/hooks'
import { Field } from '../../state/burn/actions' import { Field } from '../../state/burn/actions'
import { useWalletModalToggle } from '../../state/application/hooks' import { useWalletModalToggle } from '../../state/application/hooks'
import { useUserSlippageTolerance } from '../../state/user/hooks' import { useUserSlippageToleranceWithDefault } from '../../state/user/hooks'
import { BigNumber } from '@ethersproject/bignumber' import { BigNumber } from '@ethersproject/bignumber'
const DEFAULT_REMOVE_LIQUIDITY_SLIPPAGE_TOLERANCE = new Percent(5, 100)
export default function RemoveLiquidity({ export default function RemoveLiquidity({
history, history,
match: { match: {
...@@ -76,7 +78,7 @@ export default function RemoveLiquidity({ ...@@ -76,7 +78,7 @@ export default function RemoveLiquidity({
// txn values // txn values
const [txHash, setTxHash] = useState<string>('') const [txHash, setTxHash] = useState<string>('')
const deadline = useTransactionDeadline() const deadline = useTransactionDeadline()
const [allowedSlippage] = useUserSlippageTolerance() const allowedSlippage = useUserSlippageToleranceWithDefault(DEFAULT_REMOVE_LIQUIDITY_SLIPPAGE_TOLERANCE)
const formattedAmounts = { const formattedAmounts = {
[Field.LIQUIDITY_PERCENT]: parsedAmounts[Field.LIQUIDITY_PERCENT].equalTo('0') [Field.LIQUIDITY_PERCENT]: parsedAmounts[Field.LIQUIDITY_PERCENT].equalTo('0')
...@@ -426,7 +428,7 @@ export default function RemoveLiquidity({ ...@@ -426,7 +428,7 @@ export default function RemoveLiquidity({
return ( return (
<> <>
<AppBody> <AppBody>
<AddRemoveTabs creating={false} adding={false} /> <AddRemoveTabs creating={false} adding={false} defaultSlippage={DEFAULT_REMOVE_LIQUIDITY_SLIPPAGE_TOLERANCE} />
<Wrapper> <Wrapper>
<TransactionConfirmationModal <TransactionConfirmationModal
isOpen={showConfirm} isOpen={showConfirm}
......
...@@ -46,7 +46,7 @@ import { ...@@ -46,7 +46,7 @@ import {
useSwapActionHandlers, useSwapActionHandlers,
useSwapState, useSwapState,
} from '../../state/swap/hooks' } from '../../state/swap/hooks'
import { useExpertModeManager, useUserSingleHopOnly, useUserSlippageTolerance } from '../../state/user/hooks' import { useExpertModeManager, useUserSingleHopOnly } from '../../state/user/hooks'
import { HideSmall, LinkStyledButton, TYPE } from '../../theme' import { HideSmall, LinkStyledButton, TYPE } from '../../theme'
import { computeFiatValuePriceImpact } from '../../utils/computeFiatValuePriceImpact' import { computeFiatValuePriceImpact } from '../../utils/computeFiatValuePriceImpact'
import { computePriceImpactWithMaximumSlippage } from '../../utils/computePriceImpactWithMaximumSlippage' import { computePriceImpactWithMaximumSlippage } from '../../utils/computePriceImpactWithMaximumSlippage'
...@@ -100,19 +100,21 @@ export default function Swap({ history }: RouteComponentProps) { ...@@ -100,19 +100,21 @@ export default function Swap({ history }: RouteComponentProps) {
// for expert mode // for expert mode
const [isExpertMode] = useExpertModeManager() const [isExpertMode] = useExpertModeManager()
// get custom setting values for user // get version from the url
const [allowedSlippage] = useUserSlippageTolerance() const toggledVersion = useToggledVersion()
// swap state // swap state
const { independentField, typedValue, recipient } = useSwapState() const { independentField, typedValue, recipient } = useSwapState()
const { const {
v2Trade, v2Trade,
v3TradeState: { trade: v3Trade, state: v3TradeState }, v3TradeState: { trade: v3Trade, state: v3TradeState },
toggledTrade: trade,
allowedSlippage,
currencyBalances, currencyBalances,
parsedAmount, parsedAmount,
currencies, currencies,
inputError: swapInputError, inputError: swapInputError,
} = useDerivedSwapInfo() } = useDerivedSwapInfo(toggledVersion)
const { wrapType, execute: onWrap, inputError: wrapInputError } = useWrapCallback( const { wrapType, execute: onWrap, inputError: wrapInputError } = useWrapCallback(
currencies[Field.INPUT], currencies[Field.INPUT],
...@@ -121,13 +123,6 @@ export default function Swap({ history }: RouteComponentProps) { ...@@ -121,13 +123,6 @@ export default function Swap({ history }: RouteComponentProps) {
) )
const showWrap: boolean = wrapType !== WrapType.NOT_APPLICABLE const showWrap: boolean = wrapType !== WrapType.NOT_APPLICABLE
const { address: recipientAddress } = useENSAddress(recipient) const { address: recipientAddress } = useENSAddress(recipient)
const toggledVersion = useToggledVersion()
const trade = showWrap
? undefined
: {
[Version.v2]: v2Trade,
[Version.v3]: v3Trade ?? undefined,
}[toggledVersion]
const parsedAmounts = useMemo( const parsedAmounts = useMemo(
() => () =>
...@@ -357,7 +352,7 @@ export default function Swap({ history }: RouteComponentProps) { ...@@ -357,7 +352,7 @@ export default function Swap({ history }: RouteComponentProps) {
onDismiss={handleDismissTokenWarning} onDismiss={handleDismissTokenWarning}
/> />
<AppBody> <AppBody>
<SwapHeader /> <SwapHeader allowedSlippage={allowedSlippage} />
<Wrapper id="swap-page"> <Wrapper id="swap-page">
<ConfirmSwapModal <ConfirmSwapModal
isOpen={showConfirm} isOpen={showConfirm}
...@@ -484,7 +479,9 @@ export default function Swap({ history }: RouteComponentProps) { ...@@ -484,7 +479,9 @@ export default function Swap({ history }: RouteComponentProps) {
showInverted={showInverted} showInverted={showInverted}
setShowInverted={setShowInverted} setShowInverted={setShowInverted}
/> />
<MouseoverTooltipContent content={<AdvancedSwapDetails trade={trade} />}> <MouseoverTooltipContent
content={<AdvancedSwapDetails trade={trade} allowedSlippage={allowedSlippage} />}
>
<StyledInfo /> <StyledInfo />
</MouseoverTooltipContent> </MouseoverTooltipContent>
</RowFixed> </RowFixed>
......
...@@ -2,13 +2,15 @@ import { Trade as V3Trade } from '@uniswap/v3-sdk' ...@@ -2,13 +2,15 @@ import { Trade as V3Trade } from '@uniswap/v3-sdk'
import { useBestV3TradeExactIn, useBestV3TradeExactOut, V3TradeState } from '../../hooks/useBestV3Trade' import { useBestV3TradeExactIn, useBestV3TradeExactOut, V3TradeState } from '../../hooks/useBestV3Trade'
import useENS from '../../hooks/useENS' import useENS from '../../hooks/useENS'
import { parseUnits } from '@ethersproject/units' import { parseUnits } from '@ethersproject/units'
import { Currency, CurrencyAmount, ETHER, Token, TokenAmount } from '@uniswap/sdk-core' import { Currency, CurrencyAmount, ETHER, Token, TokenAmount, Percent } from '@uniswap/sdk-core'
import { JSBI, Trade as V2Trade } from '@uniswap/v2-sdk' import { JSBI, Trade as V2Trade } from '@uniswap/v2-sdk'
import { ParsedQs } from 'qs' import { ParsedQs } from 'qs'
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { useCurrency } from '../../hooks/Tokens' import { useCurrency } from '../../hooks/Tokens'
import useSwapSlippageTolerance from '../../hooks/useSwapSlippageTolerance'
import { Version } from '../../hooks/useToggledVersion'
import { useV2TradeExactIn, useV2TradeExactOut } from '../../hooks/useV2Trade' import { useV2TradeExactIn, useV2TradeExactOut } from '../../hooks/useV2Trade'
import useParsedQueryString from '../../hooks/useParsedQueryString' import useParsedQueryString from '../../hooks/useParsedQueryString'
import { isAddress } from '../../utils' import { isAddress } from '../../utils'
...@@ -16,7 +18,6 @@ import { AppDispatch, AppState } from '../index' ...@@ -16,7 +18,6 @@ import { AppDispatch, AppState } from '../index'
import { useCurrencyBalances } from '../wallet/hooks' import { useCurrencyBalances } from '../wallet/hooks'
import { Field, replaceSwapState, selectCurrency, setRecipient, switchCurrencies, typeInput } from './actions' import { Field, replaceSwapState, selectCurrency, setRecipient, switchCurrencies, typeInput } from './actions'
import { SwapState } from './reducer' import { SwapState } from './reducer'
import { useUserSlippageTolerance } from '../user/hooks'
export function useSwapState(): AppState['swap'] { export function useSwapState(): AppState['swap'] {
return useSelector<AppState, AppState['swap']>((state) => state.swap) return useSelector<AppState, AppState['swap']>((state) => state.swap)
...@@ -109,13 +110,17 @@ function involvesAddress(trade: V2Trade | V3Trade, checksummedAddress: string): ...@@ -109,13 +110,17 @@ function involvesAddress(trade: V2Trade | V3Trade, checksummedAddress: string):
} }
// from the current swap inputs, compute the best trade and return it. // from the current swap inputs, compute the best trade and return it.
export function useDerivedSwapInfo(): { export function useDerivedSwapInfo(
toggledVersion: Version
): {
currencies: { [field in Field]?: Currency } currencies: { [field in Field]?: Currency }
currencyBalances: { [field in Field]?: CurrencyAmount } currencyBalances: { [field in Field]?: CurrencyAmount }
parsedAmount: CurrencyAmount | undefined parsedAmount: CurrencyAmount | undefined
v2Trade: V2Trade | undefined
inputError?: string inputError?: string
v2Trade: V2Trade | undefined
v3TradeState: { trade: V3Trade | null; state: V3TradeState } v3TradeState: { trade: V3Trade | null; state: V3TradeState }
toggledTrade: V2Trade | V3Trade | undefined
allowedSlippage: Percent
} { } {
const { account } = useActiveWeb3React() const { account } = useActiveWeb3React()
...@@ -185,7 +190,8 @@ export function useDerivedSwapInfo(): { ...@@ -185,7 +190,8 @@ export function useDerivedSwapInfo(): {
} }
} }
const [allowedSlippage] = useUserSlippageTolerance() const toggledTrade = (toggledVersion === Version.v2 ? v2Trade : v3Trade.trade) ?? undefined
const allowedSlippage = useSwapSlippageTolerance(toggledTrade)
// compare input balance to max input based on version // compare input balance to max input based on version
const [balanceIn, amountIn] = [currencyBalances[Field.INPUT], v2Trade?.maximumAmountIn(allowedSlippage)] const [balanceIn, amountIn] = [currencyBalances[Field.INPUT], v2Trade?.maximumAmountIn(allowedSlippage)]
...@@ -198,9 +204,11 @@ export function useDerivedSwapInfo(): { ...@@ -198,9 +204,11 @@ export function useDerivedSwapInfo(): {
currencies, currencies,
currencyBalances, currencyBalances,
parsedAmount, parsedAmount,
v2Trade: v2Trade ?? undefined,
inputError, inputError,
v2Trade: v2Trade ?? undefined,
v3TradeState: v3Trade, v3TradeState: v3Trade,
toggledTrade,
allowedSlippage,
} }
} }
......
...@@ -17,7 +17,7 @@ export const updateMatchesDarkMode = createAction<{ matchesDarkMode: boolean }>( ...@@ -17,7 +17,7 @@ export const updateMatchesDarkMode = createAction<{ matchesDarkMode: boolean }>(
export const updateUserDarkMode = createAction<{ userDarkMode: boolean }>('user/updateUserDarkMode') export const updateUserDarkMode = createAction<{ userDarkMode: boolean }>('user/updateUserDarkMode')
export const updateUserExpertMode = createAction<{ userExpertMode: boolean }>('user/updateUserExpertMode') export const updateUserExpertMode = createAction<{ userExpertMode: boolean }>('user/updateUserExpertMode')
export const updateUserSingleHopOnly = createAction<{ userSingleHopOnly: boolean }>('user/updateUserSingleHopOnly') export const updateUserSingleHopOnly = createAction<{ userSingleHopOnly: boolean }>('user/updateUserSingleHopOnly')
export const updateUserSlippageTolerance = createAction<{ userSlippageTolerance: number }>( export const updateUserSlippageTolerance = createAction<{ userSlippageTolerance: number | 'auto' }>(
'user/updateUserSlippageTolerance' 'user/updateUserSlippageTolerance'
) )
export const updateUserDeadline = createAction<{ userDeadline: number }>('user/updateUserDeadline') export const updateUserDeadline = createAction<{ userDeadline: number }>('user/updateUserDeadline')
......
import { ChainId, Percent, Token } from '@uniswap/sdk-core' import { ChainId, Percent, Token } from '@uniswap/sdk-core'
import { Pair } from '@uniswap/v2-sdk' import { Pair } from '@uniswap/v2-sdk'
import JSBI from 'jsbi'
import flatMap from 'lodash.flatmap' import flatMap from 'lodash.flatmap'
import { useCallback, useMemo } from 'react' import { useCallback, useMemo } from 'react'
import { shallowEqual, useDispatch, useSelector } from 'react-redux' import { shallowEqual, useDispatch, useSelector } from 'react-redux'
...@@ -14,12 +15,12 @@ import { ...@@ -14,12 +15,12 @@ import {
removeSerializedToken, removeSerializedToken,
SerializedPair, SerializedPair,
SerializedToken, SerializedToken,
toggleURLWarning,
updateUserDarkMode, updateUserDarkMode,
updateUserDeadline, updateUserDeadline,
updateUserExpertMode, updateUserExpertMode,
updateUserSlippageTolerance,
toggleURLWarning,
updateUserSingleHopOnly, updateUserSingleHopOnly,
updateUserSlippageTolerance,
} from './actions' } from './actions'
function serializeToken(token: Token): SerializedToken { function serializeToken(token: Token): SerializedToken {
...@@ -100,22 +101,51 @@ export function useUserSingleHopOnly(): [boolean, (newSingleHopOnly: boolean) => ...@@ -100,22 +101,51 @@ export function useUserSingleHopOnly(): [boolean, (newSingleHopOnly: boolean) =>
return [singleHopOnly, setSingleHopOnly] return [singleHopOnly, setSingleHopOnly]
} }
export function useUserSlippageTolerance(): [Percent, (slippageBips: number) => void] { export function useSetUserSlippageTolerance(): (slippageTolerance: Percent | 'auto') => void {
const dispatch = useDispatch<AppDispatch>() const dispatch = useDispatch<AppDispatch>()
const userSlippageTolerance = useSelector<AppState, AppState['user']['userSlippageTolerance']>((state) => {
return state.user.userSlippageTolerance
})
const percentage = useMemo(() => new Percent(userSlippageTolerance, 10_000), [userSlippageTolerance]) return useCallback(
(userSlippageTolerance: Percent | 'auto') => {
const setUserSlippageTolerance = useCallback( let value: 'auto' | number
(userSlippageTolerance: number) => { try {
dispatch(updateUserSlippageTolerance({ userSlippageTolerance })) value =
userSlippageTolerance === 'auto' ? 'auto' : JSBI.toNumber(userSlippageTolerance.multiply(10_000).quotient)
} catch (error) {
value = 'auto'
}
dispatch(
updateUserSlippageTolerance({
userSlippageTolerance: value,
})
)
}, },
[dispatch] [dispatch]
) )
}
return [percentage, setUserSlippageTolerance] /**
* Return the user's slippage tolerance, from the redux store, and a function to update the slippage tolerance
*/
export function useUserSlippageTolerance(): Percent | 'auto' {
const userSlippageTolerance = useSelector<AppState, AppState['user']['userSlippageTolerance']>((state) => {
return state.user.userSlippageTolerance
})
return useMemo(() => (userSlippageTolerance === 'auto' ? 'auto' : new Percent(userSlippageTolerance, 10_000)), [
userSlippageTolerance,
])
}
/**
* Same as above but replaces the auto with a default value
* @param defaultSlippageTolerance the default value to replace auto with
*/
export function useUserSlippageToleranceWithDefault(defaultSlippageTolerance: Percent): Percent {
const allowedSlippage = useUserSlippageTolerance()
return useMemo(() => (allowedSlippage === 'auto' ? defaultSlippageTolerance : allowedSlippage), [
allowedSlippage,
defaultSlippageTolerance,
])
} }
export function useUserTransactionTTL(): [number, (slippage: number) => void] { export function useUserTransactionTTL(): [number, (slippage: number) => void] {
......
...@@ -27,7 +27,7 @@ describe('swap reducer', () => { ...@@ -27,7 +27,7 @@ describe('swap reducer', () => {
} as any) } as any)
store.dispatch(updateVersion()) store.dispatch(updateVersion())
expect(store.getState().userDeadline).toEqual(DEFAULT_DEADLINE_FROM_NOW) expect(store.getState().userDeadline).toEqual(DEFAULT_DEADLINE_FROM_NOW)
expect(store.getState().userSlippageTolerance).toEqual(10) expect(store.getState().userSlippageTolerance).toEqual('auto')
}) })
}) })
}) })
...@@ -31,7 +31,8 @@ export interface UserState { ...@@ -31,7 +31,8 @@ export interface UserState {
userSingleHopOnly: boolean // only allow swaps on direct pairs userSingleHopOnly: boolean // only allow swaps on direct pairs
// user defined slippage tolerance in bips, used in all txns // user defined slippage tolerance in bips, used in all txns
userSlippageTolerance: number userSlippageTolerance: number | 'auto'
userSlippageToleranceHasBeenMigratedToAuto: boolean // temporary flag for migration status
// deadline set by user in minutes, used in all txns // deadline set by user in minutes, used in all txns
userDeadline: number userDeadline: number
...@@ -62,7 +63,8 @@ export const initialState: UserState = { ...@@ -62,7 +63,8 @@ export const initialState: UserState = {
matchesDarkMode: false, matchesDarkMode: false,
userExpertMode: false, userExpertMode: false,
userSingleHopOnly: false, userSingleHopOnly: false,
userSlippageTolerance: 10, userSlippageTolerance: 'auto',
userSlippageToleranceHasBeenMigratedToAuto: true,
userDeadline: DEFAULT_DEADLINE_FROM_NOW, userDeadline: DEFAULT_DEADLINE_FROM_NOW,
tokens: {}, tokens: {},
pairs: {}, pairs: {},
...@@ -75,13 +77,26 @@ export default createReducer(initialState, (builder) => ...@@ -75,13 +77,26 @@ export default createReducer(initialState, (builder) =>
.addCase(updateVersion, (state) => { .addCase(updateVersion, (state) => {
// slippage isnt being tracked in local storage, reset to default // slippage isnt being tracked in local storage, reset to default
// noinspection SuspiciousTypeOfGuard // noinspection SuspiciousTypeOfGuard
if (typeof state.userSlippageTolerance !== 'number') { if (
state.userSlippageTolerance = 10 typeof state.userSlippageTolerance !== 'number' ||
!Number.isInteger(state.userSlippageTolerance) ||
state.userSlippageTolerance < 0 ||
state.userSlippageTolerance > 5000
) {
state.userSlippageTolerance = 'auto'
} else {
if (
!state.userSlippageToleranceHasBeenMigratedToAuto &&
[10, 50, 100].indexOf(state.userSlippageTolerance) !== -1
) {
state.userSlippageTolerance = 'auto'
state.userSlippageToleranceHasBeenMigratedToAuto = true
}
} }
// deadline isnt being tracked in local storage, reset to default // deadline isnt being tracked in local storage, reset to default
// noinspection SuspiciousTypeOfGuard // noinspection SuspiciousTypeOfGuard
if (typeof state.userDeadline !== 'number') { if (typeof state.userDeadline !== 'number' || !Number.isInteger(state.userDeadline) || state.userDeadline < 60) {
state.userDeadline = DEFAULT_DEADLINE_FROM_NOW state.userDeadline = DEFAULT_DEADLINE_FROM_NOW
} }
......
...@@ -13,46 +13,48 @@ const THIRTY_BIPS_FEE = new Percent(JSBI.BigInt(30), JSBI.BigInt(10000)) ...@@ -13,46 +13,48 @@ const THIRTY_BIPS_FEE = new Percent(JSBI.BigInt(30), JSBI.BigInt(10000))
const ONE_HUNDRED_PERCENT = new Percent(JSBI.BigInt(10000), JSBI.BigInt(10000)) const ONE_HUNDRED_PERCENT = new Percent(JSBI.BigInt(10000), JSBI.BigInt(10000))
const INPUT_FRACTION_AFTER_FEE = ONE_HUNDRED_PERCENT.subtract(THIRTY_BIPS_FEE) const INPUT_FRACTION_AFTER_FEE = ONE_HUNDRED_PERCENT.subtract(THIRTY_BIPS_FEE)
// computes price breakdown for the trade // computes realized lp fee as a percent
export function computeRealizedLPFeeAmount(trade?: V2Trade | V3Trade | null): CurrencyAmount | undefined { export function computeRealizedLPFeePercent(trade: V2Trade | V3Trade): Percent {
let percent: Percent
if (trade instanceof V2Trade) { if (trade instanceof V2Trade) {
// for each hop in our trade, take away the x*y=k price impact from 0.3% fees // for each hop in our trade, take away the x*y=k price impact from 0.3% fees
// e.g. for 3 tokens/2 hops: 1 - ((1 - .03) * (1-.03)) // e.g. for 3 tokens/2 hops: 1 - ((1 - .03) * (1-.03))
const realizedLPFee = !trade percent = ONE_HUNDRED_PERCENT.subtract(
? undefined
: ONE_HUNDRED_PERCENT.subtract(
trade.route.pairs.reduce<Fraction>( trade.route.pairs.reduce<Fraction>(
(currentFee: Fraction): Fraction => currentFee.multiply(INPUT_FRACTION_AFTER_FEE), (currentFee: Fraction): Fraction => currentFee.multiply(INPUT_FRACTION_AFTER_FEE),
ONE_HUNDRED_PERCENT ONE_HUNDRED_PERCENT
) )
) )
} else {
// the amount of the input that accrues to LPs percent = ONE_HUNDRED_PERCENT.subtract(
return (
realizedLPFee &&
trade &&
(trade.inputAmount instanceof TokenAmount
? new TokenAmount(trade.inputAmount.token, realizedLPFee.multiply(trade.inputAmount.raw).quotient)
: CurrencyAmount.ether(realizedLPFee.multiply(trade.inputAmount.raw).quotient))
)
} else if (trade instanceof V3Trade) {
const realizedLPFee = !trade
? undefined
: ONE_HUNDRED_PERCENT.subtract(
trade.route.pools.reduce<Fraction>( trade.route.pools.reduce<Fraction>(
(currentFee: Fraction, pool): Fraction => (currentFee: Fraction, pool): Fraction =>
currentFee.multiply(ONE_HUNDRED_PERCENT.subtract(new Fraction(pool.fee, 1_000_000))), currentFee.multiply(ONE_HUNDRED_PERCENT.subtract(new Fraction(pool.fee, 1_000_000))),
ONE_HUNDRED_PERCENT ONE_HUNDRED_PERCENT
) )
) )
return ( }
realizedLPFee &&
trade && return new Percent(percent.numerator, percent.denominator)
(trade.inputAmount instanceof TokenAmount }
// computes price breakdown for the trade
export function computeRealizedLPFeeAmount(trade?: V2Trade | V3Trade | null): CurrencyAmount | undefined {
if (trade instanceof V2Trade) {
const realizedLPFee = computeRealizedLPFeePercent(trade)
// the amount of the input that accrues to LPs
return trade.inputAmount instanceof TokenAmount
? new TokenAmount(trade.inputAmount.token, realizedLPFee.multiply(trade.inputAmount.raw).quotient) ? new TokenAmount(trade.inputAmount.token, realizedLPFee.multiply(trade.inputAmount.raw).quotient)
: CurrencyAmount.ether(realizedLPFee.multiply(trade.inputAmount.raw).quotient)) : CurrencyAmount.ether(realizedLPFee.multiply(trade.inputAmount.raw).quotient)
) } else if (trade instanceof V3Trade) {
const realizedLPFee = computeRealizedLPFeePercent(trade)
return trade.inputAmount instanceof TokenAmount
? new TokenAmount(trade.inputAmount.token, realizedLPFee.multiply(trade.inputAmount.raw).quotient)
: CurrencyAmount.ether(realizedLPFee.multiply(trade.inputAmount.raw).quotient)
} }
return undefined return undefined
} }
......
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