improvement: estimated amounts instead of minimum/maximums (#1570)

* estimated amounts instead of minimum/maximums

* missed a spot

* fix linting error
This commit is contained in:
Moody Salem 2021-05-14 07:15:53 -05:00 committed by GitHub
parent 534afb3278
commit 7a0b85bf41
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 57 additions and 65 deletions

@ -51,7 +51,7 @@
"@uniswap/v2-sdk": "^3.0.0-alpha.0", "@uniswap/v2-sdk": "^3.0.0-alpha.0",
"@uniswap/v3-core": "1.0.0", "@uniswap/v3-core": "1.0.0",
"@uniswap/v3-periphery": "1.0.0", "@uniswap/v3-periphery": "1.0.0",
"@uniswap/v3-sdk": "^3.0.0-alpha.2", "@uniswap/v3-sdk": "^3.0.0-alpha.4",
"@web3-react/core": "^6.0.9", "@web3-react/core": "^6.0.9",
"@web3-react/fortmatic-connector": "^6.0.9", "@web3-react/fortmatic-connector": "^6.0.9",
"@web3-react/injected-connector": "^6.0.7", "@web3-react/injected-connector": "^6.0.7",

@ -1,11 +1,10 @@
import { Percent, Currency, TradeType } from '@uniswap/sdk-core' import { Percent, Currency, TradeType } 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, useMemo } from 'react'
import { ThemeContext } from 'styled-components' import { ThemeContext } from 'styled-components'
import { TYPE } from '../../theme' import { TYPE } from '../../theme'
import { computePriceImpactWithMaximumSlippage } from '../../utils/computePriceImpactWithMaximumSlippage' import { computeRealizedLPFeePercent } from '../../utils/prices'
import { computeRealizedLPFeeAmount } from '../../utils/prices'
import { AutoColumn } from '../Column' import { AutoColumn } from '../Column'
import { RowBetween, RowFixed } from '../Row' import { RowBetween, RowFixed } from '../Row'
import FormattedPriceImpact from './FormattedPriceImpact' import FormattedPriceImpact from './FormattedPriceImpact'
@ -19,7 +18,14 @@ export interface AdvancedSwapDetailsProps {
export function AdvancedSwapDetails({ trade, allowedSlippage }: AdvancedSwapDetailsProps) { export function AdvancedSwapDetails({ trade, allowedSlippage }: AdvancedSwapDetailsProps) {
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
const realizedLPFee = computeRealizedLPFeeAmount(trade) const { realizedLPFee, priceImpact } = useMemo(() => {
if (!trade) return { realizedLPFee: undefined, priceImpact: undefined }
const realizedLpFeePercent = computeRealizedLPFeePercent(trade)
const realizedLPFee = trade.inputAmount.multiply(realizedLpFeePercent)
const priceImpact = trade.priceImpact.subtract(realizedLpFeePercent)
return { priceImpact, realizedLPFee }
}, [trade])
return !trade ? null : ( return !trade ? null : (
<AutoColumn gap="8px"> <AutoColumn gap="8px">
@ -30,7 +36,7 @@ export function AdvancedSwapDetails({ trade, allowedSlippage }: AdvancedSwapDeta
</TYPE.black> </TYPE.black>
</RowFixed> </RowFixed>
<TYPE.black textAlign="right" fontSize={12} color={theme.text1}> <TYPE.black textAlign="right" fontSize={12} color={theme.text1}>
{realizedLPFee ? `${realizedLPFee.toSignificant(4)} ${trade.inputAmount.currency.symbol}` : '-'} {realizedLPFee ? `${realizedLPFee.toSignificant(4)} ${realizedLPFee.currency.symbol}` : '-'}
</TYPE.black> </TYPE.black>
</RowBetween> </RowBetween>
@ -48,11 +54,24 @@ export function AdvancedSwapDetails({ trade, allowedSlippage }: AdvancedSwapDeta
<RowBetween> <RowBetween>
<RowFixed> <RowFixed>
<TYPE.black fontSize={12} fontWeight={400} color={theme.text2}> <TYPE.black fontSize={12} fontWeight={400} color={theme.text2}>
Execution price vs. spot price Price Impact
</TYPE.black> </TYPE.black>
</RowFixed> </RowFixed>
<TYPE.black textAlign="right" fontSize={12} color={theme.text1}> <TYPE.black textAlign="right" fontSize={12} color={theme.text1}>
<FormattedPriceImpact priceImpact={computePriceImpactWithMaximumSlippage(trade, allowedSlippage)} /> <FormattedPriceImpact priceImpact={priceImpact} />
</TYPE.black>
</RowBetween>
<RowBetween>
<RowFixed>
<TYPE.black fontSize={12} fontWeight={400} color={theme.text2}>
{trade.tradeType === TradeType.EXACT_INPUT ? 'Minimum Amount Out' : 'Maximum Amount In'}
</TYPE.black>
</RowFixed>
<TYPE.black textAlign="right" fontSize={12} color={theme.text1}>
{trade.tradeType === TradeType.EXACT_INPUT
? `${trade.minimumAmountOut(allowedSlippage).toSignificant(6)} ${trade.outputAmount.currency.symbol}`
: `${trade.maximumAmountIn(allowedSlippage).toSignificant(6)} ${trade.inputAmount.currency.symbol}`}
</TYPE.black> </TYPE.black>
</RowBetween> </RowBetween>

@ -90,9 +90,9 @@ export default function ConfirmSwapModal({
}, [onConfirm, showAcceptChanges, swapErrorMessage, trade]) }, [onConfirm, showAcceptChanges, swapErrorMessage, trade])
// text to show while loading // text to show while loading
const pendingText = `Swapping ${trade?.maximumAmountIn(allowedSlippage)?.toSignificant(6)} ${ const pendingText = `Swapping ${trade?.inputAmount?.toSignificant(6)} ${
trade?.inputAmount?.currency?.symbol trade?.inputAmount?.currency?.symbol
} for ${trade?.minimumAmountOut(allowedSlippage)?.toSignificant(6)} ${trade?.outputAmount?.currency?.symbol}` } for ${trade?.outputAmount?.toSignificant(6)} ${trade?.outputAmount?.currency?.symbol}`
const confirmationContent = useCallback( const confirmationContent = useCallback(
() => () =>

@ -31,12 +31,10 @@ export const ArrowWrapper = styled.div`
margin-top: -18px; margin-top: -18px;
margin-bottom: -18px; margin-bottom: -18px;
left: calc(50% - 16px); left: calc(50% - 16px);
/* transform: rotate(90deg); */
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
background-color: ${({ theme }) => theme.bg1}; background-color: ${({ theme }) => theme.bg1};
/* border: 4px solid ${({ theme }) => theme.bg0}; */
z-index: 2; z-index: 2;
` `
@ -53,15 +51,12 @@ export default function SwapModalHeader({
showAcceptChanges: boolean showAcceptChanges: boolean
onAcceptChanges: () => void onAcceptChanges: () => void
}) { }) {
const maximumAmountIn = trade.maximumAmountIn(allowedSlippage)
const minimumAmountOut = trade.minimumAmountOut(allowedSlippage)
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
const [showInverted, setShowInverted] = useState<boolean>(false) const [showInverted, setShowInverted] = useState<boolean>(false)
const fiatValueInput = useUSDCValue(maximumAmountIn) const fiatValueInput = useUSDCValue(trade.inputAmount)
const fiatValueOutput = useUSDCValue(minimumAmountOut) const fiatValueOutput = useUSDCValue(trade.outputAmount)
return ( return (
<AutoColumn gap={'4px'} style={{ marginTop: '1rem' }}> <AutoColumn gap={'4px'} style={{ marginTop: '1rem' }}>
@ -86,7 +81,7 @@ export default function SwapModalHeader({
fontWeight={500} fontWeight={500}
color={showAcceptChanges && trade.tradeType === TradeType.EXACT_OUTPUT ? theme.primary1 : ''} color={showAcceptChanges && trade.tradeType === TradeType.EXACT_OUTPUT ? theme.primary1 : ''}
> >
{maximumAmountIn.toSignificant(6)} {trade.inputAmount.toSignificant(6)}
</TruncatedText> </TruncatedText>
</RowFixed> </RowFixed>
</RowBetween> </RowBetween>
@ -117,7 +112,7 @@ export default function SwapModalHeader({
</RowFixed> </RowFixed>
<RowFixed gap={'0px'}> <RowFixed gap={'0px'}>
<TruncatedText fontSize={24} fontWeight={500}> <TruncatedText fontSize={24} fontWeight={500}>
{minimumAmountOut.toSignificant(6)} {trade.outputAmount.toSignificant(6)}
</TruncatedText> </TruncatedText>
</RowFixed> </RowFixed>
</RowBetween> </RowBetween>
@ -127,11 +122,7 @@ export default function SwapModalHeader({
<TYPE.body color={theme.text2} fontWeight={500} fontSize={14}> <TYPE.body color={theme.text2} fontWeight={500} fontSize={14}>
{'Price:'} {'Price:'}
</TYPE.body> </TYPE.body>
<TradePrice <TradePrice price={trade.executionPrice} showInverted={showInverted} setShowInverted={setShowInverted} />
price={trade.worstExecutionPrice(allowedSlippage)}
showInverted={showInverted}
setShowInverted={setShowInverted}
/>
</RowBetween> </RowBetween>
<LightCard style={{ padding: '.75rem', marginTop: '0.5rem' }}> <LightCard style={{ padding: '.75rem', marginTop: '0.5rem' }}>
@ -155,12 +146,12 @@ export default function SwapModalHeader({
</SwapShowAcceptChanges> </SwapShowAcceptChanges>
) : null} ) : null}
{/* <AutoColumn justify="flex-start" gap="sm" style={{ padding: '.75rem 1rem' }}> <AutoColumn justify="flex-start" gap="sm" style={{ padding: '.75rem 1rem' }}>
{trade.tradeType === TradeType.EXACT_INPUT ? ( {trade.tradeType === TradeType.EXACT_INPUT ? (
<TYPE.italic fontWeight={400} textAlign="left" style={{ width: '100%' }}> <TYPE.italic fontWeight={400} textAlign="left" style={{ width: '100%' }}>
{`Output is estimated. You will receive at least `} {`Output is estimated. You will receive at least `}
<b> <b>
{minimumAmountOut.toSignificant(6)} {trade.outputAmount.currency.symbol} {trade.minimumAmountOut(allowedSlippage).toSignificant(6)} {trade.outputAmount.currency.symbol}
</b> </b>
{' or the transaction will revert.'} {' or the transaction will revert.'}
</TYPE.italic> </TYPE.italic>
@ -168,12 +159,12 @@ export default function SwapModalHeader({
<TYPE.italic fontWeight={400} textAlign="left" style={{ width: '100%' }}> <TYPE.italic fontWeight={400} textAlign="left" style={{ width: '100%' }}>
{`Input is estimated. You will sell at most `} {`Input is estimated. You will sell at most `}
<b> <b>
{maximumAmountIn.toSignificant(6)} {trade.inputAmount.currency.symbol} {trade.maximumAmountIn(allowedSlippage).toSignificant(6)} {trade.inputAmount.currency.symbol}
</b> </b>
{' or the transaction will revert.'} {' or the transaction will revert.'}
</TYPE.italic> </TYPE.italic>
)} )}
</AutoColumn> */} </AutoColumn>
{recipient !== null ? ( {recipient !== null ? (
<AutoColumn justify="flex-start" gap="sm" style={{ padding: '12px 0 0 0px' }}> <AutoColumn justify="flex-start" gap="sm" style={{ padding: '12px 0 0 0px' }}>
<TYPE.main> <TYPE.main>

@ -275,8 +275,8 @@ export function useSwapCallback(
.then((response) => { .then((response) => {
const inputSymbol = trade.inputAmount.currency.symbol const inputSymbol = trade.inputAmount.currency.symbol
const outputSymbol = trade.outputAmount.currency.symbol const outputSymbol = trade.outputAmount.currency.symbol
const inputAmount = trade.maximumAmountIn(allowedSlippage).toSignificant(4) const inputAmount = trade.inputAmount.toSignificant(4)
const outputAmount = trade.minimumAmountOut(allowedSlippage).toSignificant(4) const outputAmount = trade.outputAmount.toSignificant(4)
const base = `Swap ${inputAmount} ${inputSymbol} for ${outputAmount} ${outputSymbol}` const base = `Swap ${inputAmount} ${inputSymbol} for ${outputAmount} ${outputSymbol}`
const withRecipient = const withRecipient =
@ -312,5 +312,5 @@ export function useSwapCallback(
}, },
error: null, error: null,
} }
}, [trade, library, account, chainId, recipient, recipientAddressOrName, swapCalls, allowedSlippage, addTransaction]) }, [trade, library, account, chainId, recipient, recipientAddressOrName, swapCalls, addTransaction])
} }

@ -49,7 +49,6 @@ import {
import { useExpertModeManager, useUserSingleHopOnly } 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 { getTradeVersion } from '../../utils/getTradeVersion' import { getTradeVersion } from '../../utils/getTradeVersion'
import { isTradeBetter } from '../../utils/isTradeBetter' import { isTradeBetter } from '../../utils/isTradeBetter'
import { maxAmountSpend } from '../../utils/maxAmountSpend' import { maxAmountSpend } from '../../utils/maxAmountSpend'
@ -132,10 +131,10 @@ export default function Swap({ history }: RouteComponentProps) {
[Field.OUTPUT]: parsedAmount, [Field.OUTPUT]: parsedAmount,
} }
: { : {
[Field.INPUT]: independentField === Field.INPUT ? parsedAmount : trade?.maximumAmountIn(allowedSlippage), [Field.INPUT]: independentField === Field.INPUT ? parsedAmount : trade?.inputAmount,
[Field.OUTPUT]: independentField === Field.OUTPUT ? parsedAmount : trade?.minimumAmountOut(allowedSlippage), [Field.OUTPUT]: independentField === Field.OUTPUT ? parsedAmount : trade?.outputAmount,
}, },
[allowedSlippage, independentField, parsedAmount, showWrap, trade] [independentField, parsedAmount, showWrap, trade]
) )
const fiatValueInput = useUSDCValue(parsedAmounts[Field.INPUT]) const fiatValueInput = useUSDCValue(parsedAmounts[Field.INPUT])
@ -292,7 +291,7 @@ export default function Swap({ history }: RouteComponentProps) {
// warnings on the greater of fiat value price impact and execution price impact // warnings on the greater of fiat value price impact and execution price impact
const priceImpactSeverity = useMemo(() => { const priceImpactSeverity = useMemo(() => {
const executionPriceImpact = trade ? computePriceImpactWithMaximumSlippage(trade, allowedSlippage) : undefined const executionPriceImpact = trade?.priceImpact
return warningSeverity( return warningSeverity(
executionPriceImpact && priceImpact executionPriceImpact && priceImpact
? executionPriceImpact.greaterThan(priceImpact) ? executionPriceImpact.greaterThan(priceImpact)
@ -300,7 +299,7 @@ export default function Swap({ history }: RouteComponentProps) {
: priceImpact : priceImpact
: executionPriceImpact ?? priceImpact : executionPriceImpact ?? priceImpact
) )
}, [allowedSlippage, priceImpact, trade]) }, [priceImpact, trade])
// show approve flow when: no error on inputs, not approved or pending, or approved in current session // show approve flow when: no error on inputs, not approved or pending, or approved in current session
// never show if price impact is above threshold in non expert mode // never show if price impact is above threshold in non expert mode
@ -475,7 +474,7 @@ export default function Swap({ history }: RouteComponentProps) {
{trade ? ( {trade ? (
<RowFixed> <RowFixed>
<TradePrice <TradePrice
price={trade.worstExecutionPrice(allowedSlippage)} price={trade.executionPrice}
showInverted={showInverted} showInverted={showInverted}
setShowInverted={setShowInverted} setShowInverted={setShowInverted}
/> />

@ -1,14 +0,0 @@
import { computePriceImpact, Currency, Percent, TradeType } from '@uniswap/sdk-core'
import { Trade as V2Trade } from '@uniswap/v2-sdk'
import { Trade as V3Trade } from '@uniswap/v3-sdk'
export function computePriceImpactWithMaximumSlippage(
trade: V2Trade<Currency, Currency, TradeType> | V3Trade<Currency, Currency, TradeType>,
allowedSlippage: Percent
): Percent {
return computePriceImpact(
trade.route.midPrice,
trade.maximumAmountIn(allowedSlippage),
trade.minimumAmountOut(allowedSlippage)
)
}

@ -22,15 +22,15 @@ export function computeRealizedLPFeePercent(
// 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))
percent = ONE_HUNDRED_PERCENT.subtract( percent = ONE_HUNDRED_PERCENT.subtract(
trade.route.pairs.reduce<Fraction>( trade.route.pairs.reduce<Percent>(
(currentFee: Fraction): Fraction => currentFee.multiply(INPUT_FRACTION_AFTER_FEE), (currentFee: Percent): Percent => currentFee.multiply(INPUT_FRACTION_AFTER_FEE),
ONE_HUNDRED_PERCENT ONE_HUNDRED_PERCENT
) )
) )
} else { } else {
percent = ONE_HUNDRED_PERCENT.subtract( percent = ONE_HUNDRED_PERCENT.subtract(
trade.route.pools.reduce<Fraction>( trade.route.pools.reduce<Percent>(
(currentFee: Fraction, pool): Fraction => (currentFee: Percent, pool): Percent =>
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
) )
@ -44,14 +44,11 @@ export function computeRealizedLPFeePercent(
export function computeRealizedLPFeeAmount( export function computeRealizedLPFeeAmount(
trade?: V2Trade<Currency, Currency, TradeType> | V3Trade<Currency, Currency, TradeType> | null trade?: V2Trade<Currency, Currency, TradeType> | V3Trade<Currency, Currency, TradeType> | null
): CurrencyAmount<Currency> | undefined { ): CurrencyAmount<Currency> | undefined {
if (trade instanceof V2Trade || trade instanceof V3Trade) { if (trade) {
const realizedLPFee = computeRealizedLPFeePercent(trade) const realizedLPFee = computeRealizedLPFeePercent(trade)
// the amount of the input that accrues to LPs // the amount of the input that accrues to LPs
return CurrencyAmount.fromRawAmount( return CurrencyAmount.fromRawAmount(trade.inputAmount.currency, trade.inputAmount.multiply(realizedLPFee).quotient)
trade.inputAmount.currency,
trade.inputAmount.asFraction.multiply(realizedLPFee).quotient
)
} }
return undefined return undefined

@ -4478,10 +4478,10 @@
"@uniswap/v3-core" "1.0.0" "@uniswap/v3-core" "1.0.0"
base64-sol "1.0.1" base64-sol "1.0.1"
"@uniswap/v3-sdk@^3.0.0-alpha.2": "@uniswap/v3-sdk@^3.0.0-alpha.4":
version "3.0.0-alpha.2" version "3.0.0-alpha.4"
resolved "https://registry.yarnpkg.com/@uniswap/v3-sdk/-/v3-sdk-3.0.0-alpha.2.tgz#c72c2a81da515839f48e8aba6dff2a7163c94da1" resolved "https://registry.yarnpkg.com/@uniswap/v3-sdk/-/v3-sdk-3.0.0-alpha.4.tgz#e8bf26291fd74e36a5a3d9b88f1809a7aceb7d3a"
integrity sha512-L5coA6F2/Xq25wLjEddXzj3uXCV0v5TOm7Fsw+Z8PubWmi2sXaJFqUfsoOXSfICD5NjJUb3u3bpe4+ZHRaUrnw== integrity sha512-BcEH8eHt+b6eaaiLDlzbFox2NquP1H7KgrgmNjAlU/et+vC4azdfNN6SsRlTFzhioPOwHlAKAAZJLq+yzboDOQ==
dependencies: dependencies:
"@ethersproject/abi" "^5.0.12" "@ethersproject/abi" "^5.0.12"
"@ethersproject/solidity" "^5.0.9" "@ethersproject/solidity" "^5.0.9"