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'
import { darken } from 'polished'
import { useTranslation } from 'react-i18next'
import { NavLink, Link as HistoryLink } from 'react-router-dom'
import { Percent } from '@uniswap/sdk-core'
import { ArrowLeft } from 'react-feather'
import { RowBetween } from '../Row'
......@@ -80,7 +81,6 @@ export function FindPoolTabs({ origin }: { origin: string }) {
<StyledArrowLeft />
</HistoryLink>
<ActiveText>Import Pool</ActiveText>
<SettingsTab />
</RowBetween>
</Tabs>
)
......@@ -90,10 +90,12 @@ export function AddRemoveTabs({
adding,
creating,
positionID,
defaultSlippage,
}: {
adding: boolean
creating: boolean
positionID?: string | undefined
defaultSlippage: Percent
}) {
const theme = useTheme()
......@@ -118,7 +120,7 @@ export function AddRemoveTabs({
<TYPE.mediumHeader fontWeight={500} fontSize={20}>
{creating ? 'Create a pair' : adding ? 'Add Liquidity' : 'Remove Liquidity'}
</TYPE.mediumHeader>
<SettingsTab />
<SettingsTab placeholderSlippage={defaultSlippage} />
</RowBetween>
</Tabs>
)
......
import JSBI from 'jsbi'
import React, { useContext, useRef, useState } from 'react'
import { Settings, X } from 'react-feather'
import ReactGA from 'react-ga'
......@@ -7,12 +6,7 @@ import styled, { ThemeContext } from 'styled-components'
import { useOnClickOutside } from '../../hooks/useOnClickOutside'
import { ApplicationModal } from '../../state/application/actions'
import { useModalOpen, useToggleSettingsMenu } from '../../state/application/hooks'
import {
useExpertModeManager,
useUserTransactionTTL,
useUserSlippageTolerance,
useUserSingleHopOnly,
} from '../../state/user/hooks'
import { useExpertModeManager, useUserSingleHopOnly } from '../../state/user/hooks'
import { TYPE } from '../../theme'
import { ButtonError } from '../Button'
import { AutoColumn } from '../Column'
......@@ -21,6 +15,7 @@ import QuestionHelper from '../QuestionHelper'
import { RowBetween, RowFixed } from '../Row'
import Toggle from '../Toggle'
import TransactionSettings from '../TransactionSettings'
import { Percent } from '@uniswap/sdk-core'
const StyledMenuIcon = styled(Settings)`
height: 20px;
......@@ -116,15 +111,12 @@ const ModalContentWrapper = styled.div`
border-radius: 20px;
`
export default function SettingsTab() {
export default function SettingsTab({ placeholderSlippage }: { placeholderSlippage: Percent }) {
const node = useRef<HTMLDivElement>()
const open = useModalOpen(ApplicationModal.SETTINGS)
const toggle = useToggleSettingsMenu()
const theme = useContext(ThemeContext)
const [userSlippageTolerance, setUserslippageTolerance] = useUserSlippageTolerance()
const [ttl, setTtl] = useUserTransactionTTL()
const [expertMode, toggleExpertMode] = useExpertModeManager()
......@@ -191,12 +183,7 @@ export default function SettingsTab() {
<Text fontWeight={600} fontSize={14}>
Transaction Settings
</Text>
<TransactionSettings
rawSlippage={JSBI.toNumber(userSlippageTolerance.numerator)}
setRawSlippage={setUserslippageTolerance}
deadline={ttl}
setDeadline={setTtl}
/>
<TransactionSettings placeholderSlippage={placeholderSlippage} />
<Text fontWeight={600} fontSize={14}>
Interface Settings
</Text>
......
import { Percent } from '@uniswap/sdk-core'
import { Trade as V2Trade } from '@uniswap/v2-sdk'
import { Trade as V3Trade } from '@uniswap/v3-sdk'
import React, { useContext } from 'react'
import { ThemeContext } from 'styled-components'
import { useUserSlippageTolerance } from '../../state/user/hooks'
import { TYPE } from '../../theme'
import { computePriceImpactWithMaximumSlippage } from '../../utils/computePriceImpactWithMaximumSlippage'
import { computeRealizedLPFeeAmount } from '../../utils/prices'
......@@ -13,13 +13,13 @@ import SwapRoute from './SwapRoute'
export interface AdvancedSwapDetailsProps {
trade?: V2Trade | V3Trade
allowedSlippage: Percent
}
export function AdvancedSwapDetails({ trade }: AdvancedSwapDetailsProps) {
export function AdvancedSwapDetails({ trade, allowedSlippage }: AdvancedSwapDetailsProps) {
const theme = useContext(ThemeContext)
const realizedLPFee = computeRealizedLPFeeAmount(trade)
const [allowedSlippage] = useUserSlippageTolerance()
return !trade ? null : (
<AutoColumn gap="8px">
......@@ -55,6 +55,17 @@ export function AdvancedSwapDetails({ trade }: AdvancedSwapDetailsProps) {
<FormattedPriceImpact priceImpact={computePriceImpactWithMaximumSlippage(trade, allowedSlippage)} />
</TYPE.black>
</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>
)
}
import React from 'react'
import styled from 'styled-components'
import SettingsTab from '../Settings'
import { Percent } from '@uniswap/sdk-core'
import { RowBetween, RowFixed } from '../Row'
import { TYPE } from '../../theme'
......@@ -11,7 +12,7 @@ const StyledSwapHeader = styled.div`
color: ${({ theme }) => theme.text2};
`
export default function SwapHeader() {
export default function SwapHeader({ allowedSlippage }: { allowedSlippage: Percent }) {
return (
<StyledSwapHeader>
<RowBetween>
......@@ -21,9 +22,7 @@ export default function SwapHeader() {
</TYPE.black>
</RowFixed>
<RowFixed>
{/* <TradeInfo disabled={!trade} trade={trade} /> */}
{/* <div style={{ width: '8px' }}></div> */}
<SettingsTab />
<SettingsTab placeholderSlippage={allowedSlippage} />
</RowFixed>
</RowBetween>
</StyledSwapHeader>
......
......@@ -135,7 +135,7 @@ export default function SwapModalHeader({
</RowBetween>
<LightCard style={{ padding: '.75rem', marginTop: '0.5rem' }}>
<AdvancedSwapDetails trade={trade} />
<AdvancedSwapDetails trade={trade} allowedSlippage={allowedSlippage} />
</LightCard>
{showAcceptChanges ? (
......
......@@ -25,7 +25,7 @@ export default memo(function SwapRoute({ trade }: { trade: V2Trade | V3Trade })
return (
<Fragment key={i}>
<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}
</TYPE.black>
</Flex>
......
......@@ -205,10 +205,8 @@ export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = {
export const NetworkContextName = 'NETWORK'
// default allowed slippage, in bips
export const INITIAL_ALLOWED_SLIPPAGE = new Percent(10, 10_000)
// 20 minutes, denominated in seconds
export const DEFAULT_DEADLINE_FROM_NOW = 60 * 20
// 30 minutes, denominated in seconds
export const DEFAULT_DEADLINE_FROM_NOW = 60 * 30
// used for rewards deadlines
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'
import { SwapRouter, Trade as V3Trade } from '@uniswap/v3-sdk'
import { ChainId, Percent, TradeType } from '@uniswap/sdk-core'
import { useMemo } from 'react'
import { INITIAL_ALLOWED_SLIPPAGE } from '../constants'
import { SWAP_ROUTER_ADDRESSES } from '../constants/v3'
import { getTradeVersion } from '../utils/getTradeVersion'
import { useTransactionAdder } from '../state/transactions/hooks'
......@@ -51,7 +50,7 @@ interface FailedCall extends SwapCallEstimate {
*/
function useSwapCallArguments(
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
signatureData: SignatureData | null | undefined
): SwapCall[] {
......@@ -138,7 +137,7 @@ function useSwapCallArguments(
// and the user has approved the slippage adjusted input amount for the trade
export function useSwapCallback(
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
signatureData: SignatureData | undefined | 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 { 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 { AlertTriangle, AlertCircle } from 'react-feather'
import ReactGA from 'react-ga'
import { ZERO_PERCENT } from '../../constants'
import { useV3NFTPositionManagerContract } from '../../hooks/useContract'
import { RouteComponentProps } from 'react-router-dom'
import { Text } from 'rebass'
......@@ -25,7 +26,7 @@ import { useWalletModalToggle } from '../../state/application/hooks'
import { Field, Bound } from '../../state/mint/v3/actions'
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 { maxAmountSpend } from '../../utils/maxAmountSpend'
import AppBody from '../AppBody'
......@@ -52,6 +53,8 @@ import { BigNumber } from '@ethersproject/bignumber'
import { calculateGasMargin } from 'utils'
import { AddRemoveTabs } from 'components/NavigationTabs'
const DEFAULT_ADD_IN_RANGE_SLIPPAGE_TOLERANCE = new Percent(50, 10_000)
export default function AddLiquidity({
match: {
params: { currencyIdA, currencyIdB, feeAmount: feeAmountFromUrl, tokenId },
......@@ -148,7 +151,7 @@ export default function AddLiquidity({
// txn values
const deadline = useTransactionDeadline() // custom from users settings
const [allowedSlippage] = useUserSlippageTolerance() // custom from users
const [txHash, setTxHash] = useState<string>('')
// get formatted amounts
......@@ -193,6 +196,10 @@ export default function AddLiquidity({
chainId ? NONFUNGIBLE_POSITION_MANAGER_ADDRESSES[chainId] : undefined
)
const allowedSlippage = useUserSlippageToleranceWithDefault(
outOfRange ? ZERO_PERCENT : DEFAULT_ADD_IN_RANGE_SLIPPAGE_TOLERANCE
)
async function onAdd() {
if (!chainId || !library || !account) return
......@@ -396,7 +403,12 @@ export default function AddLiquidity({
pendingText={pendingText}
/>
<AppBody>
<AddRemoveTabs creating={false} adding={true} positionID={tokenId} />
<AddRemoveTabs
creating={false}
adding={true}
positionID={tokenId}
defaultSlippage={DEFAULT_ADD_IN_RANGE_SLIPPAGE_TOLERANCE}
/>
<Wrapper>
<AutoColumn gap="32px">
{!hasExistingPosition && (
......
import { BigNumber } from '@ethersproject/bignumber'
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 { Plus } from 'react-feather'
import ReactGA from 'react-ga'
......@@ -30,7 +30,7 @@ import { Field } from '../../state/mint/actions'
import { useDerivedMintInfo, useMintActionHandlers, useMintState } from '../../state/mint/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 { calculateGasMargin, calculateSlippageAmount } from '../../utils'
import { maxAmountSpend } from '../../utils/maxAmountSpend'
......@@ -42,6 +42,8 @@ import { currencyId } from '../../utils/currencyId'
import { PoolPriceBar } from './PoolPriceBar'
import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter'
const DEFAULT_ADD_V2_SLIPPAGE_TOLERANCE = new Percent(50, 10_000)
export default function AddLiquidity({
match: {
params: { currencyIdA, currencyIdB },
......@@ -90,7 +92,7 @@ export default function AddLiquidity({
// txn values
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>('')
// get formatted amounts
......@@ -315,7 +317,7 @@ export default function AddLiquidity({
return (
<>
<AppBody>
<AddRemoveTabs creating={isCreate} adding={true} />
<AddRemoveTabs creating={isCreate} adding={true} defaultSlippage={DEFAULT_ADD_V2_SLIPPAGE_TOLERANCE} />
<Wrapper>
<TransactionConfirmationModal
isOpen={showConfirm}
......
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 { Redirect, RouteComponentProps } from 'react-router'
import { Text } from 'rebass'
......@@ -25,7 +25,7 @@ import { ApprovalState, useApproveCallback } from 'hooks/useApproveCallback'
import { Dots } from 'components/swap/styleds'
import { ButtonConfirmed } from 'components/Button'
import useTransactionDeadline from 'hooks/useTransactionDeadline'
import { useUserSlippageTolerance } from 'state/user/hooks'
import { useUserSlippageToleranceWithDefault } from 'state/user/hooks'
import ReactGA from 'react-ga'
import { TransactionResponse } from '@ethersproject/providers'
import { useIsTransactionPending, useTransactionAdder } from 'state/transactions/hooks'
......@@ -49,6 +49,8 @@ import SettingsTab from 'components/Settings'
const ZERO = JSBI.BigInt(0)
const DEFAULT_MIGRATE_SLIPPAGE_TOLERANCE = new Percent(75, 10_000)
function EmptyState({ message }: { message: string }) {
return (
<AutoColumn style={{ minHeight: 200, justifyContent: 'center', alignItems: 'center' }}>
......@@ -119,7 +121,7 @@ function V2PairMigration({
const deadline = useTransactionDeadline() // custom from users settings
const blockTimestamp = useCurrentBlockTimestamp()
const [allowedSlippage] = useUserSlippageTolerance() // custom from users
const allowedSlippage = useUserSlippageToleranceWithDefault(DEFAULT_MIGRATE_SLIPPAGE_TOLERANCE) // custom from users
const currency0 = unwrappedToken(token0)
const currency1 = unwrappedToken(token1)
......@@ -673,7 +675,7 @@ export default function MigrateV2Pair({
<AutoRow style={{ alignItems: 'center', justifyContent: 'space-between' }} gap="8px">
<BackArrow to="/migrate/v2" />
<TYPE.mediumHeader>Migrate V2 Liquidity</TYPE.mediumHeader>
<SettingsTab />
<SettingsTab placeholderSlippage={DEFAULT_MIGRATE_SLIPPAGE_TOLERANCE} />
</AutoRow>
{!account ? (
......
......@@ -15,13 +15,13 @@ import { Text } from 'rebass'
import CurrencyLogo from 'components/CurrencyLogo'
import FormattedCurrencyAmount from 'components/FormattedCurrencyAmount'
import { useV3NFTPositionManagerContract } from 'hooks/useContract'
import { useUserSlippageTolerance } from 'state/user/hooks'
import { useUserSlippageToleranceWithDefault } from 'state/user/hooks'
import useTransactionDeadline from 'hooks/useTransactionDeadline'
import ReactGA from 'react-ga'
import { useActiveWeb3React } from 'hooks'
import { TransactionResponse } from '@ethersproject/providers'
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 { Wrapper, SmallMaxButton, ResponsiveHeaderText } from './styled'
import Loader from 'components/Loader'
......@@ -37,6 +37,8 @@ import RangeBadge from 'components/Badge/RangeBadge'
export const UINT128MAX = BigNumber.from(2).pow(128).sub(1)
const DEFAULT_REMOVE_V3_LIQUIDITY_SLIPPAGE_TOLERANCE = new Percent(5, 100)
// redirect invalid tokenIds
export default function RemoveLiquidityV3({
location,
......@@ -89,7 +91,7 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
const [percentForSlider, onPercentSelectForSlider] = useDebouncedChangeHandler(percent, onPercentSelect)
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 [attemptingTxn, setAttemptingTxn] = useState(false)
......@@ -274,7 +276,12 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
pendingText={pendingText}
/>
<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>
{position ? (
<AutoColumn gap="lg">
......
......@@ -40,9 +40,11 @@ import { useBurnActionHandlers } from '../../state/burn/hooks'
import { useDerivedBurnInfo, useBurnState } from '../../state/burn/hooks'
import { Field } from '../../state/burn/actions'
import { useWalletModalToggle } from '../../state/application/hooks'
import { useUserSlippageTolerance } from '../../state/user/hooks'
import { useUserSlippageToleranceWithDefault } from '../../state/user/hooks'
import { BigNumber } from '@ethersproject/bignumber'
const DEFAULT_REMOVE_LIQUIDITY_SLIPPAGE_TOLERANCE = new Percent(5, 100)
export default function RemoveLiquidity({
history,
match: {
......@@ -76,7 +78,7 @@ export default function RemoveLiquidity({
// txn values
const [txHash, setTxHash] = useState<string>('')
const deadline = useTransactionDeadline()
const [allowedSlippage] = useUserSlippageTolerance()
const allowedSlippage = useUserSlippageToleranceWithDefault(DEFAULT_REMOVE_LIQUIDITY_SLIPPAGE_TOLERANCE)
const formattedAmounts = {
[Field.LIQUIDITY_PERCENT]: parsedAmounts[Field.LIQUIDITY_PERCENT].equalTo('0')
......@@ -426,7 +428,7 @@ export default function RemoveLiquidity({
return (
<>
<AppBody>
<AddRemoveTabs creating={false} adding={false} />
<AddRemoveTabs creating={false} adding={false} defaultSlippage={DEFAULT_REMOVE_LIQUIDITY_SLIPPAGE_TOLERANCE} />
<Wrapper>
<TransactionConfirmationModal
isOpen={showConfirm}
......
......@@ -46,7 +46,7 @@ import {
useSwapActionHandlers,
useSwapState,
} 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 { computeFiatValuePriceImpact } from '../../utils/computeFiatValuePriceImpact'
import { computePriceImpactWithMaximumSlippage } from '../../utils/computePriceImpactWithMaximumSlippage'
......@@ -100,19 +100,21 @@ export default function Swap({ history }: RouteComponentProps) {
// for expert mode
const [isExpertMode] = useExpertModeManager()
// get custom setting values for user
const [allowedSlippage] = useUserSlippageTolerance()
// get version from the url
const toggledVersion = useToggledVersion()
// swap state
const { independentField, typedValue, recipient } = useSwapState()
const {
v2Trade,
v3TradeState: { trade: v3Trade, state: v3TradeState },
toggledTrade: trade,
allowedSlippage,
currencyBalances,
parsedAmount,
currencies,
inputError: swapInputError,
} = useDerivedSwapInfo()
} = useDerivedSwapInfo(toggledVersion)
const { wrapType, execute: onWrap, inputError: wrapInputError } = useWrapCallback(
currencies[Field.INPUT],
......@@ -121,13 +123,6 @@ export default function Swap({ history }: RouteComponentProps) {
)
const showWrap: boolean = wrapType !== WrapType.NOT_APPLICABLE
const { address: recipientAddress } = useENSAddress(recipient)
const toggledVersion = useToggledVersion()
const trade = showWrap
? undefined
: {
[Version.v2]: v2Trade,
[Version.v3]: v3Trade ?? undefined,
}[toggledVersion]
const parsedAmounts = useMemo(
() =>
......@@ -357,7 +352,7 @@ export default function Swap({ history }: RouteComponentProps) {
onDismiss={handleDismissTokenWarning}
/>
<AppBody>
<SwapHeader />
<SwapHeader allowedSlippage={allowedSlippage} />
<Wrapper id="swap-page">
<ConfirmSwapModal
isOpen={showConfirm}
......@@ -484,7 +479,9 @@ export default function Swap({ history }: RouteComponentProps) {
showInverted={showInverted}
setShowInverted={setShowInverted}
/>
<MouseoverTooltipContent content={<AdvancedSwapDetails trade={trade} />}>
<MouseoverTooltipContent
content={<AdvancedSwapDetails trade={trade} allowedSlippage={allowedSlippage} />}
>
<StyledInfo />
</MouseoverTooltipContent>
</RowFixed>
......
......@@ -2,13 +2,15 @@ import { Trade as V3Trade } from '@uniswap/v3-sdk'
import { useBestV3TradeExactIn, useBestV3TradeExactOut, V3TradeState } from '../../hooks/useBestV3Trade'
import useENS from '../../hooks/useENS'
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 { ParsedQs } from 'qs'
import { useCallback, useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { useActiveWeb3React } from '../../hooks'
import { useCurrency } from '../../hooks/Tokens'
import useSwapSlippageTolerance from '../../hooks/useSwapSlippageTolerance'
import { Version } from '../../hooks/useToggledVersion'
import { useV2TradeExactIn, useV2TradeExactOut } from '../../hooks/useV2Trade'
import useParsedQueryString from '../../hooks/useParsedQueryString'
import { isAddress } from '../../utils'
......@@ -16,7 +18,6 @@ import { AppDispatch, AppState } from '../index'
import { useCurrencyBalances } from '../wallet/hooks'
import { Field, replaceSwapState, selectCurrency, setRecipient, switchCurrencies, typeInput } from './actions'
import { SwapState } from './reducer'
import { useUserSlippageTolerance } from '../user/hooks'
export function useSwapState(): AppState['swap'] {
return useSelector<AppState, AppState['swap']>((state) => state.swap)
......@@ -109,13 +110,17 @@ function involvesAddress(trade: V2Trade | V3Trade, checksummedAddress: string):
}
// 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 }
currencyBalances: { [field in Field]?: CurrencyAmount }
parsedAmount: CurrencyAmount | undefined
v2Trade: V2Trade | undefined
inputError?: string
v2Trade: V2Trade | undefined
v3TradeState: { trade: V3Trade | null; state: V3TradeState }
toggledTrade: V2Trade | V3Trade | undefined
allowedSlippage: Percent
} {
const { account } = useActiveWeb3React()
......@@ -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
const [balanceIn, amountIn] = [currencyBalances[Field.INPUT], v2Trade?.maximumAmountIn(allowedSlippage)]
......@@ -198,9 +204,11 @@ export function useDerivedSwapInfo(): {
currencies,
currencyBalances,
parsedAmount,
v2Trade: v2Trade ?? undefined,
inputError,
v2Trade: v2Trade ?? undefined,
v3TradeState: v3Trade,
toggledTrade,
allowedSlippage,
}
}
......
......@@ -17,7 +17,7 @@ export const updateMatchesDarkMode = createAction<{ matchesDarkMode: boolean }>(
export const updateUserDarkMode = createAction<{ userDarkMode: boolean }>('user/updateUserDarkMode')
export const updateUserExpertMode = createAction<{ userExpertMode: boolean }>('user/updateUserExpertMode')
export const updateUserSingleHopOnly = createAction<{ userSingleHopOnly: boolean }>('user/updateUserSingleHopOnly')
export const updateUserSlippageTolerance = createAction<{ userSlippageTolerance: number }>(
export const updateUserSlippageTolerance = createAction<{ userSlippageTolerance: number | 'auto' }>(
'user/updateUserSlippageTolerance'
)
export const updateUserDeadline = createAction<{ userDeadline: number }>('user/updateUserDeadline')
......
import { ChainId, Percent, Token } from '@uniswap/sdk-core'
import { Pair } from '@uniswap/v2-sdk'
import JSBI from 'jsbi'
import flatMap from 'lodash.flatmap'
import { useCallback, useMemo } from 'react'
import { shallowEqual, useDispatch, useSelector } from 'react-redux'
......@@ -14,12 +15,12 @@ import {
removeSerializedToken,
SerializedPair,
SerializedToken,
toggleURLWarning,
updateUserDarkMode,
updateUserDeadline,
updateUserExpertMode,
updateUserSlippageTolerance,
toggleURLWarning,
updateUserSingleHopOnly,
updateUserSlippageTolerance,
} from './actions'
function serializeToken(token: Token): SerializedToken {
......@@ -100,22 +101,51 @@ export function useUserSingleHopOnly(): [boolean, (newSingleHopOnly: boolean) =>
return [singleHopOnly, setSingleHopOnly]
}
export function useUserSlippageTolerance(): [Percent, (slippageBips: number) => void] {
export function useSetUserSlippageTolerance(): (slippageTolerance: Percent | 'auto') => void {
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(
(userSlippageTolerance: number) => {
dispatch(updateUserSlippageTolerance({ userSlippageTolerance }))
return useCallback(
(userSlippageTolerance: Percent | 'auto') => {
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]
)
}
/**
* 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] {
......
......@@ -27,7 +27,7 @@ describe('swap reducer', () => {
} as any)
store.dispatch(updateVersion())
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 {
userSingleHopOnly: boolean // only allow swaps on direct pairs
// 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
userDeadline: number
......@@ -62,7 +63,8 @@ export const initialState: UserState = {
matchesDarkMode: false,
userExpertMode: false,
userSingleHopOnly: false,
userSlippageTolerance: 10,
userSlippageTolerance: 'auto',
userSlippageToleranceHasBeenMigratedToAuto: true,
userDeadline: DEFAULT_DEADLINE_FROM_NOW,
tokens: {},
pairs: {},
......@@ -75,13 +77,26 @@ export default createReducer(initialState, (builder) =>
.addCase(updateVersion, (state) => {
// slippage isnt being tracked in local storage, reset to default
// noinspection SuspiciousTypeOfGuard
if (typeof state.userSlippageTolerance !== 'number') {
state.userSlippageTolerance = 10
if (
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
// 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
}
......
......@@ -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 INPUT_FRACTION_AFTER_FEE = ONE_HUNDRED_PERCENT.subtract(THIRTY_BIPS_FEE)
// computes price breakdown for the trade
export function computeRealizedLPFeeAmount(trade?: V2Trade | V3Trade | null): CurrencyAmount | undefined {
// computes realized lp fee as a percent
export function computeRealizedLPFeePercent(trade: V2Trade | V3Trade): Percent {
let percent: Percent
if (trade instanceof V2Trade) {
// 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))
const realizedLPFee = !trade
? undefined
: ONE_HUNDRED_PERCENT.subtract(
trade.route.pairs.reduce<Fraction>(
(currentFee: Fraction): Fraction => currentFee.multiply(INPUT_FRACTION_AFTER_FEE),
ONE_HUNDRED_PERCENT
)
)
percent = ONE_HUNDRED_PERCENT.subtract(
trade.route.pairs.reduce<Fraction>(
(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
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 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>(
(currentFee: Fraction, pool): Fraction =>
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))
)
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
}
......
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