Commit 1c142bb7 authored by cartcrom's avatar cartcrom Committed by GitHub

feat: workaround for FOT swaps (#7218)

* fix: workaround to include tax in slippage tolerance

* refactor: simplify tax line item components

* docs: update FOT comments

* fix: simplify tax calculation logic

* docs: update comment and add TODO

* fix: use postTaxAmountOut for price change calculation

* fix: use preTaxStablecoinPriceImpact for swab button warnings

* test: unit test for swap details/modal

* feat: add feature flag to gate FOT changes

* docs: add comment to postTaxOutputAmount getter

* fix: reword fee tooltip

* refactor: warning theme color variable usage

* fix: update snapshots

* fix: use preTaxStablecoinPriceImpact for price impact prompt logic

* lint: dependency array
parent 3e67982b
...@@ -90,7 +90,7 @@ const DescriptionText = styled(ThemedText.LabelMicro)` ...@@ -90,7 +90,7 @@ const DescriptionText = styled(ThemedText.LabelMicro)`
function useOrderAmounts( function useOrderAmounts(
orderDetails?: UniswapXOrderDetails orderDetails?: UniswapXOrderDetails
): Pick<InterfaceTrade, 'inputAmount' | 'outputAmount'> | undefined { ): Pick<InterfaceTrade, 'inputAmount' | 'postTaxOutputAmount'> | undefined {
const inputCurrency = useCurrency(orderDetails?.swapInfo?.inputCurrencyId, orderDetails?.chainId) const inputCurrency = useCurrency(orderDetails?.swapInfo?.inputCurrencyId, orderDetails?.chainId)
const outputCurrency = useCurrency(orderDetails?.swapInfo?.outputCurrencyId, orderDetails?.chainId) const outputCurrency = useCurrency(orderDetails?.swapInfo?.outputCurrencyId, orderDetails?.chainId)
...@@ -106,7 +106,7 @@ function useOrderAmounts( ...@@ -106,7 +106,7 @@ function useOrderAmounts(
if (swapInfo.tradeType === TradeType.EXACT_INPUT) { if (swapInfo.tradeType === TradeType.EXACT_INPUT) {
return { return {
inputAmount: CurrencyAmount.fromRawAmount(inputCurrency, swapInfo.inputCurrencyAmountRaw), inputAmount: CurrencyAmount.fromRawAmount(inputCurrency, swapInfo.inputCurrencyAmountRaw),
outputAmount: CurrencyAmount.fromRawAmount( postTaxOutputAmount: CurrencyAmount.fromRawAmount(
outputCurrency, outputCurrency,
swapInfo.settledOutputCurrencyAmountRaw ?? swapInfo.expectedOutputCurrencyAmountRaw swapInfo.settledOutputCurrencyAmountRaw ?? swapInfo.expectedOutputCurrencyAmountRaw
), ),
...@@ -114,7 +114,7 @@ function useOrderAmounts( ...@@ -114,7 +114,7 @@ function useOrderAmounts(
} else { } else {
return { return {
inputAmount: CurrencyAmount.fromRawAmount(inputCurrency, swapInfo.expectedInputCurrencyAmountRaw), inputAmount: CurrencyAmount.fromRawAmount(inputCurrency, swapInfo.expectedInputCurrencyAmountRaw),
outputAmount: CurrencyAmount.fromRawAmount(outputCurrency, swapInfo.outputCurrencyAmountRaw), postTaxOutputAmount: CurrencyAmount.fromRawAmount(outputCurrency, swapInfo.outputCurrencyAmountRaw),
} }
} }
} }
......
import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'featureFlags' import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'featureFlags'
import { useCurrencyConversionFlag } from 'featureFlags/flags/currencyConversion' import { useCurrencyConversionFlag } from 'featureFlags/flags/currencyConversion'
import { useForceUniswapXOnFlag } from 'featureFlags/flags/forceUniswapXOn' import { useForceUniswapXOnFlag } from 'featureFlags/flags/forceUniswapXOn'
import { useFotAdjustmentsFlag } from 'featureFlags/flags/fotAdjustments'
import { useMultichainUXFlag } from 'featureFlags/flags/multichainUx' import { useMultichainUXFlag } from 'featureFlags/flags/multichainUx'
import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc' import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc'
import { UniswapXVariant, useUniswapXFlag } from 'featureFlags/flags/uniswapx' import { UniswapXVariant, useUniswapXFlag } from 'featureFlags/flags/uniswapx'
...@@ -242,6 +243,12 @@ export default function FeatureFlagModal() { ...@@ -242,6 +243,12 @@ export default function FeatureFlagModal() {
featureFlag={FeatureFlag.multichainUX} featureFlag={FeatureFlag.multichainUX}
label="Updated Multichain UX" label="Updated Multichain UX"
/> />
<FeatureFlagOption
variant={BaseVariant}
value={useFotAdjustmentsFlag()}
featureFlag={FeatureFlag.fotAdjustedmentsEnabled}
label="Enable fee-on-transfer UI and slippage adjustments"
/>
<FeatureFlagGroup name="Debug"> <FeatureFlagGroup name="Debug">
<FeatureFlagOption <FeatureFlagOption
variant={TraceJsonRpcVariant} variant={TraceJsonRpcVariant}
......
...@@ -5,8 +5,9 @@ import { useWeb3React } from '@web3-react/core' ...@@ -5,8 +5,9 @@ import { useWeb3React } from '@web3-react/core'
import { sendAnalyticsEvent } from 'analytics' import { sendAnalyticsEvent } from 'analytics'
import { LoadingRows } from 'components/Loader/styled' import { LoadingRows } from 'components/Loader/styled'
import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains' import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains'
import { ZERO_PERCENT } from 'constants/misc'
import useNativeCurrency from 'lib/hooks/useNativeCurrency' import useNativeCurrency from 'lib/hooks/useNativeCurrency'
import { InterfaceTrade } from 'state/routing/types' import { ClassicTrade, InterfaceTrade } from 'state/routing/types'
import { getTransactionCount, isClassicTrade } from 'state/routing/utils' import { getTransactionCount, isClassicTrade } from 'state/routing/utils'
import { formatCurrencyAmount, formatNumber, formatPriceImpact, NumberType } from 'utils/formatNumbers' import { formatCurrencyAmount, formatNumber, formatPriceImpact, NumberType } from 'utils/formatNumbers'
...@@ -82,16 +83,20 @@ export function AdvancedSwapDetails({ trade, allowedSlippage, syncing = false }: ...@@ -82,16 +83,20 @@ export function AdvancedSwapDetails({ trade, allowedSlippage, syncing = false }:
</RowBetween> </RowBetween>
)} )}
{isClassicTrade(trade) && ( {isClassicTrade(trade) && (
<RowBetween> <>
<MouseoverTooltip text={<Trans>The impact your trade has on the market price of this pool.</Trans>}> <TokenTaxLineItem trade={trade} type="input" />
<ThemedText.BodySmall color="textSecondary"> <TokenTaxLineItem trade={trade} type="output" />
<Trans>Price Impact</Trans> <RowBetween>
</ThemedText.BodySmall> <MouseoverTooltip text={<Trans>The impact your trade has on the market price of this pool.</Trans>}>
</MouseoverTooltip> <ThemedText.BodySmall color="textSecondary">
<TextWithLoadingPlaceholder syncing={syncing} width={50}> <Trans>Price Impact</Trans>
<ThemedText.BodySmall>{formatPriceImpact(trade.priceImpact)}</ThemedText.BodySmall> </ThemedText.BodySmall>
</TextWithLoadingPlaceholder> </MouseoverTooltip>
</RowBetween> <TextWithLoadingPlaceholder syncing={syncing} width={50}>
<ThemedText.BodySmall>{formatPriceImpact(trade.priceImpact)}</ThemedText.BodySmall>
</TextWithLoadingPlaceholder>
</RowBetween>
</>
)} )}
<RowBetween> <RowBetween>
<RowFixed> <RowFixed>
...@@ -135,7 +140,7 @@ export function AdvancedSwapDetails({ trade, allowedSlippage, syncing = false }: ...@@ -135,7 +140,7 @@ export function AdvancedSwapDetails({ trade, allowedSlippage, syncing = false }:
</RowFixed> </RowFixed>
<TextWithLoadingPlaceholder syncing={syncing} width={65}> <TextWithLoadingPlaceholder syncing={syncing} width={65}>
<ThemedText.BodySmall> <ThemedText.BodySmall>
{`${formatCurrencyAmount(trade.outputAmount, NumberType.SwapTradeAmount)} ${ {`${formatCurrencyAmount(trade.postTaxOutputAmount, NumberType.SwapTradeAmount)} ${
trade.outputAmount.currency.symbol trade.outputAmount.currency.symbol
}`} }`}
</ThemedText.BodySmall> </ThemedText.BodySmall>
...@@ -176,3 +181,26 @@ export function AdvancedSwapDetails({ trade, allowedSlippage, syncing = false }: ...@@ -176,3 +181,26 @@ export function AdvancedSwapDetails({ trade, allowedSlippage, syncing = false }:
</Column> </Column>
) )
} }
function TokenTaxLineItem({ trade, type }: { trade: ClassicTrade; type: 'input' | 'output' }) {
const [currency, percentage] =
type === 'input' ? [trade.inputAmount.currency, trade.inputTax] : [trade.outputAmount.currency, trade.outputTax]
if (percentage.equalTo(ZERO_PERCENT)) return null
return (
<RowBetween>
<MouseoverTooltip
text={
<Trans>
Some tokens take a fee when they are bought or sold, which is set by the token issuer. Uniswap does not
receive any of these fees.
</Trans>
}
>
<ThemedText.BodySmall color="textSecondary">{`${currency.symbol} fee`}</ThemedText.BodySmall>
</MouseoverTooltip>
<ThemedText.BodySmall>{formatPriceImpact(percentage)}</ThemedText.BodySmall>
</RowBetween>
)
}
...@@ -304,6 +304,7 @@ export default function ConfirmSwapModal({ ...@@ -304,6 +304,7 @@ export default function ConfirmSwapModal({
// Swap failed locally and was not broadcast to the blockchain. // Swap failed locally and was not broadcast to the blockchain.
const localSwapFailure = Boolean(swapError) && !didUserReject(swapError) const localSwapFailure = Boolean(swapError) && !didUserReject(swapError)
const swapFailed = localSwapFailure || swapReverted const swapFailed = localSwapFailure || swapReverted
useEffect(() => { useEffect(() => {
// Reset the modal state if the user rejected the swap. // Reset the modal state if the user rejected the swap.
if (swapError && !swapFailed) { if (swapError && !swapFailed) {
......
...@@ -6,7 +6,7 @@ import { useTheme } from 'styled-components' ...@@ -6,7 +6,7 @@ import { useTheme } from 'styled-components'
import { ThemedText } from 'theme' import { ThemedText } from 'theme'
import { formatReviewSwapCurrencyAmount } from 'utils/formatNumbers' import { formatReviewSwapCurrencyAmount } from 'utils/formatNumbers'
export function TradeSummary({ trade }: { trade: Pick<InterfaceTrade, 'inputAmount' | 'outputAmount'> }) { export function TradeSummary({ trade }: { trade: Pick<InterfaceTrade, 'inputAmount' | 'postTaxOutputAmount'> }) {
const theme = useTheme() const theme = useTheme()
return ( return (
<Row gap="sm" justify="center" align="center"> <Row gap="sm" justify="center" align="center">
...@@ -15,9 +15,9 @@ export function TradeSummary({ trade }: { trade: Pick<InterfaceTrade, 'inputAmou ...@@ -15,9 +15,9 @@ export function TradeSummary({ trade }: { trade: Pick<InterfaceTrade, 'inputAmou
{formatReviewSwapCurrencyAmount(trade.inputAmount)} {trade.inputAmount.currency.symbol} {formatReviewSwapCurrencyAmount(trade.inputAmount)} {trade.inputAmount.currency.symbol}
</ThemedText.LabelSmall> </ThemedText.LabelSmall>
<ArrowRight color={theme.textPrimary} size="12px" /> <ArrowRight color={theme.textPrimary} size="12px" />
<CurrencyLogo currency={trade.outputAmount.currency} size="16px" /> <CurrencyLogo currency={trade.postTaxOutputAmount.currency} size="16px" />
<ThemedText.LabelSmall color="textPrimary"> <ThemedText.LabelSmall color="textPrimary">
{formatReviewSwapCurrencyAmount(trade.outputAmount)} {trade.outputAmount.currency.symbol} {formatReviewSwapCurrencyAmount(trade.postTaxOutputAmount)} {trade.postTaxOutputAmount.currency.symbol}
</ThemedText.LabelSmall> </ThemedText.LabelSmall>
</Row> </Row>
) )
......
import userEvent from '@testing-library/user-event' import userEvent from '@testing-library/user-event'
import { TEST_ALLOWED_SLIPPAGE, TEST_TRADE_EXACT_INPUT } from 'test-utils/constants' import {
TEST_ALLOWED_SLIPPAGE,
TEST_TOKEN_1,
TEST_TOKEN_2,
TEST_TRADE_EXACT_INPUT,
TEST_TRADE_FEE_ON_BUY,
TEST_TRADE_FEE_ON_SELL,
} from 'test-utils/constants'
import { act, render, screen } from 'test-utils/render' import { act, render, screen } from 'test-utils/render'
import SwapDetailsDropdown from './SwapDetailsDropdown' import SwapDetailsDropdown from './SwapDetailsDropdown'
...@@ -39,4 +46,42 @@ describe('SwapDetailsDropdown.tsx', () => { ...@@ -39,4 +46,42 @@ describe('SwapDetailsDropdown.tsx', () => {
await act(() => userEvent.click(screen.getByTestId('swap-details-header-row'))) await act(() => userEvent.click(screen.getByTestId('swap-details-header-row')))
expect(screen.getByTestId('advanced-swap-details')).toBeInTheDocument() expect(screen.getByTestId('advanced-swap-details')).toBeInTheDocument()
}) })
it('renders fee on input transfer information', async () => {
render(
<SwapDetailsDropdown
trade={TEST_TRADE_FEE_ON_SELL}
syncing={true}
loading={true}
allowedSlippage={TEST_ALLOWED_SLIPPAGE}
/>
)
await act(() => userEvent.click(screen.getByTestId('swap-details-header-row')))
expect(
screen.getByText(
'Some tokens take a fee when they are bought or sold, which is set by the token issuer. Uniswap does not receive any of these fees.'
)
).toBeInTheDocument()
expect(screen.getByText(`${TEST_TOKEN_1.symbol} fee`)).toBeInTheDocument()
})
it('renders fee on ouput transfer information', async () => {
render(
<SwapDetailsDropdown
trade={TEST_TRADE_FEE_ON_BUY}
syncing={true}
loading={true}
allowedSlippage={TEST_ALLOWED_SLIPPAGE}
/>
)
await act(() => userEvent.click(screen.getByTestId('swap-details-header-row')))
expect(
screen.getByText(
'Some tokens take a fee when they are bought or sold, which is set by the token issuer. Uniswap does not receive any of these fees.'
)
).toBeInTheDocument()
expect(screen.getByText(`${TEST_TOKEN_2.symbol} fee`)).toBeInTheDocument()
})
}) })
import { TEST_ALLOWED_SLIPPAGE, TEST_TRADE_EXACT_INPUT, TEST_TRADE_EXACT_OUTPUT } from 'test-utils/constants' import {
TEST_ALLOWED_SLIPPAGE,
TEST_TOKEN_1,
TEST_TOKEN_2,
TEST_TRADE_EXACT_INPUT,
TEST_TRADE_EXACT_OUTPUT,
TEST_TRADE_FEE_ON_BUY,
TEST_TRADE_FEE_ON_SELL,
} from 'test-utils/constants'
import { render, screen, within } from 'test-utils/render' import { render, screen, within } from 'test-utils/render'
import SwapModalFooter from './SwapModalFooter' import SwapModalFooter from './SwapModalFooter'
...@@ -97,4 +105,62 @@ describe('SwapModalFooter.tsx', () => { ...@@ -97,4 +105,62 @@ describe('SwapModalFooter.tsx', () => {
).toBeInTheDocument() ).toBeInTheDocument()
expect(screen.getByText('The impact your trade has on the market price of this pool.')).toBeInTheDocument() expect(screen.getByText('The impact your trade has on the market price of this pool.')).toBeInTheDocument()
}) })
it('test trade fee on input token transfer', () => {
render(
<SwapModalFooter
trade={TEST_TRADE_FEE_ON_SELL}
allowedSlippage={TEST_ALLOWED_SLIPPAGE}
swapResult={undefined}
onConfirm={jest.fn()}
swapErrorMessage={undefined}
disabledConfirm={false}
fiatValueInput={{
data: undefined,
isLoading: false,
}}
fiatValueOutput={{
data: undefined,
isLoading: false,
}}
showAcceptChanges={true}
onAcceptChanges={jest.fn()}
/>
)
expect(
screen.getByText(
'Some tokens take a fee when they are bought or sold, which is set by the token issuer. Uniswap does not receive any of these fees.'
)
).toBeInTheDocument()
expect(screen.getByText(`${TEST_TOKEN_1.symbol} fee`)).toBeInTheDocument()
})
it('test trade fee on output token transfer', () => {
render(
<SwapModalFooter
trade={TEST_TRADE_FEE_ON_BUY}
allowedSlippage={TEST_ALLOWED_SLIPPAGE}
swapResult={undefined}
onConfirm={jest.fn()}
swapErrorMessage={undefined}
disabledConfirm={false}
fiatValueInput={{
data: undefined,
isLoading: false,
}}
fiatValueOutput={{
data: undefined,
isLoading: false,
}}
showAcceptChanges={true}
onAcceptChanges={jest.fn()}
/>
)
expect(
screen.getByText(
'Some tokens take a fee when they are bought or sold, which is set by the token issuer. Uniswap does not receive any of these fees.'
)
).toBeInTheDocument()
expect(screen.getByText(`${TEST_TOKEN_2.symbol} fee`)).toBeInTheDocument()
})
}) })
import { Plural, Trans } from '@lingui/macro' import { Plural, t, Trans } from '@lingui/macro'
import { BrowserEvent, InterfaceElementName, SwapEventName } from '@uniswap/analytics-events' import { BrowserEvent, InterfaceElementName, SwapEventName } from '@uniswap/analytics-events'
import { Percent, TradeType } from '@uniswap/sdk-core' import { Percent, TradeType } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import { TraceEvent } from 'analytics' import { TraceEvent } from 'analytics'
import Column from 'components/Column' import Column from 'components/Column'
import { MouseoverTooltip, TooltipSize } from 'components/Tooltip' import { MouseoverTooltip, TooltipSize } from 'components/Tooltip'
import { ZERO_PERCENT } from 'constants/misc'
import { SwapResult } from 'hooks/useSwapCallback' import { SwapResult } from 'hooks/useSwapCallback'
import useTransactionDeadline from 'hooks/useTransactionDeadline' import useTransactionDeadline from 'hooks/useTransactionDeadline'
import useNativeCurrency from 'lib/hooks/useNativeCurrency' import useNativeCurrency from 'lib/hooks/useNativeCurrency'
import { ReactNode } from 'react' import { ReactNode } from 'react'
import { AlertTriangle } from 'react-feather' import { AlertTriangle } from 'react-feather'
import { InterfaceTrade, RouterPreference } from 'state/routing/types' import { ClassicTrade, InterfaceTrade, RouterPreference } from 'state/routing/types'
import { getTransactionCount, isClassicTrade } from 'state/routing/utils' import { getTransactionCount, isClassicTrade } from 'state/routing/utils'
import { useRouterPreference, useUserSlippageTolerance } from 'state/user/hooks' import { useRouterPreference, useUserSlippageTolerance } from 'state/user/hooks'
import styled, { useTheme } from 'styled-components' import styled, { DefaultTheme, useTheme } from 'styled-components'
import { ThemedText } from 'theme' import { ThemedText } from 'theme'
import { formatNumber, formatPriceImpact, NumberType } from 'utils/formatNumbers' import { formatNumber, formatPriceImpact, NumberType } from 'utils/formatNumbers'
import { formatTransactionAmount, priceToPreciseFloat } from 'utils/formatNumbers' import { formatTransactionAmount, priceToPreciseFloat } from 'utils/formatNumbers'
import getRoutingDiagramEntries from 'utils/getRoutingDiagramEntries' import getRoutingDiagramEntries from 'utils/getRoutingDiagramEntries'
import { formatSwapButtonClickEventProperties } from 'utils/loggingFormatters' import { formatSwapButtonClickEventProperties } from 'utils/loggingFormatters'
import { getPriceImpactWarning } from 'utils/prices' import { getPriceImpactColor } from 'utils/prices'
import { ButtonError, SmallButtonPrimary } from '../Button' import { ButtonError, SmallButtonPrimary } from '../Button'
import Row, { AutoRow, RowBetween, RowFixed } from '../Row' import Row, { AutoRow, RowBetween, RowFixed } from '../Row'
...@@ -41,9 +42,10 @@ const ConfirmButton = styled(ButtonError)` ...@@ -41,9 +42,10 @@ const ConfirmButton = styled(ButtonError)`
margin-top: 10px; margin-top: 10px;
` `
const DetailRowValue = styled(ThemedText.BodySmall)` const DetailRowValue = styled(ThemedText.BodySmall)<{ warningColor?: keyof DefaultTheme }>`
text-align: right; text-align: right;
overflow-wrap: break-word; overflow-wrap: break-word;
${({ warningColor, theme }) => warningColor && `color: ${theme[warningColor]};`};
` `
export default function SwapModalFooter({ export default function SwapModalFooter({
...@@ -112,18 +114,22 @@ export default function SwapModalFooter({ ...@@ -112,18 +114,22 @@ export default function SwapModalFooter({
</Row> </Row>
</ThemedText.BodySmall> </ThemedText.BodySmall>
{isClassicTrade(trade) && ( {isClassicTrade(trade) && (
<ThemedText.BodySmall> <>
<Row align="flex-start" justify="space-between" gap="sm"> <TokenTaxLineItem trade={trade} type="input" />
<MouseoverTooltip text={<Trans>The impact your trade has on the market price of this pool.</Trans>}> <TokenTaxLineItem trade={trade} type="output" />
<Label cursor="help"> <ThemedText.BodySmall>
<Trans>Price impact</Trans> <Row align="flex-start" justify="space-between" gap="sm">
</Label> <MouseoverTooltip text={<Trans>The impact your trade has on the market price of this pool.</Trans>}>
</MouseoverTooltip> <Label cursor="help">
<DetailRowValue color={getPriceImpactWarning(trade.priceImpact)}> <Trans>Price impact</Trans>
{trade.priceImpact ? formatPriceImpact(trade.priceImpact) : '-'} </Label>
</DetailRowValue> </MouseoverTooltip>
</Row> <DetailRowValue warningColor={getPriceImpactColor(trade.priceImpact)}>
</ThemedText.BodySmall> {trade.priceImpact ? formatPriceImpact(trade.priceImpact) : '-'}
</DetailRowValue>
</Row>
</ThemedText.BodySmall>
</>
)} )}
<ThemedText.BodySmall> <ThemedText.BodySmall>
<Row align="flex-start" justify="space-between" gap="sm"> <Row align="flex-start" justify="space-between" gap="sm">
...@@ -209,3 +215,28 @@ export default function SwapModalFooter({ ...@@ -209,3 +215,28 @@ export default function SwapModalFooter({
</> </>
) )
} }
function TokenTaxLineItem({ trade, type }: { trade: ClassicTrade; type: 'input' | 'output' }) {
const [currency, percentage] =
type === 'input' ? [trade.inputAmount.currency, trade.inputTax] : [trade.outputAmount.currency, trade.outputTax]
if (percentage.equalTo(ZERO_PERCENT)) return null
return (
<ThemedText.BodySmall>
<Row align="flex-start" justify="space-between" gap="sm">
<MouseoverTooltip
text={
<Trans>
Some tokens take a fee when they are bought or sold, which is set by the token issuer. Uniswap does not
receive any of these fees.
</Trans>
}
>
<Label cursor="help">{t`${currency.symbol} fee`}</Label>
</MouseoverTooltip>
<DetailRowValue warningColor={getPriceImpactColor(percentage)}>{formatPriceImpact(percentage)}</DetailRowValue>
</Row>
</ThemedText.BodySmall>
)
}
...@@ -27,7 +27,7 @@ export default function SwapModalHeader({ ...@@ -27,7 +27,7 @@ export default function SwapModalHeader({
allowedSlippage: Percent allowedSlippage: Percent
}) { }) {
const fiatValueInput = useUSDPrice(trade.inputAmount) const fiatValueInput = useUSDPrice(trade.inputAmount)
const fiatValueOutput = useUSDPrice(trade.outputAmount) const fiatValueOutput = useUSDPrice(trade.postTaxOutputAmount)
return ( return (
<HeaderContainer gap="sm"> <HeaderContainer gap="sm">
...@@ -42,7 +42,7 @@ export default function SwapModalHeader({ ...@@ -42,7 +42,7 @@ export default function SwapModalHeader({
<SwapModalHeaderAmount <SwapModalHeaderAmount
field={Field.OUTPUT} field={Field.OUTPUT}
label={<Trans>You receive</Trans>} label={<Trans>You receive</Trans>}
amount={trade.outputAmount} amount={trade.postTaxOutputAmount}
currency={trade.outputAmount.currency} currency={trade.outputAmount.currency}
usdAmount={fiatValueOutput.data} usdAmount={fiatValueOutput.data}
tooltipText={ tooltipText={
......
...@@ -141,7 +141,7 @@ exports[`SwapModalFooter.tsx matches base snapshot, test trade exact input 1`] = ...@@ -141,7 +141,7 @@ exports[`SwapModalFooter.tsx matches base snapshot, test trade exact input 1`] =
</div> </div>
</div> </div>
<div <div
class="c6 css-zhpkf8" class="c2 c6 css-zhpkf8"
> >
105566.373% 105566.373%
</div> </div>
......
import { ChainId, Currency, Percent } from '@uniswap/sdk-core'
import { ZERO_PERCENT } from './misc'
interface TokenTaxMetadata {
buyFee?: Percent
sellFee?: Percent
}
const CHAIN_TOKEN_TAX_MAP: { [chainId in number]?: { [address in string]?: TokenTaxMetadata } } = {
[ChainId.MAINNET]: {
// BULLET
'0x8ef32a03784c8fd63bbf027251b9620865bd54b6': {
buyFee: new Percent(5, 100), // 5%
sellFee: new Percent(5, 100), // 5%
},
// X
'0xabec00542d141bddf58649bfe860c6449807237c': {
buyFee: new Percent(1, 100), // 1%
sellFee: new Percent(1, 100), // 1%
},
// HarryPotterObamaKnuckles9Inu
'0x2577944fd4b556a99cc5aa0f072e4b944aa088df': {
buyFee: new Percent(1, 100), // 1%
sellFee: new Percent(11, 1000), // 1.1%
},
// QWN
'0xb354b5da5ea39dadb1cea8140bf242eb24b1821a': {
buyFee: new Percent(5, 100), // 5%
sellFee: new Percent(5, 100), // 5%
},
// HarryPotterObamaPacMan8Inu
'0x07e0edf8ce600fb51d44f51e3348d77d67f298ae': {
buyFee: new Percent(2, 100), // 2%
sellFee: new Percent(2, 100), // 2%
},
// KUKU
'0x27206f5a9afd0c51da95f20972885545d3b33647': {
buyFee: new Percent(2, 100), // 2%
sellFee: new Percent(21, 1000), // 2.1%
},
// AIMBOT
'0x0c48250eb1f29491f1efbeec0261eb556f0973c7': {
buyFee: new Percent(5, 100), // 5%
sellFee: new Percent(5, 100), // 5%
},
// PYUSD
'0xe0a8ed732658832fac18141aa5ad3542e2eb503b': {
buyFee: new Percent(1, 100), // 1%
sellFee: new Percent(13, 1000), // 1.3%
},
// ND4
'0x4f849c55180ddf8185c5cc495ed58c3aea9c9a28': {
buyFee: new Percent(1, 100), // 1%
sellFee: new Percent(1, 100), // 1%
},
// COCO
'0xcb50350ab555ed5d56265e096288536e8cac41eb': {
buyFee: new Percent(2, 100), // 2%
sellFee: new Percent(26, 1000), // 2.6%
},
},
}
export function getInputTax(currency: Currency): Percent {
if (currency.isNative) return ZERO_PERCENT
return CHAIN_TOKEN_TAX_MAP[currency.chainId]?.[currency.address.toLowerCase()]?.sellFee ?? ZERO_PERCENT
}
export function getOutputTax(currency: Currency): Percent {
if (currency.isNative) return ZERO_PERCENT
return CHAIN_TOKEN_TAX_MAP[currency.chainId]?.[currency.address.toLowerCase()]?.buyFee ?? ZERO_PERCENT
}
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
export function useFotAdjustmentsFlag(): BaseVariant {
return useBaseFlag(FeatureFlag.fotAdjustedmentsEnabled)
}
export function useFotAdjustmentsEnabled(): boolean {
return useFotAdjustmentsFlag() === BaseVariant.Enabled
}
...@@ -16,6 +16,7 @@ export enum FeatureFlag { ...@@ -16,6 +16,7 @@ export enum FeatureFlag {
uniswapXEthOutputEnabled = 'uniswapx_eth_output_enabled', uniswapXEthOutputEnabled = 'uniswapx_eth_output_enabled',
multichainUX = 'multichain_ux', multichainUX = 'multichain_ux',
currencyConversion = 'currency_conversion', currencyConversion = 'currency_conversion',
fotAdjustedmentsEnabled = 'fot_adjustments_enabled',
} }
interface FeatureFlagsContextType { interface FeatureFlagsContextType {
......
...@@ -67,13 +67,13 @@ export function useSwapCallback( ...@@ -67,13 +67,13 @@ export function useSwapCallback(
? { ? {
tradeType: TradeType.EXACT_INPUT, tradeType: TradeType.EXACT_INPUT,
inputCurrencyAmountRaw: trade.inputAmount.quotient.toString(), inputCurrencyAmountRaw: trade.inputAmount.quotient.toString(),
expectedOutputCurrencyAmountRaw: trade.outputAmount.quotient.toString(), expectedOutputCurrencyAmountRaw: trade.postTaxOutputAmount.quotient.toString(),
minimumOutputCurrencyAmountRaw: trade.minimumAmountOut(allowedSlippage).quotient.toString(), minimumOutputCurrencyAmountRaw: trade.minimumAmountOut(allowedSlippage).quotient.toString(),
} }
: { : {
tradeType: TradeType.EXACT_OUTPUT, tradeType: TradeType.EXACT_OUTPUT,
maximumInputCurrencyAmountRaw: trade.maximumAmountIn(allowedSlippage).quotient.toString(), maximumInputCurrencyAmountRaw: trade.maximumAmountIn(allowedSlippage).quotient.toString(),
outputCurrencyAmountRaw: trade.outputAmount.quotient.toString(), outputCurrencyAmountRaw: trade.postTaxOutputAmount.quotient.toString(),
expectedInputCurrencyAmountRaw: trade.inputAmount.quotient.toString(), expectedInputCurrencyAmountRaw: trade.inputAmount.quotient.toString(),
}), }),
} }
......
...@@ -64,8 +64,13 @@ export function useUniversalRouterSwapCallback( ...@@ -64,8 +64,13 @@ export function useUniversalRouterSwapCallback(
if (chainId !== connectedChainId) throw new WrongChainError() if (chainId !== connectedChainId) throw new WrongChainError()
setTraceData('slippageTolerance', options.slippageTolerance.toFixed(2)) setTraceData('slippageTolerance', options.slippageTolerance.toFixed(2))
// universal-router-sdk reconstructs V2Trade objects, so rather than updating the trade amounts to account for tax, we adjust the slippage tolerance as a workaround
// TODO(WEB-2725): update universal-router-sdk to not reconstruct trades
const taxAdjustedSlippageTolerance = options.slippageTolerance.add(trade.totalTaxRate)
const { calldata: data, value } = SwapRouter.swapERC20CallParameters(trade, { const { calldata: data, value } = SwapRouter.swapERC20CallParameters(trade, {
slippageTolerance: options.slippageTolerance, slippageTolerance: taxAdjustedSlippageTolerance,
deadlineOrPreviousBlockhash: options.deadline?.toString(), deadlineOrPreviousBlockhash: options.deadline?.toString(),
inputTokenPermit: options.permit, inputTokenPermit: options.permit,
fee: options.feeOptions, fee: options.feeOptions,
......
import { SkipToken, skipToken } from '@reduxjs/toolkit/query/react' import { SkipToken, skipToken } from '@reduxjs/toolkit/query/react'
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core' import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
import { useForceUniswapXOn } from 'featureFlags/flags/forceUniswapXOn' import { useForceUniswapXOn } from 'featureFlags/flags/forceUniswapXOn'
import { useFotAdjustmentsEnabled } from 'featureFlags/flags/fotAdjustments'
import { useUniswapXEnabled } from 'featureFlags/flags/uniswapx' import { useUniswapXEnabled } from 'featureFlags/flags/uniswapx'
import { useUniswapXEthOutputEnabled } from 'featureFlags/flags/uniswapXEthOutput' import { useUniswapXEthOutputEnabled } from 'featureFlags/flags/uniswapXEthOutput'
import { useUniswapXSyntheticQuoteEnabled } from 'featureFlags/flags/uniswapXUseSyntheticQuote' import { useUniswapXSyntheticQuoteEnabled } from 'featureFlags/flags/uniswapXUseSyntheticQuote'
...@@ -34,6 +35,7 @@ export function useRoutingAPIArguments({ ...@@ -34,6 +35,7 @@ export function useRoutingAPIArguments({
const forceUniswapXOn = useForceUniswapXOn() const forceUniswapXOn = useForceUniswapXOn()
const userDisabledUniswapX = useUserDisabledUniswapX() const userDisabledUniswapX = useUserDisabledUniswapX()
const uniswapXEthOutputEnabled = useUniswapXEthOutputEnabled() const uniswapXEthOutputEnabled = useUniswapXEthOutputEnabled()
const fotAdjustmentsEnabled = useFotAdjustmentsEnabled()
return useMemo( return useMemo(
() => () =>
...@@ -58,6 +60,7 @@ export function useRoutingAPIArguments({ ...@@ -58,6 +60,7 @@ export function useRoutingAPIArguments({
forceUniswapXOn, forceUniswapXOn,
userDisabledUniswapX, userDisabledUniswapX,
uniswapXEthOutputEnabled, uniswapXEthOutputEnabled,
fotAdjustmentsEnabled,
}, },
[ [
account, account,
...@@ -71,6 +74,7 @@ export function useRoutingAPIArguments({ ...@@ -71,6 +74,7 @@ export function useRoutingAPIArguments({
forceUniswapXOn, forceUniswapXOn,
userDisabledUniswapX, userDisabledUniswapX,
uniswapXEthOutputEnabled, uniswapXEthOutputEnabled,
fotAdjustmentsEnabled,
] ]
) )
} }
...@@ -294,7 +294,7 @@ export function Swap({ ...@@ -294,7 +294,7 @@ export function Swap({
} }
: { : {
[Field.INPUT]: independentField === Field.INPUT ? parsedAmount : trade?.inputAmount, [Field.INPUT]: independentField === Field.INPUT ? parsedAmount : trade?.inputAmount,
[Field.OUTPUT]: independentField === Field.OUTPUT ? parsedAmount : trade?.outputAmount, [Field.OUTPUT]: independentField === Field.OUTPUT ? parsedAmount : trade?.postTaxOutputAmount,
}, },
[independentField, parsedAmount, showWrap, trade] [independentField, parsedAmount, showWrap, trade]
) )
...@@ -314,13 +314,17 @@ export function Swap({ ...@@ -314,13 +314,17 @@ export function Swap({
) )
const fiatValueTradeInput = useUSDPrice(trade?.inputAmount) const fiatValueTradeInput = useUSDPrice(trade?.inputAmount)
const fiatValueTradeOutput = useUSDPrice(trade?.outputAmount) const fiatValueTradeOutput = useUSDPrice(trade?.postTaxOutputAmount)
const stablecoinPriceImpact = useMemo( const preTaxFiatValueTradeOutput = useUSDPrice(trade?.outputAmount)
const [stablecoinPriceImpact, preTaxStablecoinPriceImpact] = useMemo(
() => () =>
routeIsSyncing || !isClassicTrade(trade) routeIsSyncing || !isClassicTrade(trade)
? undefined ? [undefined, undefined]
: computeFiatValuePriceImpact(fiatValueTradeInput.data, fiatValueTradeOutput.data), : [
[fiatValueTradeInput, fiatValueTradeOutput, routeIsSyncing, trade] computeFiatValuePriceImpact(fiatValueTradeInput.data, fiatValueTradeOutput.data),
computeFiatValuePriceImpact(fiatValueTradeInput.data, preTaxFiatValueTradeOutput.data),
],
[fiatValueTradeInput, fiatValueTradeOutput, preTaxFiatValueTradeOutput, routeIsSyncing, trade]
) )
const { onSwitchTokens, onCurrencySelection, onUserInput, onChangeRecipient } = useSwapActionHandlers(dispatch) const { onSwitchTokens, onCurrencySelection, onUserInput, onChangeRecipient } = useSwapActionHandlers(dispatch)
...@@ -417,7 +421,7 @@ export function Swap({ ...@@ -417,7 +421,7 @@ export function Swap({
if (!swapCallback) { if (!swapCallback) {
return return
} }
if (stablecoinPriceImpact && !confirmPriceImpactWithoutFee(stablecoinPriceImpact)) { if (preTaxStablecoinPriceImpact && !confirmPriceImpactWithoutFee(preTaxStablecoinPriceImpact)) {
return return
} }
setSwapState((currentState) => ({ setSwapState((currentState) => ({
...@@ -440,7 +444,7 @@ export function Swap({ ...@@ -440,7 +444,7 @@ export function Swap({
swapResult: undefined, swapResult: undefined,
})) }))
}) })
}, [swapCallback, stablecoinPriceImpact]) }, [swapCallback, preTaxStablecoinPriceImpact])
const handleOnWrap = useCallback(async () => { const handleOnWrap = useCallback(async () => {
if (!onWrap) return if (!onWrap) return
...@@ -476,9 +480,9 @@ export function Swap({ ...@@ -476,9 +480,9 @@ export function Swap({
} }
const marketPriceImpact = trade?.priceImpact ? computeRealizedPriceImpact(trade) : undefined const marketPriceImpact = trade?.priceImpact ? computeRealizedPriceImpact(trade) : undefined
const largerPriceImpact = largerPercentValue(marketPriceImpact, stablecoinPriceImpact) const largerPriceImpact = largerPercentValue(marketPriceImpact, preTaxStablecoinPriceImpact)
return { priceImpactSeverity: warningSeverity(largerPriceImpact), largerPriceImpact } return { priceImpactSeverity: warningSeverity(largerPriceImpact), largerPriceImpact }
}, [stablecoinPriceImpact, trade]) }, [preTaxStablecoinPriceImpact, trade])
const handleConfirmDismiss = useCallback(() => { const handleConfirmDismiss = useCallback(() => {
setSwapState((currentState) => ({ ...currentState, showConfirm: false })) setSwapState((currentState) => ({ ...currentState, showConfirm: false }))
......
import { MixedRouteSDK, Protocol, Trade } from '@uniswap/router-sdk' import { MixedRouteSDK, ONE, Protocol, Trade } from '@uniswap/router-sdk'
import { ChainId, Currency, CurrencyAmount, Percent, Token, TradeType } from '@uniswap/sdk-core' import { ChainId, Currency, CurrencyAmount, Fraction, Percent, Token, TradeType } from '@uniswap/sdk-core'
import { DutchOrderInfo, DutchOrderInfoJSON, DutchOrderTrade as IDutchOrderTrade } from '@uniswap/uniswapx-sdk' import { DutchOrderInfo, DutchOrderInfoJSON, DutchOrderTrade as IDutchOrderTrade } from '@uniswap/uniswapx-sdk'
import { Route as V2Route } from '@uniswap/v2-sdk' import { Route as V2Route } from '@uniswap/v2-sdk'
import { Route as V3Route } from '@uniswap/v3-sdk' import { Route as V3Route } from '@uniswap/v3-sdk'
...@@ -47,6 +47,7 @@ export interface GetQuoteArgs { ...@@ -47,6 +47,7 @@ export interface GetQuoteArgs {
uniswapXEthOutputEnabled: boolean uniswapXEthOutputEnabled: boolean
forceUniswapXOn: boolean forceUniswapXOn: boolean
userDisabledUniswapX: boolean userDisabledUniswapX: boolean
fotAdjustmentsEnabled: boolean
} }
// from https://github.com/Uniswap/routing-api/blob/main/lib/handlers/schema.ts // from https://github.com/Uniswap/routing-api/blob/main/lib/handlers/schema.ts
...@@ -146,6 +147,8 @@ export class ClassicTrade extends Trade<Currency, Currency, TradeType> { ...@@ -146,6 +147,8 @@ export class ClassicTrade extends Trade<Currency, Currency, TradeType> {
isUniswapXBetter: boolean | undefined isUniswapXBetter: boolean | undefined
requestId: string | undefined requestId: string | undefined
quoteMethod: QuoteMethod quoteMethod: QuoteMethod
inputTax: Percent
outputTax: Percent
constructor({ constructor({
gasUseEstimateUSD, gasUseEstimateUSD,
...@@ -154,6 +157,8 @@ export class ClassicTrade extends Trade<Currency, Currency, TradeType> { ...@@ -154,6 +157,8 @@ export class ClassicTrade extends Trade<Currency, Currency, TradeType> {
requestId, requestId,
quoteMethod, quoteMethod,
approveInfo, approveInfo,
inputTax,
outputTax,
...routes ...routes
}: { }: {
gasUseEstimateUSD?: number gasUseEstimateUSD?: number
...@@ -163,6 +168,8 @@ export class ClassicTrade extends Trade<Currency, Currency, TradeType> { ...@@ -163,6 +168,8 @@ export class ClassicTrade extends Trade<Currency, Currency, TradeType> {
requestId?: string requestId?: string
quoteMethod: QuoteMethod quoteMethod: QuoteMethod
approveInfo: ApproveInfo approveInfo: ApproveInfo
inputTax: Percent
outputTax: Percent
v2Routes: { v2Routes: {
routev2: V2Route<Currency, Currency> routev2: V2Route<Currency, Currency>
inputAmount: CurrencyAmount<Currency> inputAmount: CurrencyAmount<Currency>
...@@ -187,6 +194,26 @@ export class ClassicTrade extends Trade<Currency, Currency, TradeType> { ...@@ -187,6 +194,26 @@ export class ClassicTrade extends Trade<Currency, Currency, TradeType> {
this.requestId = requestId this.requestId = requestId
this.quoteMethod = quoteMethod this.quoteMethod = quoteMethod
this.approveInfo = approveInfo this.approveInfo = approveInfo
this.inputTax = inputTax
this.outputTax = outputTax
}
public get totalTaxRate(): Percent {
return this.inputTax.add(this.outputTax)
}
public get postTaxOutputAmount() {
// Ideally we should calculate the final output amount by ammending the inputAmount based on the input tax and then applying the output tax,
// but this isn't currently possible because V2Trade reconstructs the total inputAmount based on the swap routes
// TODO(WEB-2761): Amend V2Trade objects in the v2-sdk to have a separate field for post-input tax routes
return this.outputAmount.multiply(new Fraction(ONE).subtract(this.totalTaxRate))
}
public minimumAmountOut(slippageTolerance: Percent, amountOut = this.outputAmount): CurrencyAmount<Currency> {
// Since universal-router-sdk reconstructs V2Trade objects, overriding this method does not actually change the minimumAmountOut that gets submitted on-chain
// Our current workaround is to add tax rate to slippage tolerance before we submit the trade to universal-router-sdk in useUniversalRouter.ts
// So the purpose of this override is so the UI displays the same minimum amount out as what is submitted on-chain
return super.minimumAmountOut(slippageTolerance.add(this.totalTaxRate), amountOut)
} }
// gas estimate for maybe approve + swap // gas estimate for maybe approve + swap
...@@ -259,6 +286,11 @@ export class DutchOrderTrade extends IDutchOrderTrade<Currency, Currency, TradeT ...@@ -259,6 +286,11 @@ export class DutchOrderTrade extends IDutchOrderTrade<Currency, Currency, TradeT
return 0 return 0
} }
/** For UniswapX, handling token taxes in the output amount is outsourced to quoters */
public get postTaxOutputAmount() {
return this.outputAmount
}
} }
export type InterfaceTrade = ClassicTrade | DutchOrderTrade export type InterfaceTrade = ClassicTrade | DutchOrderTrade
......
...@@ -6,7 +6,9 @@ import { DutchOrderInfo, DutchOrderInfoJSON } from '@uniswap/uniswapx-sdk' ...@@ -6,7 +6,9 @@ import { DutchOrderInfo, DutchOrderInfoJSON } from '@uniswap/uniswapx-sdk'
import { Pair, Route as V2Route } from '@uniswap/v2-sdk' import { Pair, Route as V2Route } from '@uniswap/v2-sdk'
import { FeeAmount, Pool, Route as V3Route } from '@uniswap/v3-sdk' import { FeeAmount, Pool, Route as V3Route } from '@uniswap/v3-sdk'
import { asSupportedChain } from 'constants/chains' import { asSupportedChain } from 'constants/chains'
import { ZERO_PERCENT } from 'constants/misc'
import { RPC_PROVIDERS } from 'constants/providers' import { RPC_PROVIDERS } from 'constants/providers'
import { getInputTax, getOutputTax } from 'constants/tax'
import { isAvalanche, isBsc, isMatic, nativeOnChain } from 'constants/tokens' import { isAvalanche, isBsc, isMatic, nativeOnChain } from 'constants/tokens'
import { toSlippagePercent } from 'utils/slippage' import { toSlippagePercent } from 'utils/slippage'
...@@ -213,6 +215,9 @@ export async function transformRoutesToTrade( ...@@ -213,6 +215,9 @@ export async function transformRoutesToTrade(
const approveInfo = await getApproveInfo(account, currencyIn, amount, usdCostPerGas) const approveInfo = await getApproveInfo(account, currencyIn, amount, usdCostPerGas)
const inputTax = args.fotAdjustmentsEnabled ? getInputTax(currencyIn) : ZERO_PERCENT
const outputTax = args.fotAdjustmentsEnabled ? getOutputTax(currencyOut) : ZERO_PERCENT
const classicTrade = new ClassicTrade({ const classicTrade = new ClassicTrade({
v2Routes: v2Routes:
routes routes
...@@ -247,6 +252,8 @@ export async function transformRoutesToTrade( ...@@ -247,6 +252,8 @@ export async function transformRoutesToTrade(
isUniswapXBetter, isUniswapXBetter,
requestId: data.quote.requestId, requestId: data.quote.requestId,
quoteMethod, quoteMethod,
inputTax,
outputTax,
}) })
// During the opt-in period, only return UniswapX quotes if the user has turned on the setting, // During the opt-in period, only return UniswapX quotes if the user has turned on the setting,
......
import { ChainId, CurrencyAmount, Percent, Token, TradeType } from '@uniswap/sdk-core' import { ChainId, CurrencyAmount, Percent, Token, TradeType } from '@uniswap/sdk-core'
import { V3Route } from '@uniswap/smart-order-router' import { V3Route } from '@uniswap/smart-order-router'
import { FeeAmount, Pool } from '@uniswap/v3-sdk' import { FeeAmount, Pool } from '@uniswap/v3-sdk'
import { ZERO_PERCENT } from 'constants/misc'
import { nativeOnChain } from 'constants/tokens' import { nativeOnChain } from 'constants/tokens'
import { BigNumber } from 'ethers/lib/ethers' import { BigNumber } from 'ethers/lib/ethers'
import JSBI from 'jsbi' import JSBI from 'jsbi'
...@@ -46,6 +47,8 @@ export const TEST_TRADE_EXACT_INPUT = new ClassicTrade({ ...@@ -46,6 +47,8 @@ export const TEST_TRADE_EXACT_INPUT = new ClassicTrade({
gasUseEstimateUSD: 1.0, gasUseEstimateUSD: 1.0,
approveInfo: { needsApprove: false }, approveInfo: { needsApprove: false },
quoteMethod: QuoteMethod.CLIENT_SIDE, quoteMethod: QuoteMethod.CLIENT_SIDE,
inputTax: ZERO_PERCENT,
outputTax: ZERO_PERCENT,
}) })
export const TEST_TRADE_EXACT_INPUT_API = new ClassicTrade({ export const TEST_TRADE_EXACT_INPUT_API = new ClassicTrade({
...@@ -61,6 +64,8 @@ export const TEST_TRADE_EXACT_INPUT_API = new ClassicTrade({ ...@@ -61,6 +64,8 @@ export const TEST_TRADE_EXACT_INPUT_API = new ClassicTrade({
gasUseEstimateUSD: 1.0, gasUseEstimateUSD: 1.0,
approveInfo: { needsApprove: false }, approveInfo: { needsApprove: false },
quoteMethod: QuoteMethod.ROUTING_API, quoteMethod: QuoteMethod.ROUTING_API,
inputTax: ZERO_PERCENT,
outputTax: ZERO_PERCENT,
}) })
export const TEST_TRADE_EXACT_OUTPUT = new ClassicTrade({ export const TEST_TRADE_EXACT_OUTPUT = new ClassicTrade({
...@@ -75,6 +80,8 @@ export const TEST_TRADE_EXACT_OUTPUT = new ClassicTrade({ ...@@ -75,6 +80,8 @@ export const TEST_TRADE_EXACT_OUTPUT = new ClassicTrade({
tradeType: TradeType.EXACT_OUTPUT, tradeType: TradeType.EXACT_OUTPUT,
quoteMethod: QuoteMethod.CLIENT_SIDE, quoteMethod: QuoteMethod.CLIENT_SIDE,
approveInfo: { needsApprove: false }, approveInfo: { needsApprove: false },
inputTax: ZERO_PERCENT,
outputTax: ZERO_PERCENT,
}) })
export const TEST_ALLOWED_SLIPPAGE = new Percent(2, 100) export const TEST_ALLOWED_SLIPPAGE = new Percent(2, 100)
...@@ -116,3 +123,37 @@ export const TEST_DUTCH_TRADE_ETH_INPUT = new DutchOrderTrade({ ...@@ -116,3 +123,37 @@ export const TEST_DUTCH_TRADE_ETH_INPUT = new DutchOrderTrade({
deadlineBufferSecs: 30, deadlineBufferSecs: 30,
slippageTolerance: new Percent(5, 100), slippageTolerance: new Percent(5, 100),
}) })
export const TEST_TRADE_FEE_ON_SELL = new ClassicTrade({
v3Routes: [
{
routev3: new V3Route([TEST_POOL_12], TEST_TOKEN_1, TEST_TOKEN_2),
inputAmount: toCurrencyAmount(TEST_TOKEN_1, 1000),
outputAmount: toCurrencyAmount(TEST_TOKEN_2, 1000),
},
],
v2Routes: [],
tradeType: TradeType.EXACT_INPUT,
gasUseEstimateUSD: 1.0,
approveInfo: { needsApprove: false },
quoteMethod: QuoteMethod.ROUTING_API,
inputTax: new Percent(3, 100),
outputTax: ZERO_PERCENT,
})
export const TEST_TRADE_FEE_ON_BUY = new ClassicTrade({
v3Routes: [
{
routev3: new V3Route([TEST_POOL_12], TEST_TOKEN_1, TEST_TOKEN_2),
inputAmount: toCurrencyAmount(TEST_TOKEN_1, 1000),
outputAmount: toCurrencyAmount(TEST_TOKEN_2, 1000),
},
],
v2Routes: [],
tradeType: TradeType.EXACT_INPUT,
gasUseEstimateUSD: 1.0,
approveInfo: { needsApprove: false },
quoteMethod: QuoteMethod.ROUTING_API,
inputTax: ZERO_PERCENT,
outputTax: new Percent(3, 100),
})
...@@ -3,6 +3,7 @@ import { Currency, CurrencyAmount, Fraction, Percent, TradeType } from '@uniswap ...@@ -3,6 +3,7 @@ import { Currency, CurrencyAmount, Fraction, Percent, TradeType } from '@uniswap
import { Pair } from '@uniswap/v2-sdk' import { Pair } from '@uniswap/v2-sdk'
import { FeeAmount } from '@uniswap/v3-sdk' import { FeeAmount } from '@uniswap/v3-sdk'
import JSBI from 'jsbi' import JSBI from 'jsbi'
import { DefaultTheme } from 'styled-components'
import { import {
ALLOWED_PRICE_IMPACT_HIGH, ALLOWED_PRICE_IMPACT_HIGH,
...@@ -104,3 +105,14 @@ export function getPriceImpactWarning(priceImpact: Percent): 'warning' | 'error' ...@@ -104,3 +105,14 @@ export function getPriceImpactWarning(priceImpact: Percent): 'warning' | 'error'
if (priceImpact.greaterThan(ALLOWED_PRICE_IMPACT_MEDIUM)) return 'warning' if (priceImpact.greaterThan(ALLOWED_PRICE_IMPACT_MEDIUM)) return 'warning'
return return
} }
export function getPriceImpactColor(priceImpact: Percent): keyof DefaultTheme | undefined {
switch (getPriceImpactWarning(priceImpact)) {
case 'error':
return 'accentFailure'
case 'warning':
return 'accentWarning'
default:
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