Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e8d3e8d2f | ||
|
|
d674f8500f | ||
|
|
686f24b98e | ||
|
|
40d8da42a5 | ||
|
|
2151ffbac7 |
1
CODEOWNERS
Normal file
1
CODEOWNERS
Normal file
@ -0,0 +1 @@
|
||||
@uniswap/web-admins
|
||||
@ -90,7 +90,7 @@ const DescriptionText = styled(ThemedText.LabelMicro)`
|
||||
|
||||
function useOrderAmounts(
|
||||
orderDetails?: UniswapXOrderDetails
|
||||
): Pick<InterfaceTrade, 'inputAmount' | 'outputAmount'> | undefined {
|
||||
): Pick<InterfaceTrade, 'inputAmount' | 'postTaxOutputAmount'> | undefined {
|
||||
const inputCurrency = useCurrency(orderDetails?.swapInfo?.inputCurrencyId, orderDetails?.chainId)
|
||||
const outputCurrency = useCurrency(orderDetails?.swapInfo?.outputCurrencyId, orderDetails?.chainId)
|
||||
|
||||
@ -106,7 +106,7 @@ function useOrderAmounts(
|
||||
if (swapInfo.tradeType === TradeType.EXACT_INPUT) {
|
||||
return {
|
||||
inputAmount: CurrencyAmount.fromRawAmount(inputCurrency, swapInfo.inputCurrencyAmountRaw),
|
||||
outputAmount: CurrencyAmount.fromRawAmount(
|
||||
postTaxOutputAmount: CurrencyAmount.fromRawAmount(
|
||||
outputCurrency,
|
||||
swapInfo.settledOutputCurrencyAmountRaw ?? swapInfo.expectedOutputCurrencyAmountRaw
|
||||
),
|
||||
@ -114,7 +114,7 @@ function useOrderAmounts(
|
||||
} else {
|
||||
return {
|
||||
inputAmount: CurrencyAmount.fromRawAmount(inputCurrency, swapInfo.expectedInputCurrencyAmountRaw),
|
||||
outputAmount: CurrencyAmount.fromRawAmount(outputCurrency, swapInfo.outputCurrencyAmountRaw),
|
||||
postTaxOutputAmount: CurrencyAmount.fromRawAmount(outputCurrency, swapInfo.outputCurrencyAmountRaw),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'featureFlags'
|
||||
import { useCurrencyConversionFlag } from 'featureFlags/flags/currencyConversion'
|
||||
import { useForceUniswapXOnFlag } from 'featureFlags/flags/forceUniswapXOn'
|
||||
import { useFotAdjustmentsFlag } from 'featureFlags/flags/fotAdjustments'
|
||||
import { useMultichainUXFlag } from 'featureFlags/flags/multichainUx'
|
||||
import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc'
|
||||
import { UniswapXVariant, useUniswapXFlag } from 'featureFlags/flags/uniswapx'
|
||||
@ -242,6 +243,12 @@ export default function FeatureFlagModal() {
|
||||
featureFlag={FeatureFlag.multichainUX}
|
||||
label="Updated Multichain UX"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={BaseVariant}
|
||||
value={useFotAdjustmentsFlag()}
|
||||
featureFlag={FeatureFlag.fotAdjustedmentsEnabled}
|
||||
label="Enable fee-on-transfer UI and slippage adjustments"
|
||||
/>
|
||||
<FeatureFlagGroup name="Debug">
|
||||
<FeatureFlagOption
|
||||
variant={TraceJsonRpcVariant}
|
||||
|
||||
@ -5,8 +5,9 @@ import { useWeb3React } from '@web3-react/core'
|
||||
import { sendAnalyticsEvent } from 'analytics'
|
||||
import { LoadingRows } from 'components/Loader/styled'
|
||||
import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains'
|
||||
import { ZERO_PERCENT } from 'constants/misc'
|
||||
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 { formatCurrencyAmount, formatNumber, formatPriceImpact, NumberType } from 'utils/formatNumbers'
|
||||
|
||||
@ -82,16 +83,20 @@ export function AdvancedSwapDetails({ trade, allowedSlippage, syncing = false }:
|
||||
</RowBetween>
|
||||
)}
|
||||
{isClassicTrade(trade) && (
|
||||
<RowBetween>
|
||||
<MouseoverTooltip text={<Trans>The impact your trade has on the market price of this pool.</Trans>}>
|
||||
<ThemedText.BodySmall color="textSecondary">
|
||||
<Trans>Price Impact</Trans>
|
||||
</ThemedText.BodySmall>
|
||||
</MouseoverTooltip>
|
||||
<TextWithLoadingPlaceholder syncing={syncing} width={50}>
|
||||
<ThemedText.BodySmall>{formatPriceImpact(trade.priceImpact)}</ThemedText.BodySmall>
|
||||
</TextWithLoadingPlaceholder>
|
||||
</RowBetween>
|
||||
<>
|
||||
<TokenTaxLineItem trade={trade} type="input" />
|
||||
<TokenTaxLineItem trade={trade} type="output" />
|
||||
<RowBetween>
|
||||
<MouseoverTooltip text={<Trans>The impact your trade has on the market price of this pool.</Trans>}>
|
||||
<ThemedText.BodySmall color="textSecondary">
|
||||
<Trans>Price Impact</Trans>
|
||||
</ThemedText.BodySmall>
|
||||
</MouseoverTooltip>
|
||||
<TextWithLoadingPlaceholder syncing={syncing} width={50}>
|
||||
<ThemedText.BodySmall>{formatPriceImpact(trade.priceImpact)}</ThemedText.BodySmall>
|
||||
</TextWithLoadingPlaceholder>
|
||||
</RowBetween>
|
||||
</>
|
||||
)}
|
||||
<RowBetween>
|
||||
<RowFixed>
|
||||
@ -135,7 +140,7 @@ export function AdvancedSwapDetails({ trade, allowedSlippage, syncing = false }:
|
||||
</RowFixed>
|
||||
<TextWithLoadingPlaceholder syncing={syncing} width={65}>
|
||||
<ThemedText.BodySmall>
|
||||
{`${formatCurrencyAmount(trade.outputAmount, NumberType.SwapTradeAmount)} ${
|
||||
{`${formatCurrencyAmount(trade.postTaxOutputAmount, NumberType.SwapTradeAmount)} ${
|
||||
trade.outputAmount.currency.symbol
|
||||
}`}
|
||||
</ThemedText.BodySmall>
|
||||
@ -176,3 +181,26 @@ export function AdvancedSwapDetails({ trade, allowedSlippage, syncing = false }:
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
||||
@ -303,6 +303,7 @@ export default function ConfirmSwapModal({
|
||||
// Swap failed locally and was not broadcast to the blockchain.
|
||||
const localSwapFailure = Boolean(swapError) && !didUserReject(swapError)
|
||||
const swapFailed = localSwapFailure || swapReverted
|
||||
|
||||
useEffect(() => {
|
||||
// Reset the modal state if the user rejected the swap.
|
||||
if (swapError && !swapFailed) {
|
||||
|
||||
@ -6,7 +6,7 @@ import { useTheme } from 'styled-components'
|
||||
import { ThemedText } from 'theme'
|
||||
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()
|
||||
return (
|
||||
<Row gap="sm" justify="center" align="center">
|
||||
@ -15,9 +15,9 @@ export function TradeSummary({ trade }: { trade: Pick<InterfaceTrade, 'inputAmou
|
||||
{formatReviewSwapCurrencyAmount(trade.inputAmount)} {trade.inputAmount.currency.symbol}
|
||||
</ThemedText.LabelSmall>
|
||||
<ArrowRight color={theme.textPrimary} size="12px" />
|
||||
<CurrencyLogo currency={trade.outputAmount.currency} size="16px" />
|
||||
<CurrencyLogo currency={trade.postTaxOutputAmount.currency} size="16px" />
|
||||
<ThemedText.LabelSmall color="textPrimary">
|
||||
{formatReviewSwapCurrencyAmount(trade.outputAmount)} {trade.outputAmount.currency.symbol}
|
||||
{formatReviewSwapCurrencyAmount(trade.postTaxOutputAmount)} {trade.postTaxOutputAmount.currency.symbol}
|
||||
</ThemedText.LabelSmall>
|
||||
</Row>
|
||||
)
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
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 SwapDetailsDropdown from './SwapDetailsDropdown'
|
||||
@ -39,4 +46,42 @@ describe('SwapDetailsDropdown.tsx', () => {
|
||||
await act(() => userEvent.click(screen.getByTestId('swap-details-header-row')))
|
||||
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()
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,4 +1,12 @@
|
||||
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 SwapModalFooter from './SwapModalFooter'
|
||||
@ -97,4 +105,62 @@ describe('SwapModalFooter.tsx', () => {
|
||||
).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()
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,25 +1,26 @@
|
||||
import { Plural, Trans } from '@lingui/macro'
|
||||
import { Plural, t, Trans } from '@lingui/macro'
|
||||
import { BrowserEvent, InterfaceElementName, SwapEventName } from '@uniswap/analytics-events'
|
||||
import { Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { TraceEvent } from 'analytics'
|
||||
import Column from 'components/Column'
|
||||
import { MouseoverTooltip, TooltipSize } from 'components/Tooltip'
|
||||
import { ZERO_PERCENT } from 'constants/misc'
|
||||
import { SwapResult } from 'hooks/useSwapCallback'
|
||||
import useTransactionDeadline from 'hooks/useTransactionDeadline'
|
||||
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
|
||||
import { ReactNode } from 'react'
|
||||
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 { useRouterPreference, useUserSlippageTolerance } from 'state/user/hooks'
|
||||
import styled, { useTheme } from 'styled-components'
|
||||
import styled, { DefaultTheme, useTheme } from 'styled-components'
|
||||
import { ThemedText } from 'theme'
|
||||
import { formatNumber, formatPriceImpact, NumberType } from 'utils/formatNumbers'
|
||||
import { formatTransactionAmount, priceToPreciseFloat } from 'utils/formatNumbers'
|
||||
import getRoutingDiagramEntries from 'utils/getRoutingDiagramEntries'
|
||||
import { formatSwapButtonClickEventProperties } from 'utils/loggingFormatters'
|
||||
import { getPriceImpactWarning } from 'utils/prices'
|
||||
import { getPriceImpactColor } from 'utils/prices'
|
||||
|
||||
import { ButtonError, SmallButtonPrimary } from '../Button'
|
||||
import Row, { AutoRow, RowBetween, RowFixed } from '../Row'
|
||||
@ -41,9 +42,10 @@ const ConfirmButton = styled(ButtonError)`
|
||||
margin-top: 10px;
|
||||
`
|
||||
|
||||
const DetailRowValue = styled(ThemedText.BodySmall)`
|
||||
const DetailRowValue = styled(ThemedText.BodySmall)<{ warningColor?: keyof DefaultTheme }>`
|
||||
text-align: right;
|
||||
overflow-wrap: break-word;
|
||||
${({ warningColor, theme }) => warningColor && `color: ${theme[warningColor]};`};
|
||||
`
|
||||
|
||||
export default function SwapModalFooter({
|
||||
@ -112,18 +114,22 @@ export default function SwapModalFooter({
|
||||
</Row>
|
||||
</ThemedText.BodySmall>
|
||||
{isClassicTrade(trade) && (
|
||||
<ThemedText.BodySmall>
|
||||
<Row align="flex-start" justify="space-between" gap="sm">
|
||||
<MouseoverTooltip text={<Trans>The impact your trade has on the market price of this pool.</Trans>}>
|
||||
<Label cursor="help">
|
||||
<Trans>Price impact</Trans>
|
||||
</Label>
|
||||
</MouseoverTooltip>
|
||||
<DetailRowValue color={getPriceImpactWarning(trade.priceImpact)}>
|
||||
{trade.priceImpact ? formatPriceImpact(trade.priceImpact) : '-'}
|
||||
</DetailRowValue>
|
||||
</Row>
|
||||
</ThemedText.BodySmall>
|
||||
<>
|
||||
<TokenTaxLineItem trade={trade} type="input" />
|
||||
<TokenTaxLineItem trade={trade} type="output" />
|
||||
<ThemedText.BodySmall>
|
||||
<Row align="flex-start" justify="space-between" gap="sm">
|
||||
<MouseoverTooltip text={<Trans>The impact your trade has on the market price of this pool.</Trans>}>
|
||||
<Label cursor="help">
|
||||
<Trans>Price impact</Trans>
|
||||
</Label>
|
||||
</MouseoverTooltip>
|
||||
<DetailRowValue warningColor={getPriceImpactColor(trade.priceImpact)}>
|
||||
{trade.priceImpact ? formatPriceImpact(trade.priceImpact) : '-'}
|
||||
</DetailRowValue>
|
||||
</Row>
|
||||
</ThemedText.BodySmall>
|
||||
</>
|
||||
)}
|
||||
<ThemedText.BodySmall>
|
||||
<Row align="flex-start" justify="space-between" gap="sm">
|
||||
@ -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({
|
||||
allowedSlippage: Percent
|
||||
}) {
|
||||
const fiatValueInput = useUSDPrice(trade.inputAmount)
|
||||
const fiatValueOutput = useUSDPrice(trade.outputAmount)
|
||||
const fiatValueOutput = useUSDPrice(trade.postTaxOutputAmount)
|
||||
|
||||
return (
|
||||
<HeaderContainer gap="sm">
|
||||
@ -42,7 +42,7 @@ export default function SwapModalHeader({
|
||||
<SwapModalHeaderAmount
|
||||
field={Field.OUTPUT}
|
||||
label={<Trans>You receive</Trans>}
|
||||
amount={trade.outputAmount}
|
||||
amount={trade.postTaxOutputAmount}
|
||||
currency={trade.outputAmount.currency}
|
||||
usdAmount={fiatValueOutput.data}
|
||||
tooltipText={
|
||||
|
||||
@ -141,7 +141,7 @@ exports[`SwapModalFooter.tsx matches base snapshot, test trade exact input 1`] =
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c6 css-zhpkf8"
|
||||
class="c2 c6 css-zhpkf8"
|
||||
>
|
||||
105566.373%
|
||||
</div>
|
||||
|
||||
75
src/constants/tax.ts
Normal file
75
src/constants/tax.ts
Normal file
@ -0,0 +1,75 @@
|
||||
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
|
||||
}
|
||||
@ -24,12 +24,12 @@ class TokenSafetyLookupTable {
|
||||
this.initialized = true
|
||||
|
||||
// Initialize extended tokens first
|
||||
lists.byUrl[UNI_EXTENDED_LIST].current?.tokens.forEach((token) => {
|
||||
lists.byUrl[UNI_EXTENDED_LIST]?.current?.tokens.forEach((token) => {
|
||||
this.dict[token.address.toLowerCase()] = TOKEN_LIST_TYPES.UNI_EXTENDED
|
||||
})
|
||||
|
||||
// Initialize default tokens second, so that any tokens on both default and extended will display as default (no warning)
|
||||
lists.byUrl[UNI_LIST].current?.tokens.forEach((token) => {
|
||||
lists.byUrl[UNI_LIST]?.current?.tokens.forEach((token) => {
|
||||
this.dict[token.address.toLowerCase()] = TOKEN_LIST_TYPES.UNI_DEFAULT
|
||||
})
|
||||
|
||||
@ -39,7 +39,7 @@ class TokenSafetyLookupTable {
|
||||
})
|
||||
|
||||
// Initialize blocked tokens from all urls included
|
||||
UNSUPPORTED_LIST_URLS.map((url) => lists.byUrl[url].current?.tokens)
|
||||
UNSUPPORTED_LIST_URLS.map((url) => lists.byUrl[url]?.current?.tokens)
|
||||
.filter((x): x is TokenInfo[] => !!x)
|
||||
.flat(1)
|
||||
.forEach((token) => {
|
||||
|
||||
9
src/featureFlags/flags/fotAdjustments.ts
Normal file
9
src/featureFlags/flags/fotAdjustments.ts
Normal file
@ -0,0 +1,9 @@
|
||||
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 {
|
||||
uniswapXEthOutputEnabled = 'uniswapx_eth_output_enabled',
|
||||
multichainUX = 'multichain_ux',
|
||||
currencyConversion = 'currency_conversion',
|
||||
fotAdjustedmentsEnabled = 'fot_adjustments_enabled',
|
||||
}
|
||||
|
||||
interface FeatureFlagsContextType {
|
||||
|
||||
@ -5,6 +5,8 @@ import { useEffect } from 'react'
|
||||
import { useAppDispatch, useAppSelector } from 'state/hooks'
|
||||
import { updateSelectedWallet } from 'state/user/reducer'
|
||||
|
||||
import { useStateRehydrated } from './useStateRehydrated'
|
||||
|
||||
async function connect(connector: Connector) {
|
||||
try {
|
||||
if (connector.connectEagerly) {
|
||||
@ -21,7 +23,7 @@ export default function useEagerlyConnect() {
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const selectedWallet = useAppSelector((state) => state.user.selectedWallet)
|
||||
const rehydrated = useAppSelector((state) => state._persist.rehydrated)
|
||||
const rehydrated = useStateRehydrated()
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
|
||||
5
src/hooks/useStateRehydrated.ts
Normal file
5
src/hooks/useStateRehydrated.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { useAppSelector } from 'state/hooks'
|
||||
|
||||
export function useStateRehydrated() {
|
||||
return useAppSelector((state) => state._persist.rehydrated)
|
||||
}
|
||||
@ -67,13 +67,13 @@ export function useSwapCallback(
|
||||
? {
|
||||
tradeType: TradeType.EXACT_INPUT,
|
||||
inputCurrencyAmountRaw: trade.inputAmount.quotient.toString(),
|
||||
expectedOutputCurrencyAmountRaw: trade.outputAmount.quotient.toString(),
|
||||
expectedOutputCurrencyAmountRaw: trade.postTaxOutputAmount.quotient.toString(),
|
||||
minimumOutputCurrencyAmountRaw: trade.minimumAmountOut(allowedSlippage).quotient.toString(),
|
||||
}
|
||||
: {
|
||||
tradeType: TradeType.EXACT_OUTPUT,
|
||||
maximumInputCurrencyAmountRaw: trade.maximumAmountIn(allowedSlippage).quotient.toString(),
|
||||
outputCurrencyAmountRaw: trade.outputAmount.quotient.toString(),
|
||||
outputCurrencyAmountRaw: trade.postTaxOutputAmount.quotient.toString(),
|
||||
expectedInputCurrencyAmountRaw: trade.inputAmount.quotient.toString(),
|
||||
}),
|
||||
}
|
||||
|
||||
@ -62,8 +62,13 @@ export function useUniversalRouterSwapCallback(
|
||||
if (chainId !== connectedChainId) throw new Error('signer chainId does not match')
|
||||
|
||||
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, {
|
||||
slippageTolerance: options.slippageTolerance,
|
||||
slippageTolerance: taxAdjustedSlippageTolerance,
|
||||
deadlineOrPreviousBlockhash: options.deadline?.toString(),
|
||||
inputTokenPermit: options.permit,
|
||||
fee: options.feeOptions,
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { SkipToken, skipToken } from '@reduxjs/toolkit/query/react'
|
||||
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
|
||||
import { useForceUniswapXOn } from 'featureFlags/flags/forceUniswapXOn'
|
||||
import { useFotAdjustmentsEnabled } from 'featureFlags/flags/fotAdjustments'
|
||||
import { useUniswapXEnabled } from 'featureFlags/flags/uniswapx'
|
||||
import { useUniswapXEthOutputEnabled } from 'featureFlags/flags/uniswapXEthOutput'
|
||||
import { useUniswapXSyntheticQuoteEnabled } from 'featureFlags/flags/uniswapXUseSyntheticQuote'
|
||||
@ -34,6 +35,7 @@ export function useRoutingAPIArguments({
|
||||
const forceUniswapXOn = useForceUniswapXOn()
|
||||
const userDisabledUniswapX = useUserDisabledUniswapX()
|
||||
const uniswapXEthOutputEnabled = useUniswapXEthOutputEnabled()
|
||||
const fotAdjustmentsEnabled = useFotAdjustmentsEnabled()
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
@ -58,6 +60,7 @@ export function useRoutingAPIArguments({
|
||||
forceUniswapXOn,
|
||||
userDisabledUniswapX,
|
||||
uniswapXEthOutputEnabled,
|
||||
fotAdjustmentsEnabled,
|
||||
},
|
||||
[
|
||||
account,
|
||||
@ -71,6 +74,7 @@ export function useRoutingAPIArguments({
|
||||
forceUniswapXOn,
|
||||
userDisabledUniswapX,
|
||||
uniswapXEthOutputEnabled,
|
||||
fotAdjustmentsEnabled,
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
3602
src/locales/af-ZA.po
Normal file
3602
src/locales/af-ZA.po
Normal file
File diff suppressed because it is too large
Load Diff
3602
src/locales/ar-SA.po
Normal file
3602
src/locales/ar-SA.po
Normal file
File diff suppressed because it is too large
Load Diff
3602
src/locales/ca-ES.po
Normal file
3602
src/locales/ca-ES.po
Normal file
File diff suppressed because it is too large
Load Diff
3602
src/locales/cs-CZ.po
Normal file
3602
src/locales/cs-CZ.po
Normal file
File diff suppressed because it is too large
Load Diff
3602
src/locales/da-DK.po
Normal file
3602
src/locales/da-DK.po
Normal file
File diff suppressed because it is too large
Load Diff
3602
src/locales/de-DE.po
Normal file
3602
src/locales/de-DE.po
Normal file
File diff suppressed because it is too large
Load Diff
3602
src/locales/el-GR.po
Normal file
3602
src/locales/el-GR.po
Normal file
File diff suppressed because it is too large
Load Diff
3602
src/locales/es-ES.po
Normal file
3602
src/locales/es-ES.po
Normal file
File diff suppressed because it is too large
Load Diff
3602
src/locales/fi-FI.po
Normal file
3602
src/locales/fi-FI.po
Normal file
File diff suppressed because it is too large
Load Diff
3602
src/locales/fr-FR.po
Normal file
3602
src/locales/fr-FR.po
Normal file
File diff suppressed because it is too large
Load Diff
3602
src/locales/he-IL.po
Normal file
3602
src/locales/he-IL.po
Normal file
File diff suppressed because it is too large
Load Diff
3603
src/locales/hu-HU.po
Normal file
3603
src/locales/hu-HU.po
Normal file
File diff suppressed because it is too large
Load Diff
3602
src/locales/id-ID.po
Normal file
3602
src/locales/id-ID.po
Normal file
File diff suppressed because it is too large
Load Diff
3602
src/locales/it-IT.po
Normal file
3602
src/locales/it-IT.po
Normal file
File diff suppressed because it is too large
Load Diff
3602
src/locales/ja-JP.po
Normal file
3602
src/locales/ja-JP.po
Normal file
File diff suppressed because it is too large
Load Diff
3602
src/locales/ko-KR.po
Normal file
3602
src/locales/ko-KR.po
Normal file
File diff suppressed because it is too large
Load Diff
3602
src/locales/nl-NL.po
Normal file
3602
src/locales/nl-NL.po
Normal file
File diff suppressed because it is too large
Load Diff
3602
src/locales/no-NO.po
Normal file
3602
src/locales/no-NO.po
Normal file
File diff suppressed because it is too large
Load Diff
3602
src/locales/pl-PL.po
Normal file
3602
src/locales/pl-PL.po
Normal file
File diff suppressed because it is too large
Load Diff
3602
src/locales/pt-BR.po
Normal file
3602
src/locales/pt-BR.po
Normal file
File diff suppressed because it is too large
Load Diff
3602
src/locales/pt-PT.po
Normal file
3602
src/locales/pt-PT.po
Normal file
File diff suppressed because it is too large
Load Diff
3602
src/locales/ro-RO.po
Normal file
3602
src/locales/ro-RO.po
Normal file
File diff suppressed because it is too large
Load Diff
3602
src/locales/ru-RU.po
Normal file
3602
src/locales/ru-RU.po
Normal file
File diff suppressed because it is too large
Load Diff
3602
src/locales/sl-SI.po
Normal file
3602
src/locales/sl-SI.po
Normal file
File diff suppressed because it is too large
Load Diff
3602
src/locales/sr-SP.po
Normal file
3602
src/locales/sr-SP.po
Normal file
File diff suppressed because it is too large
Load Diff
3602
src/locales/sv-SE.po
Normal file
3602
src/locales/sv-SE.po
Normal file
File diff suppressed because it is too large
Load Diff
3602
src/locales/sw-TZ.po
Normal file
3602
src/locales/sw-TZ.po
Normal file
File diff suppressed because it is too large
Load Diff
3602
src/locales/th-TH.po
Normal file
3602
src/locales/th-TH.po
Normal file
File diff suppressed because it is too large
Load Diff
3602
src/locales/tr-TR.po
Normal file
3602
src/locales/tr-TR.po
Normal file
File diff suppressed because it is too large
Load Diff
3602
src/locales/uk-UA.po
Normal file
3602
src/locales/uk-UA.po
Normal file
File diff suppressed because it is too large
Load Diff
3602
src/locales/vi-VN.po
Normal file
3602
src/locales/vi-VN.po
Normal file
File diff suppressed because it is too large
Load Diff
3602
src/locales/zh-CN.po
Normal file
3602
src/locales/zh-CN.po
Normal file
File diff suppressed because it is too large
Load Diff
3602
src/locales/zh-TW.po
Normal file
3602
src/locales/zh-TW.po
Normal file
File diff suppressed because it is too large
Load Diff
@ -294,7 +294,7 @@ export function Swap({
|
||||
}
|
||||
: {
|
||||
[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]
|
||||
)
|
||||
@ -314,13 +314,17 @@ export function Swap({
|
||||
)
|
||||
|
||||
const fiatValueTradeInput = useUSDPrice(trade?.inputAmount)
|
||||
const fiatValueTradeOutput = useUSDPrice(trade?.outputAmount)
|
||||
const stablecoinPriceImpact = useMemo(
|
||||
const fiatValueTradeOutput = useUSDPrice(trade?.postTaxOutputAmount)
|
||||
const preTaxFiatValueTradeOutput = useUSDPrice(trade?.outputAmount)
|
||||
const [stablecoinPriceImpact, preTaxStablecoinPriceImpact] = useMemo(
|
||||
() =>
|
||||
routeIsSyncing || !isClassicTrade(trade)
|
||||
? undefined
|
||||
: computeFiatValuePriceImpact(fiatValueTradeInput.data, fiatValueTradeOutput.data),
|
||||
[fiatValueTradeInput, fiatValueTradeOutput, routeIsSyncing, trade]
|
||||
? [undefined, undefined]
|
||||
: [
|
||||
computeFiatValuePriceImpact(fiatValueTradeInput.data, fiatValueTradeOutput.data),
|
||||
computeFiatValuePriceImpact(fiatValueTradeInput.data, preTaxFiatValueTradeOutput.data),
|
||||
],
|
||||
[fiatValueTradeInput, fiatValueTradeOutput, preTaxFiatValueTradeOutput, routeIsSyncing, trade]
|
||||
)
|
||||
|
||||
const { onSwitchTokens, onCurrencySelection, onUserInput, onChangeRecipient } = useSwapActionHandlers(dispatch)
|
||||
@ -417,7 +421,7 @@ export function Swap({
|
||||
if (!swapCallback) {
|
||||
return
|
||||
}
|
||||
if (stablecoinPriceImpact && !confirmPriceImpactWithoutFee(stablecoinPriceImpact)) {
|
||||
if (preTaxStablecoinPriceImpact && !confirmPriceImpactWithoutFee(preTaxStablecoinPriceImpact)) {
|
||||
return
|
||||
}
|
||||
setSwapState((currentState) => ({
|
||||
@ -440,7 +444,7 @@ export function Swap({
|
||||
swapResult: undefined,
|
||||
}))
|
||||
})
|
||||
}, [swapCallback, stablecoinPriceImpact])
|
||||
}, [swapCallback, preTaxStablecoinPriceImpact])
|
||||
|
||||
const handleOnWrap = useCallback(async () => {
|
||||
if (!onWrap) return
|
||||
@ -476,9 +480,9 @@ export function Swap({
|
||||
}
|
||||
|
||||
const marketPriceImpact = trade?.priceImpact ? computeRealizedPriceImpact(trade) : undefined
|
||||
const largerPriceImpact = largerPercentValue(marketPriceImpact, stablecoinPriceImpact)
|
||||
const largerPriceImpact = largerPercentValue(marketPriceImpact, preTaxStablecoinPriceImpact)
|
||||
return { priceImpactSeverity: warningSeverity(largerPriceImpact), largerPriceImpact }
|
||||
}, [stablecoinPriceImpact, trade])
|
||||
}, [preTaxStablecoinPriceImpact, trade])
|
||||
|
||||
const handleConfirmDismiss = useCallback(() => {
|
||||
setSwapState((currentState) => ({ ...currentState, showConfirm: false }))
|
||||
|
||||
@ -2,6 +2,7 @@ import { getVersionUpgrade, VersionUpgrade } from '@uniswap/token-lists'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { DEFAULT_LIST_OF_LISTS, UNSUPPORTED_LIST_URLS } from 'constants/lists'
|
||||
import TokenSafetyLookupTable from 'constants/tokenSafetyLookup'
|
||||
import { useStateRehydrated } from 'hooks/useStateRehydrated'
|
||||
import useInterval from 'lib/hooks/useInterval'
|
||||
import ms from 'ms'
|
||||
import { useCallback, useEffect } from 'react'
|
||||
@ -21,10 +22,11 @@ export default function Updater(): null {
|
||||
// get all loaded lists, and the active urls
|
||||
const lists = useAllLists()
|
||||
const listsState = useAppSelector((state) => state.lists)
|
||||
const rehydrated = useStateRehydrated()
|
||||
|
||||
useEffect(() => {
|
||||
TokenSafetyLookupTable.update(listsState)
|
||||
}, [listsState])
|
||||
if (rehydrated) TokenSafetyLookupTable.update(listsState)
|
||||
}, [listsState, rehydrated])
|
||||
|
||||
const fetchList = useFetchListCallback()
|
||||
const fetchAllListsCallback = useCallback(() => {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { MixedRouteSDK, Protocol, Trade } from '@uniswap/router-sdk'
|
||||
import { ChainId, Currency, CurrencyAmount, Percent, Token, TradeType } from '@uniswap/sdk-core'
|
||||
import { MixedRouteSDK, ONE, Protocol, Trade } from '@uniswap/router-sdk'
|
||||
import { ChainId, Currency, CurrencyAmount, Fraction, Percent, Token, TradeType } from '@uniswap/sdk-core'
|
||||
import { DutchOrderInfo, DutchOrderInfoJSON, DutchOrderTrade as IDutchOrderTrade } from '@uniswap/uniswapx-sdk'
|
||||
import { Route as V2Route } from '@uniswap/v2-sdk'
|
||||
import { Route as V3Route } from '@uniswap/v3-sdk'
|
||||
@ -47,6 +47,7 @@ export interface GetQuoteArgs {
|
||||
uniswapXEthOutputEnabled: boolean
|
||||
forceUniswapXOn: boolean
|
||||
userDisabledUniswapX: boolean
|
||||
fotAdjustmentsEnabled: boolean
|
||||
}
|
||||
|
||||
// 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> {
|
||||
isUniswapXBetter: boolean | undefined
|
||||
requestId: string | undefined
|
||||
quoteMethod: QuoteMethod
|
||||
inputTax: Percent
|
||||
outputTax: Percent
|
||||
|
||||
constructor({
|
||||
gasUseEstimateUSD,
|
||||
@ -154,6 +157,8 @@ export class ClassicTrade extends Trade<Currency, Currency, TradeType> {
|
||||
requestId,
|
||||
quoteMethod,
|
||||
approveInfo,
|
||||
inputTax,
|
||||
outputTax,
|
||||
...routes
|
||||
}: {
|
||||
gasUseEstimateUSD?: number
|
||||
@ -163,6 +168,8 @@ export class ClassicTrade extends Trade<Currency, Currency, TradeType> {
|
||||
requestId?: string
|
||||
quoteMethod: QuoteMethod
|
||||
approveInfo: ApproveInfo
|
||||
inputTax: Percent
|
||||
outputTax: Percent
|
||||
v2Routes: {
|
||||
routev2: V2Route<Currency, Currency>
|
||||
inputAmount: CurrencyAmount<Currency>
|
||||
@ -187,6 +194,26 @@ export class ClassicTrade extends Trade<Currency, Currency, TradeType> {
|
||||
this.requestId = requestId
|
||||
this.quoteMethod = quoteMethod
|
||||
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
|
||||
@ -259,6 +286,11 @@ export class DutchOrderTrade extends IDutchOrderTrade<Currency, Currency, TradeT
|
||||
|
||||
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
|
||||
|
||||
@ -6,7 +6,9 @@ import { DutchOrderInfo, DutchOrderInfoJSON } from '@uniswap/uniswapx-sdk'
|
||||
import { Pair, Route as V2Route } from '@uniswap/v2-sdk'
|
||||
import { FeeAmount, Pool, Route as V3Route } from '@uniswap/v3-sdk'
|
||||
import { asSupportedChain } from 'constants/chains'
|
||||
import { ZERO_PERCENT } from 'constants/misc'
|
||||
import { RPC_PROVIDERS } from 'constants/providers'
|
||||
import { getInputTax, getOutputTax } from 'constants/tax'
|
||||
import { isAvalanche, isBsc, isMatic, nativeOnChain } from 'constants/tokens'
|
||||
import { toSlippagePercent } from 'utils/slippage'
|
||||
|
||||
@ -213,6 +215,9 @@ export async function transformRoutesToTrade(
|
||||
|
||||
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({
|
||||
v2Routes:
|
||||
routes
|
||||
@ -247,6 +252,8 @@ export async function transformRoutesToTrade(
|
||||
isUniswapXBetter,
|
||||
requestId: data.quote.requestId,
|
||||
quoteMethod,
|
||||
inputTax,
|
||||
outputTax,
|
||||
})
|
||||
|
||||
// During the opt-in period, only return UniswapX quotes if the user has turned on the setting,
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { ChainId, CurrencyAmount, Percent, Token, TradeType } from '@uniswap/sdk-core'
|
||||
import { V3Route } from '@uniswap/smart-order-router'
|
||||
import { FeeAmount, Pool } from '@uniswap/v3-sdk'
|
||||
import { ZERO_PERCENT } from 'constants/misc'
|
||||
import { nativeOnChain } from 'constants/tokens'
|
||||
import { BigNumber } from 'ethers/lib/ethers'
|
||||
import JSBI from 'jsbi'
|
||||
@ -46,6 +47,8 @@ export const TEST_TRADE_EXACT_INPUT = new ClassicTrade({
|
||||
gasUseEstimateUSD: 1.0,
|
||||
approveInfo: { needsApprove: false },
|
||||
quoteMethod: QuoteMethod.CLIENT_SIDE,
|
||||
inputTax: ZERO_PERCENT,
|
||||
outputTax: ZERO_PERCENT,
|
||||
})
|
||||
|
||||
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,
|
||||
approveInfo: { needsApprove: false },
|
||||
quoteMethod: QuoteMethod.ROUTING_API,
|
||||
inputTax: ZERO_PERCENT,
|
||||
outputTax: ZERO_PERCENT,
|
||||
})
|
||||
|
||||
export const TEST_TRADE_EXACT_OUTPUT = new ClassicTrade({
|
||||
@ -75,6 +80,8 @@ export const TEST_TRADE_EXACT_OUTPUT = new ClassicTrade({
|
||||
tradeType: TradeType.EXACT_OUTPUT,
|
||||
quoteMethod: QuoteMethod.CLIENT_SIDE,
|
||||
approveInfo: { needsApprove: false },
|
||||
inputTax: ZERO_PERCENT,
|
||||
outputTax: ZERO_PERCENT,
|
||||
})
|
||||
|
||||
export const TEST_ALLOWED_SLIPPAGE = new Percent(2, 100)
|
||||
@ -116,3 +123,37 @@ export const TEST_DUTCH_TRADE_ETH_INPUT = new DutchOrderTrade({
|
||||
deadlineBufferSecs: 30,
|
||||
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),
|
||||
})
|
||||
|
||||
@ -2,7 +2,8 @@
|
||||
* Returns the time elapsed between page load and now.
|
||||
* @param markName the identifier for the performance mark to be created and measured.
|
||||
*/
|
||||
export function calculateElapsedTimeWithPerformanceMark(markName: string): number {
|
||||
export function calculateElapsedTimeWithPerformanceMark(markName: string): number | undefined {
|
||||
const elapsedTime = performance.mark(markName)
|
||||
if (!elapsedTime) return undefined
|
||||
return elapsedTime.startTime
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ import { Currency, CurrencyAmount, Fraction, Percent, TradeType } from '@uniswap
|
||||
import { Pair } from '@uniswap/v2-sdk'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import JSBI from 'jsbi'
|
||||
import { DefaultTheme } from 'styled-components'
|
||||
|
||||
import {
|
||||
ALLOWED_PRICE_IMPACT_HIGH,
|
||||
@ -104,3 +105,14 @@ export function getPriceImpactWarning(priceImpact: Percent): 'warning' | 'error'
|
||||
if (priceImpact.greaterThan(ALLOWED_PRICE_IMPACT_MEDIUM)) return 'warning'
|
||||
return
|
||||
}
|
||||
|
||||
export function getPriceImpactColor(priceImpact: Percent): keyof DefaultTheme | undefined {
|
||||
switch (getPriceImpactWarning(priceImpact)) {
|
||||
case 'error':
|
||||
return 'accentFailure'
|
||||
case 'warning':
|
||||
return 'accentWarning'
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user