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 { 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])
const setUserSlippageTolerance = useCallback( return useCallback(
(userSlippageTolerance: number) => { (userSlippageTolerance: Percent | 'auto') => {
dispatch(updateUserSlippageTolerance({ userSlippageTolerance })) let value: 'auto' | number
try {
value =
userSlippageTolerance === 'auto' ? 'auto' : JSBI.toNumber(userSlippageTolerance.multiply(10_000).quotient)
} catch (error) {
value = 'auto'
}
dispatch(
updateUserSlippageTolerance({
userSlippageTolerance: value,
})
)
}, },
[dispatch] [dispatch]
) )
}
/**
* 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 [percentage, setUserSlippageTolerance] 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 trade.route.pairs.reduce<Fraction>(
: ONE_HUNDRED_PERCENT.subtract( (currentFee: Fraction): Fraction => currentFee.multiply(INPUT_FRACTION_AFTER_FEE),
trade.route.pairs.reduce<Fraction>( ONE_HUNDRED_PERCENT
(currentFee: Fraction): Fraction => currentFee.multiply(INPUT_FRACTION_AFTER_FEE), )
ONE_HUNDRED_PERCENT )
) } else {
) percent = ONE_HUNDRED_PERCENT.subtract(
trade.route.pools.reduce<Fraction>(
(currentFee: Fraction, pool): Fraction =>
currentFee.multiply(ONE_HUNDRED_PERCENT.subtract(new Fraction(pool.fee, 1_000_000))),
ONE_HUNDRED_PERCENT
)
)
}
return new Percent(percent.numerator, percent.denominator)
}
// 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 // the amount of the input that accrues to LPs
return ( return trade.inputAmount instanceof TokenAmount
realizedLPFee && ? new TokenAmount(trade.inputAmount.token, realizedLPFee.multiply(trade.inputAmount.raw).quotient)
trade && : CurrencyAmount.ether(realizedLPFee.multiply(trade.inputAmount.raw).quotient)
(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) { } else if (trade instanceof V3Trade) {
const realizedLPFee = !trade const realizedLPFee = computeRealizedLPFeePercent(trade)
? undefined
: ONE_HUNDRED_PERCENT.subtract( return trade.inputAmount instanceof TokenAmount
trade.route.pools.reduce<Fraction>( ? new TokenAmount(trade.inputAmount.token, realizedLPFee.multiply(trade.inputAmount.raw).quotient)
(currentFee: Fraction, pool): Fraction => : CurrencyAmount.ether(realizedLPFee.multiply(trade.inputAmount.raw).quotient)
currentFee.multiply(ONE_HUNDRED_PERCENT.subtract(new Fraction(pool.fee, 1_000_000))),
ONE_HUNDRED_PERCENT
)
)
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))
)
} }
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