From ff6d1cc510915f0a6d01ff4bdb4ecfc7a4ecec66 Mon Sep 17 00:00:00 2001 From: Tina <59578595+tinaszheng@users.noreply.github.com> Date: Wed, 15 Nov 2023 09:57:43 -0500 Subject: [PATCH] feat: read token taxes from backend response (#7532) * read token taxes from backend * revert env changes * upgrade router-sdk for updated price impact logic * add tax information to trade currencies instead of directly on trade object * consolidate getTradeCurrencies with getSwapCurrenciesWithTaxInfo * delete feature flag for token taxes! * run yarn dedupe again * fix unit tests * update logic for disabling inputs * update snapshot again * fix return value for uniswapx * remove unused constants and update comment * pr review * re-add useSwapTaxes for token descriptor page * add in client-side tax fetching on currency level * revert removing newline * typecheck.... * typecheck... * remove inputTax, outputTax from routing-api arguments because they are now unused * dont pass in tax info to preview trade --- package.json | 2 +- .../Activity/OffchainActivityModal.tsx | 6 +- .../FeatureFlagModal/FeatureFlagModal.tsx | 7 - .../swap/PendingModalContent/TradeSummary.tsx | 6 +- src/components/swap/SwapModalHeader.tsx | 4 +- .../__snapshots__/SwapLineItem.test.tsx.snap | 16 +- src/featureFlags/flags/fotAdjustments.ts | 9 -- src/featureFlags/index.tsx | 1 - src/hooks/useSwapCallback.tsx | 4 +- src/hooks/useUniversalRouter.ts | 6 +- .../hooks/routing/useRoutingAPIArguments.ts | 10 +- src/lib/utils/analytics.ts | 8 +- src/pages/Swap/index.tsx | 17 +- src/state/routing/slice.ts | 7 +- src/state/routing/types.ts | 79 +++------- src/state/routing/useRoutingAPITrade.test.ts | 3 - src/state/routing/useRoutingAPITrade.ts | 7 +- src/state/routing/utils.test.ts | 145 ++++++++++++------ src/state/routing/utils.ts | 74 +++++---- src/state/swap/hooks.tsx | 22 ++- src/test-utils/constants.ts | 57 +++++-- yarn.lock | 66 +++----- 22 files changed, 259 insertions(+), 297 deletions(-) delete mode 100644 src/featureFlags/flags/fotAdjustments.ts diff --git a/package.json b/package.json index c624993363..de10f658b8 100644 --- a/package.json +++ b/package.json @@ -202,7 +202,7 @@ "@uniswap/merkle-distributor": "^1.0.1", "@uniswap/permit2-sdk": "^1.2.0", "@uniswap/redux-multicall": "^1.1.8", - "@uniswap/router-sdk": "^1.6.0", + "@uniswap/router-sdk": "^1.7.1", "@uniswap/sdk-core": "4.0.7", "@uniswap/smart-order-router": "^3.15.0", "@uniswap/token-lists": "^1.0.0-beta.33", diff --git a/src/components/AccountDrawer/MiniPortfolio/Activity/OffchainActivityModal.tsx b/src/components/AccountDrawer/MiniPortfolio/Activity/OffchainActivityModal.tsx index 7d38f8cf6b..2c346d6d06 100644 --- a/src/components/AccountDrawer/MiniPortfolio/Activity/OffchainActivityModal.tsx +++ b/src/components/AccountDrawer/MiniPortfolio/Activity/OffchainActivityModal.tsx @@ -90,7 +90,7 @@ const DescriptionText = styled(ThemedText.LabelMicro)` function useOrderAmounts( orderDetails?: UniswapXOrderDetails -): Pick | undefined { +): Pick | 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), - postTaxOutputAmount: CurrencyAmount.fromRawAmount( + outputAmount: CurrencyAmount.fromRawAmount( outputCurrency, swapInfo.settledOutputCurrencyAmountRaw ?? swapInfo.expectedOutputCurrencyAmountRaw ), @@ -114,7 +114,7 @@ function useOrderAmounts( } else { return { inputAmount: CurrencyAmount.fromRawAmount(inputCurrency, swapInfo.expectedInputCurrencyAmountRaw), - postTaxOutputAmount: CurrencyAmount.fromRawAmount(outputCurrency, swapInfo.outputCurrencyAmountRaw), + outputAmount: CurrencyAmount.fromRawAmount(outputCurrency, swapInfo.outputCurrencyAmountRaw), } } } diff --git a/src/components/FeatureFlagModal/FeatureFlagModal.tsx b/src/components/FeatureFlagModal/FeatureFlagModal.tsx index a5be1e9d7c..1cb96ed7c0 100644 --- a/src/components/FeatureFlagModal/FeatureFlagModal.tsx +++ b/src/components/FeatureFlagModal/FeatureFlagModal.tsx @@ -6,7 +6,6 @@ import { useQuickRouteChains } from 'featureFlags/dynamicConfig/quickRouteChains import { useAndroidGALaunchFlag } from 'featureFlags/flags/androidGALaunch' import { useCurrencyConversionFlag } from 'featureFlags/flags/currencyConversion' import { useFallbackProviderEnabledFlag } from 'featureFlags/flags/fallbackProvider' -import { useFotAdjustmentsFlag } from 'featureFlags/flags/fotAdjustments' import { useInfoExploreFlag } from 'featureFlags/flags/infoExplore' import { useInfoLiveViewsFlag } from 'featureFlags/flags/infoLiveViews' import { useInfoPoolPageFlag } from 'featureFlags/flags/infoPoolPage' @@ -293,12 +292,6 @@ export default function FeatureFlagModal() { featureFlag={FeatureFlag.multichainUX} label="Updated Multichain UX" /> - }) { +export function TradeSummary({ trade }: { trade: Pick }) { const theme = useTheme() const { formatReviewSwapCurrencyAmount } = useFormatter() @@ -17,9 +17,9 @@ export function TradeSummary({ trade }: { trade: Pick - + - {formatReviewSwapCurrencyAmount(trade.postTaxOutputAmount)} {trade.postTaxOutputAmount.currency.symbol} + {formatReviewSwapCurrencyAmount(trade.outputAmount)} {trade.outputAmount.currency.symbol} ) diff --git a/src/components/swap/SwapModalHeader.tsx b/src/components/swap/SwapModalHeader.tsx index 084fedfa99..7650034bc4 100644 --- a/src/components/swap/SwapModalHeader.tsx +++ b/src/components/swap/SwapModalHeader.tsx @@ -24,7 +24,7 @@ export default function SwapModalHeader({ allowedSlippage: Percent }) { const fiatValueInput = useUSDPrice(trade.inputAmount) - const fiatValueOutput = useUSDPrice(trade.postTaxOutputAmount) + const fiatValueOutput = useUSDPrice(trade.outputAmount) return ( @@ -40,7 +40,7 @@ export default function SwapModalHeader({ You receive} - amount={trade.postTaxOutputAmount} + amount={trade.outputAmount} currency={trade.outputAmount.currency} usdAmount={fiatValueOutput.data} isLoading={isPreviewTrade(trade) && trade.tradeType === TradeType.EXACT_INPUT} diff --git a/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap b/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap index 989b0d94fe..933e783c44 100644 --- a/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap +++ b/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap @@ -6882,7 +6882,7 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` - ~-105566.373% + ~-108834.406% @@ -7165,14 +7165,14 @@ exports[`SwapLineItem.tsx fee on buy 1`] = `
- 0.000000000000000952 DEF + 0.00000000000000098 DEF
- If the price moves so that you will receive less than 0.000000000000000952 DEF, your transaction will be reverted. This is the minimum amount you are guaranteed to receive. + If the price moves so that you will receive less than 0.00000000000000098 DEF, your transaction will be reverted. This is the minimum amount you are guaranteed to receive. - 0.000000000000000952 DEF + 0.00000000000000098 DEF
@@ -8761,7 +8761,7 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` - ~-105566.373% + ~-108834.406% @@ -9044,14 +9044,14 @@ exports[`SwapLineItem.tsx fee on sell 1`] = `
- 0.000000000000000952 DEF + 0.00000000000000098 DEF
diff --git a/src/featureFlags/flags/fotAdjustments.ts b/src/featureFlags/flags/fotAdjustments.ts deleted file mode 100644 index 7ec01863ee..0000000000 --- a/src/featureFlags/flags/fotAdjustments.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { BaseVariant, FeatureFlag, useBaseFlag } from '../index' - -export function useFotAdjustmentsFlag(): BaseVariant { - return useBaseFlag(FeatureFlag.fotAdjustedmentsEnabled) -} - -export function useFotAdjustmentsEnabled(): boolean { - return useFotAdjustmentsFlag() === BaseVariant.Enabled -} diff --git a/src/featureFlags/index.tsx b/src/featureFlags/index.tsx index 2889136cde..2c7bd66696 100644 --- a/src/featureFlags/index.tsx +++ b/src/featureFlags/index.tsx @@ -14,7 +14,6 @@ export enum FeatureFlag { uniswapXExactOutputEnabled = 'uniswapx_exact_output_enabled', multichainUX = 'multichain_ux', currencyConversion = 'currency_conversion', - fotAdjustedmentsEnabled = 'fot_dynamic_adjustments_enabled', infoExplore = 'info_explore', infoTDP = 'info_tdp', infoPoolPage = 'info_pool_page', diff --git a/src/hooks/useSwapCallback.tsx b/src/hooks/useSwapCallback.tsx index 2200123c43..3820ff0bc1 100644 --- a/src/hooks/useSwapCallback.tsx +++ b/src/hooks/useSwapCallback.tsx @@ -84,13 +84,13 @@ export function useSwapCallback( ? { tradeType: TradeType.EXACT_INPUT, inputCurrencyAmountRaw: trade.inputAmount.quotient.toString(), - expectedOutputCurrencyAmountRaw: trade.postTaxOutputAmount.quotient.toString(), + expectedOutputCurrencyAmountRaw: trade.outputAmount.quotient.toString(), minimumOutputCurrencyAmountRaw: trade.minimumAmountOut(allowedSlippage).quotient.toString(), } : { tradeType: TradeType.EXACT_OUTPUT, maximumInputCurrencyAmountRaw: trade.maximumAmountIn(allowedSlippage).quotient.toString(), - outputCurrencyAmountRaw: trade.postTaxOutputAmount.quotient.toString(), + outputCurrencyAmountRaw: trade.outputAmount.quotient.toString(), expectedInputCurrencyAmountRaw: trade.inputAmount.quotient.toString(), }), } diff --git a/src/hooks/useUniversalRouter.ts b/src/hooks/useUniversalRouter.ts index f8e7f88c19..8a8849431a 100644 --- a/src/hooks/useUniversalRouter.ts +++ b/src/hooks/useUniversalRouter.ts @@ -73,12 +73,8 @@ export function useUniversalRouterSwapCallback( 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: taxAdjustedSlippageTolerance, + slippageTolerance: options.slippageTolerance, deadlineOrPreviousBlockhash: options.deadline?.toString(), inputTokenPermit: options.permit, fee: options.feeOptions, diff --git a/src/lib/hooks/routing/useRoutingAPIArguments.ts b/src/lib/hooks/routing/useRoutingAPIArguments.ts index ca624530e3..a8516af951 100644 --- a/src/lib/hooks/routing/useRoutingAPIArguments.ts +++ b/src/lib/hooks/routing/useRoutingAPIArguments.ts @@ -1,5 +1,5 @@ import { SkipToken, skipToken } from '@reduxjs/toolkit/query/react' -import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core' +import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core' import { useUniswapXDefaultEnabled } from 'featureFlags/flags/uniswapXDefault' import { useUniswapXEthOutputEnabled } from 'featureFlags/flags/uniswapXEthOutput' import { useUniswapXExactOutputEnabled } from 'featureFlags/flags/uniswapXExactOutput' @@ -22,8 +22,6 @@ export function useRoutingAPIArguments({ amount, tradeType, routerPreference, - inputTax, - outputTax, }: { account?: string tokenIn?: Currency @@ -31,8 +29,6 @@ export function useRoutingAPIArguments({ amount?: CurrencyAmount tradeType: TradeType routerPreference: RouterPreference | typeof INTERNAL_ROUTER_PREFERENCE_PRICE - inputTax: Percent - outputTax: Percent }): GetQuoteArgs | SkipToken { const uniswapXForceSyntheticQuotes = useUniswapXSyntheticQuoteEnabled() const userDisabledUniswapX = useUserDisabledUniswapX() @@ -70,8 +66,6 @@ export function useRoutingAPIArguments({ uniswapXExactOutputEnabled, isUniswapXDefaultEnabled, sendPortionEnabled, - inputTax, - outputTax, }, [ account, @@ -87,8 +81,6 @@ export function useRoutingAPIArguments({ uniswapXEthOutputEnabled, isUniswapXDefaultEnabled, sendPortionEnabled, - inputTax, - outputTax, ] ) } diff --git a/src/lib/utils/analytics.ts b/src/lib/utils/analytics.ts index ae3960e739..b439bd2e8f 100644 --- a/src/lib/utils/analytics.ts +++ b/src/lib/utils/analytics.ts @@ -51,7 +51,7 @@ export function formatCommonPropertiesForTrade( token_in_symbol: trade.inputAmount.currency.symbol, token_out_symbol: trade.outputAmount.currency.symbol, token_in_amount: formatToDecimal(trade.inputAmount, trade.inputAmount.currency.decimals), - token_out_amount: formatToDecimal(trade.postTaxOutputAmount, trade.outputAmount.currency.decimals), + token_out_amount: formatToDecimal(trade.outputAmount, trade.outputAmount.currency.decimals), price_impact_basis_points: isClassicTrade(trade) ? formatPercentInBasisPointsNumber(computeRealizedPriceImpact(trade)) : undefined, @@ -64,6 +64,8 @@ export function formatCommonPropertiesForTrade( allowed_slippage: formatPercentNumber(allowedSlippage), method: getQuoteMethod(trade), fee_usd: outputFeeFiatValue, + token_out_detected_tax: formatPercentNumber(trade.outputTax), + token_in_detected_tax: formatPercentNumber(trade.inputTax), } } @@ -101,8 +103,6 @@ export const formatSwapQuoteReceivedEventProperties = ( trade: InterfaceTrade, allowedSlippage: Percent, swapQuoteLatencyMs: number | undefined, - inputTax: Percent, - outputTax: Percent, outputFeeFiatValue: number | undefined ) => { return { @@ -112,7 +112,5 @@ export const formatSwapQuoteReceivedEventProperties = ( token_in_amount_max: trade.maximumAmountIn(allowedSlippage).toExact(), token_out_amount_min: trade.minimumAmountOut(allowedSlippage).toExact(), quote_latency_milliseconds: swapQuoteLatencyMs, - token_out_detected_tax: formatPercentNumber(outputTax), - token_in_detected_tax: formatPercentNumber(inputTax), } } diff --git a/src/pages/Swap/index.tsx b/src/pages/Swap/index.tsx index b5ce8f91eb..d917d204f3 100644 --- a/src/pages/Swap/index.tsx +++ b/src/pages/Swap/index.tsx @@ -292,9 +292,9 @@ export function Swap({ parsedAmount, currencies, inputError: swapInputError, + outputFeeFiatValue, inputTax, outputTax, - outputFeeFiatValue, } = swapInfo const [inputTokenHasTax, outputTokenHasTax] = useMemo( @@ -323,7 +323,7 @@ export function Swap({ } : { [Field.INPUT]: independentField === Field.INPUT ? parsedAmount : trade?.inputAmount, - [Field.OUTPUT]: independentField === Field.OUTPUT ? parsedAmount : trade?.postTaxOutputAmount, + [Field.OUTPUT]: independentField === Field.OUTPUT ? parsedAmount : trade?.outputAmount, }, [independentField, parsedAmount, showWrap, trade] ) @@ -354,7 +354,7 @@ export function Swap({ ) const fiatValueTradeInput = useUSDPrice(trade?.inputAmount) - const fiatValueTradeOutput = useUSDPrice(trade?.postTaxOutputAmount) + const fiatValueTradeOutput = useUSDPrice(trade?.outputAmount) const preTaxFiatValueTradeOutput = useUSDPrice(trade?.outputAmount) const [stablecoinPriceImpact, preTaxStablecoinPriceImpact] = useMemo( () => @@ -585,17 +585,10 @@ export function Swap({ if (!trade || prevTrade === trade) return // no new swap quote to log sendAnalyticsEvent(SwapEventName.SWAP_QUOTE_RECEIVED, { - ...formatSwapQuoteReceivedEventProperties( - trade, - allowedSlippage, - swapQuoteLatency, - inputTax, - outputTax, - outputFeeFiatValue - ), + ...formatSwapQuoteReceivedEventProperties(trade, allowedSlippage, swapQuoteLatency, outputFeeFiatValue), ...trace, }) - }, [prevTrade, trade, trace, allowedSlippage, swapQuoteLatency, inputTax, outputTax, outputFeeFiatValue]) + }, [prevTrade, trade, trace, allowedSlippage, swapQuoteLatency, outputFeeFiatValue]) const showDetailsDropdown = Boolean( !showWrap && userHasSpecifiedInputOutput && (trade || routeIsLoading || routeIsSyncing) diff --git a/src/state/routing/slice.ts b/src/state/routing/slice.ts index a0e16b2f40..9d9836d33f 100644 --- a/src/state/routing/slice.ts +++ b/src/state/routing/slice.ts @@ -19,7 +19,7 @@ import { URAQuoteResponse, URAQuoteType, } from './types' -import { isExactInput, transformRoutesToTrade } from './utils' +import { isExactInput, transformQuoteToTrade } from './utils' const UNISWAP_API_URL = process.env.REACT_APP_UNISWAP_API_URL if (UNISWAP_API_URL === undefined) { @@ -69,6 +69,7 @@ function getRoutingAPIConfig(args: GetQuoteArgs): RoutingConfig { ...DEFAULT_QUERY_PARAMS, routingType: URAQuoteType.CLASSIC, recipient: account, + enableFeeOnTransferFeeFetching: true, } const tokenOutIsNative = Object.values(SwapRouterNativeAssets).includes(tokenOutAddress as SwapRouterNativeAssets) @@ -179,7 +180,7 @@ export const routingApi = createApi({ } const uraQuoteResponse = response.data as URAQuoteResponse - const tradeResult = await transformRoutesToTrade(args, uraQuoteResponse, QuoteMethod.ROUTING_API) + const tradeResult = await transformQuoteToTrade(args, uraQuoteResponse, QuoteMethod.ROUTING_API) return { data: { ...tradeResult, latencyMs: getQuoteLatencyMeasure(quoteStartMark).duration } } } catch (error: any) { console.warn( @@ -194,7 +195,7 @@ export const routingApi = createApi({ const router = getRouter(args.tokenInChainId) const quoteResult = await getClientSideQuote(args, router, CLIENT_PARAMS) if (quoteResult.state === QuoteState.SUCCESS) { - const trade = await transformRoutesToTrade(args, quoteResult.data, QuoteMethod.CLIENT_SIDE_FALLBACK) + const trade = await transformQuoteToTrade(args, quoteResult.data, QuoteMethod.CLIENT_SIDE_FALLBACK) return { data: { ...trade, latencyMs: getQuoteLatencyMeasure(quoteStartMark).duration }, } diff --git a/src/state/routing/types.ts b/src/state/routing/types.ts index a7e0bb3650..da78c1ad3b 100644 --- a/src/state/routing/types.ts +++ b/src/state/routing/types.ts @@ -51,8 +51,6 @@ export interface GetQuoteArgs { userOptedOutOfUniswapX: boolean isUniswapXDefaultEnabled: boolean sendPortionEnabled: boolean - inputTax: Percent - outputTax: Percent } export type GetQuickQuoteArgs = { @@ -69,9 +67,12 @@ export type GetQuickQuoteArgs = { inputTax: Percent outputTax: Percent } -// from https://github.com/Uniswap/routing-api/blob/main/lib/handlers/schema.ts -type TokenInRoute = Pick +// from https://github.com/Uniswap/routing-api/blob/main/lib/handlers/schema.ts +export type TokenInRoute = Pick & { + buyFeeBps?: string + sellFeeBps?: string +} export type V3PoolInRoute = { type: 'v3-pool' @@ -201,8 +202,6 @@ export class ClassicTrade extends Trade { isUniswapXBetter: boolean | undefined requestId: string | undefined quoteMethod: QuoteMethod - inputTax: Percent - outputTax: Percent swapFee: SwapFeeInfo | undefined constructor({ @@ -212,8 +211,6 @@ export class ClassicTrade extends Trade { requestId, quoteMethod, approveInfo, - inputTax, - outputTax, swapFee, ...routes }: { @@ -224,8 +221,6 @@ export class ClassicTrade extends Trade { requestId?: string quoteMethod: QuoteMethod approveInfo: ApproveInfo - inputTax: Percent - outputTax: Percent swapFee?: SwapFeeInfo v2Routes: { routev2: V2Route @@ -251,8 +246,6 @@ export class ClassicTrade extends Trade { this.requestId = requestId this.quoteMethod = quoteMethod this.approveInfo = approveInfo - this.inputTax = inputTax - this.outputTax = outputTax this.swapFee = swapFee } @@ -263,10 +256,6 @@ export class ClassicTrade extends Trade { return new Price({ baseAmount: this.inputAmount, quoteAmount: this.postSwapFeeOutputAmount }) } - public get totalTaxRate(): Percent { - return this.inputTax.add(this.outputTax) - } - public get postSwapFeeOutputAmount(): CurrencyAmount { // Routing api already applies the swap fee to the output amount for exact-in if (this.tradeType === TradeType.EXACT_INPUT) return this.outputAmount @@ -275,20 +264,6 @@ export class ClassicTrade extends Trade { return this.outputAmount.subtract(swapFeeAmount) } - 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.postSwapFeeOutputAmount.multiply(new Fraction(ONE).subtract(this.totalTaxRate)) - } - - public minimumAmountOut(slippageTolerance: Percent, amountOut = this.outputAmount): CurrencyAmount { - // 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 public get totalGasUseEstimateUSD(): number | undefined { if (this.approveInfo.needsApprove && this.gasUseEstimateUSD) { @@ -370,11 +345,6 @@ export class DutchOrderTrade extends IDutchOrderTrade public readonly outputAmount: CurrencyAmount - inputTax: Percent - outputTax: Percent constructor({ inputAmount, outputAmount, tradeType, - inputTax, - outputTax, }: { inputAmount: CurrencyAmount outputAmount: CurrencyAmount tradeType: TradeType - inputTax: Percent - outputTax: Percent }) { this.inputAmount = inputAmount this.outputAmount = outputAmount this.tradeType = tradeType - 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)) } // below methods are copied from router-sdk @@ -440,6 +391,26 @@ export class PreviewTrade { } } + /** + * Returns the sell tax of the input token + */ + public get inputTax(): Percent { + const inputCurrency = this.inputAmount.currency + if (inputCurrency.isNative || !inputCurrency.wrapped.sellFeeBps) return ZERO_PERCENT + + return new Percent(inputCurrency.wrapped.sellFeeBps.toNumber(), 10000) + } + + /** + * Returns the buy tax of the output token + */ + public get outputTax(): Percent { + const outputCurrency = this.outputAmount.currency + if (outputCurrency.isNative || !outputCurrency.wrapped.buyFeeBps) return ZERO_PERCENT + + return new Percent(outputCurrency.wrapped.buyFeeBps.toNumber(), 10000) + } + private _executionPrice: Price | undefined /** * The price expressed in terms of output amount/input amount. diff --git a/src/state/routing/useRoutingAPITrade.test.ts b/src/state/routing/useRoutingAPITrade.test.ts index 95151b8754..b58f8b9c70 100644 --- a/src/state/routing/useRoutingAPITrade.test.ts +++ b/src/state/routing/useRoutingAPITrade.test.ts @@ -2,7 +2,6 @@ import { skipToken } from '@reduxjs/toolkit/query/react' import { renderHook } from '@testing-library/react' import { CurrencyAmount, TradeType } from '@uniswap/sdk-core' import { AVERAGE_L1_BLOCK_TIME } from 'constants/chainInfo' -import { ZERO_PERCENT } from 'constants/misc' import { USDC_MAINNET } from 'constants/tokens' import { useUniswapXDefaultEnabled } from 'featureFlags/flags/uniswapXDefault' import { useUniswapXEthOutputEnabled } from 'featureFlags/flags/uniswapXEthOutput' @@ -79,8 +78,6 @@ const MOCK_ARGS: GetQuoteArgs = { uniswapXExactOutputEnabled: false, isUniswapXDefaultEnabled: false, sendPortionEnabled: true, - inputTax: ZERO_PERCENT, - outputTax: ZERO_PERCENT, } describe('#useRoutingAPITrade ExactIn', () => { diff --git a/src/state/routing/useRoutingAPITrade.ts b/src/state/routing/useRoutingAPITrade.ts index 442ec4de89..0e92fa8c60 100644 --- a/src/state/routing/useRoutingAPITrade.ts +++ b/src/state/routing/useRoutingAPITrade.ts @@ -1,7 +1,6 @@ import { skipToken } from '@reduxjs/toolkit/query/react' import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core' import { AVERAGE_L1_BLOCK_TIME } from 'constants/chainInfo' -import { ZERO_PERCENT } from 'constants/misc' import useIsWindowVisible from 'hooks/useIsWindowVisible' import { useRoutingAPIArguments } from 'lib/hooks/routing/useRoutingAPIArguments' import ms from 'ms' @@ -65,9 +64,7 @@ export function useRoutingAPITrade( amountSpecified: CurrencyAmount | undefined, otherCurrency: Currency | undefined, routerPreference: RouterPreference | typeof INTERNAL_ROUTER_PREFERENCE_PRICE, - account?: string, - inputTax = ZERO_PERCENT, - outputTax = ZERO_PERCENT + account?: string ): { state: TradeState trade?: SubmittableTrade @@ -90,8 +87,6 @@ export function useRoutingAPITrade( amount: amountSpecified, tradeType, routerPreference, - inputTax, - outputTax, }) // skip all pricing and quote requests if the window is not focused const isWindowVisible = useIsWindowVisible() diff --git a/src/state/routing/utils.test.ts b/src/state/routing/utils.test.ts index eb3ae002db..4be97c2f79 100644 --- a/src/state/routing/utils.test.ts +++ b/src/state/routing/utils.test.ts @@ -1,26 +1,69 @@ -import { ChainId, Token } from '@uniswap/sdk-core' +import { ChainId, Currency, Token, TradeType } from '@uniswap/sdk-core' import { nativeOnChain } from 'constants/tokens' -import { PoolType } from './types' +import { GetQuoteArgs, PoolType, RouterPreference, TokenInRoute } from './types' import { computeRoutes } from './utils' -const USDC = new Token(1, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 6, 'USDC') -const DAI = new Token(1, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 6, 'DAI') -const MKR = new Token(1, '0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2', 6, 'MKR') +const USDC = new Token(1, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 6, 'USDC', undefined, false) +const USDC_IN_ROUTE = toTokenInRoute(USDC) +const DAI = new Token(1, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 6, 'DAI', undefined, false) +const DAI_IN_ROUTE = toTokenInRoute(DAI) +const MKR = new Token(1, '0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2', 6, 'MKR', undefined, false) +const MKR_IN_ROUTE = toTokenInRoute(MKR) const ETH = nativeOnChain(ChainId.MAINNET) +const WETH_IN_ROUTE = toTokenInRoute(ETH.wrapped) // helper function to make amounts more readable const amount = (raw: TemplateStringsArray) => (parseInt(raw[0]) * 1e6).toString() +const BASE_ARGS = { + amount: '100', + routerPreference: RouterPreference.API, + tradeType: TradeType.EXACT_INPUT, + needsWrapIfUniswapX: false, + uniswapXForceSyntheticQuotes: false, + uniswapXEthOutputEnabled: true, + uniswapXExactOutputEnabled: true, + userDisabledUniswapX: false, + userOptedOutOfUniswapX: false, + isUniswapXDefaultEnabled: false, + sendPortionEnabled: true, +} + +function constructArgs(currencyIn: Currency, currencyOut: Currency): GetQuoteArgs { + return { + ...BASE_ARGS, + tokenInAddress: currencyIn.isNative ? 'ETH' : currencyIn.address, + tokenInChainId: currencyIn.chainId, + tokenInDecimals: currencyIn.decimals, + tokenInSymbol: currencyIn.symbol, + tokenOutAddress: currencyOut.isNative ? 'ETH' : currencyOut.address, + tokenOutChainId: currencyOut.chainId, + tokenOutDecimals: currencyOut.decimals, + tokenOutSymbol: currencyOut.symbol, + } +} + +function toTokenInRoute(token: Token): TokenInRoute { + return { + address: token.address, + chainId: token.chainId, + symbol: token.symbol, + decimals: token.decimals, + buyFeeBps: token.buyFeeBps?.toString(), + sellFeeBps: token.sellFeeBps?.toString(), + } +} + describe('#useRoute', () => { it('handles empty edges and nodes', () => { - const result = computeRoutes(USDC, DAI, []) + const result = computeRoutes(constructArgs(USDC, DAI), []) expect(result).toEqual([]) }) it('handles a single route trade from DAI to USDC from v3', () => { - const result = computeRoutes(DAI, USDC, [ + const result = computeRoutes(constructArgs(DAI, USDC), [ [ { type: 'v3-pool', @@ -31,8 +74,8 @@ describe('#useRoute', () => { sqrtRatioX96: '2437312313659959819381354528', liquidity: '10272714736694327408', tickCurrent: '-69633', - tokenIn: DAI, - tokenOut: USDC, + tokenIn: toTokenInRoute(DAI), + tokenOut: toTokenInRoute(USDC), }, ], ]) @@ -50,21 +93,21 @@ describe('#useRoute', () => { }) it('handles a single route trade from DAI to USDC from v2', () => { - const result = computeRoutes(DAI, USDC, [ + const result = computeRoutes(constructArgs(DAI, USDC), [ [ { type: 'v2-pool', address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2', amountIn: amount`1`, amountOut: amount`5`, - tokenIn: DAI, - tokenOut: USDC, + tokenIn: DAI_IN_ROUTE, + tokenOut: USDC_IN_ROUTE, reserve0: { - token: DAI, + token: DAI_IN_ROUTE, quotient: amount`100`, }, reserve1: { - token: USDC, + token: USDC_IN_ROUTE, quotient: amount`200`, }, }, @@ -84,21 +127,21 @@ describe('#useRoute', () => { }) it('handles a multi-route trade from DAI to USDC', () => { - const result = computeRoutes(DAI, USDC, [ + const result = computeRoutes(constructArgs(DAI, USDC), [ [ { type: 'v2-pool', address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2', amountIn: amount`5`, amountOut: amount`6`, - tokenIn: DAI, - tokenOut: USDC, + tokenIn: DAI_IN_ROUTE, + tokenOut: USDC_IN_ROUTE, reserve0: { - token: DAI, + token: DAI_IN_ROUTE, quotient: amount`1000`, }, reserve1: { - token: USDC, + token: USDC_IN_ROUTE, quotient: amount`500`, }, }, @@ -110,8 +153,8 @@ describe('#useRoute', () => { amountIn: amount`10`, amountOut: amount`1`, fee: '3000', - tokenIn: DAI, - tokenOut: MKR, + tokenIn: DAI_IN_ROUTE, + tokenOut: MKR_IN_ROUTE, sqrtRatioX96: '2437312313659959819381354528', liquidity: '10272714736694327408', tickCurrent: '-69633', @@ -122,8 +165,8 @@ describe('#useRoute', () => { amountIn: amount`1`, amountOut: amount`200`, fee: '10000', - tokenIn: MKR, - tokenOut: USDC, + tokenIn: MKR_IN_ROUTE, + tokenOut: USDC_IN_ROUTE, sqrtRatioX96: '2437312313659959819381354528', liquidity: '10272714736694327408', tickCurrent: '-69633', @@ -151,7 +194,7 @@ describe('#useRoute', () => { }) it('handles a single route trade with same token pair, different fee tiers', () => { - const result = computeRoutes(DAI, USDC, [ + const result = computeRoutes(constructArgs(DAI, USDC), [ [ { type: 'v3-pool', @@ -159,8 +202,8 @@ describe('#useRoute', () => { amountIn: amount`1`, amountOut: amount`5`, fee: '500', - tokenIn: DAI, - tokenOut: USDC, + tokenIn: DAI_IN_ROUTE, + tokenOut: USDC_IN_ROUTE, sqrtRatioX96: '2437312313659959819381354528', liquidity: '10272714736694327408', tickCurrent: '-69633', @@ -173,8 +216,8 @@ describe('#useRoute', () => { amountIn: amount`10`, amountOut: amount`50`, fee: '3000', - tokenIn: DAI, - tokenOut: USDC, + tokenIn: DAI_IN_ROUTE, + tokenOut: USDC_IN_ROUTE, sqrtRatioX96: '2437312313659959819381354528', liquidity: '10272714736694327408', tickCurrent: '-69633', @@ -191,7 +234,7 @@ describe('#useRoute', () => { }) it('computes mixed routes correctly', () => { - const result = computeRoutes(DAI, MKR, [ + const result = computeRoutes(constructArgs(DAI, MKR), [ [ { type: PoolType.V3Pool, @@ -199,8 +242,8 @@ describe('#useRoute', () => { amountIn: amount`1`, amountOut: amount`5`, fee: '500', - tokenIn: DAI, - tokenOut: USDC, + tokenIn: DAI_IN_ROUTE, + tokenOut: USDC_IN_ROUTE, sqrtRatioX96: '2437312313659959819381354528', liquidity: '10272714736694327408', tickCurrent: '-69633', @@ -210,14 +253,14 @@ describe('#useRoute', () => { address: 'x2f8F72aA9304c8B593d555F12eF6589cC3A579A2', amountIn: amount`10`, amountOut: amount`50`, - tokenIn: USDC, - tokenOut: MKR, + tokenIn: USDC_IN_ROUTE, + tokenOut: MKR_IN_ROUTE, reserve0: { - token: USDC, + token: USDC_IN_ROUTE, quotient: amount`100`, }, reserve1: { - token: MKR, + token: MKR_IN_ROUTE, quotient: amount`200`, }, }, @@ -236,7 +279,7 @@ describe('#useRoute', () => { it('outputs native ETH as input currency', () => { const WETH = ETH.wrapped - const result = computeRoutes(ETH, USDC, [ + const result = computeRoutes(constructArgs(ETH, USDC), [ [ { type: 'v3-pool', @@ -247,8 +290,8 @@ describe('#useRoute', () => { sqrtRatioX96: '2437312313659959819381354528', liquidity: '10272714736694327408', tickCurrent: '-69633', - tokenIn: WETH, - tokenOut: USDC, + tokenIn: WETH_IN_ROUTE, + tokenOut: USDC_IN_ROUTE, }, ], ]) @@ -263,7 +306,7 @@ describe('#useRoute', () => { it('outputs native ETH as output currency', () => { const WETH = new Token(1, ETH.wrapped.address, 18, 'WETH') - const result = computeRoutes(USDC, ETH, [ + const result = computeRoutes(constructArgs(USDC, ETH), [ [ { type: 'v3-pool', @@ -274,8 +317,8 @@ describe('#useRoute', () => { sqrtRatioX96: '2437312313659959819381354528', liquidity: '10272714736694327408', tickCurrent: '-69633', - tokenIn: USDC, - tokenOut: WETH, + tokenIn: USDC_IN_ROUTE, + tokenOut: WETH_IN_ROUTE, }, ], ]) @@ -290,21 +333,21 @@ describe('#useRoute', () => { it('outputs native ETH as input currency for v2 routes', () => { const WETH = ETH.wrapped - const result = computeRoutes(ETH, USDC, [ + const result = computeRoutes(constructArgs(ETH, USDC), [ [ { type: 'v2-pool', address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2', amountIn: (1e18).toString(), amountOut: amount`5`, - tokenIn: WETH, - tokenOut: USDC, + tokenIn: WETH_IN_ROUTE, + tokenOut: USDC_IN_ROUTE, reserve0: { - token: WETH, + token: WETH_IN_ROUTE, quotient: amount`100`, }, reserve1: { - token: USDC, + token: USDC_IN_ROUTE, quotient: amount`200`, }, }, @@ -321,21 +364,21 @@ describe('#useRoute', () => { it('outputs native ETH as output currency for v2 routes', () => { const WETH = new Token(1, ETH.wrapped.address, 18, 'WETH') - const result = computeRoutes(USDC, ETH, [ + const result = computeRoutes(constructArgs(USDC, ETH), [ [ { type: 'v2-pool', address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2', amountIn: amount`5`, amountOut: (1e18).toString(), - tokenIn: USDC, - tokenOut: WETH, + tokenIn: USDC_IN_ROUTE, + tokenOut: WETH_IN_ROUTE, reserve0: { - token: WETH, + token: WETH_IN_ROUTE, quotient: amount`100`, }, reserve1: { - token: USDC, + token: USDC_IN_ROUTE, quotient: amount`200`, }, }, diff --git a/src/state/routing/utils.ts b/src/state/routing/utils.ts index fba9e2ec85..a5bf04a8b9 100644 --- a/src/state/routing/utils.ts +++ b/src/state/routing/utils.ts @@ -26,6 +26,7 @@ import { SubmittableTrade, SwapFeeInfo, SwapRouterNativeAssets, + TokenInRoute, TradeFillType, TradeResult, URADutchOrderQuoteData, @@ -47,16 +48,9 @@ interface RouteResult { * Transforms a Routing API quote into an array of routes that can be used to * create a `Trade`. */ -export function computeRoutes( - currencyIn: Currency, - currencyOut: Currency, - routes: ClassicQuoteData['route'] -): RouteResult[] | undefined { +export function computeRoutes(args: GetQuoteArgs, routes: ClassicQuoteData['route']): RouteResult[] | undefined { if (routes.length === 0) return [] - - const tokenIn = routes[0]?.[0]?.tokenIn - const tokenOut = routes[0]?.[routes[0]?.length - 1]?.tokenOut - if (!tokenIn || !tokenOut) throw new Error('Expected both tokenIn and tokenOut to be present') + const [currencyIn, currencyOut] = getTradeCurrencies(args, false, routes) try { return routes.map((route) => { @@ -121,7 +115,11 @@ function toDutchOrderInfo(orderInfoJSON: DutchOrderInfoJSON): DutchOrderInfo { // Prepares the currencies used for the actual Swap (either UniswapX or Universal Router) // May not match `currencyIn` that the user selected because for ETH inputs in UniswapX, the actual // swap will use WETH. -function getTradeCurrencies(args: GetQuoteArgs | GetQuickQuoteArgs, isUniswapXTrade: boolean): [Currency, Currency] { +function getTradeCurrencies( + args: GetQuoteArgs | GetQuickQuoteArgs, + isUniswapXTrade: boolean, + routes?: ClassicQuoteData['route'] +): [Currency, Currency] { const { tokenInAddress, tokenInChainId, @@ -136,9 +134,19 @@ function getTradeCurrencies(args: GetQuoteArgs | GetQuickQuoteArgs, isUniswapXTr const tokenInIsNative = Object.values(SwapRouterNativeAssets).includes(tokenInAddress as SwapRouterNativeAssets) const tokenOutIsNative = Object.values(SwapRouterNativeAssets).includes(tokenOutAddress as SwapRouterNativeAssets) + const serializedTokenIn = routes?.[0]?.[0]?.tokenIn + const serializedTokenOut = routes?.[0]?.[routes[0]?.length - 1]?.tokenOut + const currencyIn = tokenInIsNative ? nativeOnChain(tokenInChainId) - : parseToken({ address: tokenInAddress, chainId: tokenInChainId, decimals: tokenInDecimals, symbol: tokenInSymbol }) + : parseToken({ + address: tokenInAddress, + chainId: tokenInChainId, + decimals: tokenInDecimals, + symbol: tokenInSymbol, + buyFeeBps: serializedTokenIn?.buyFeeBps, + sellFeeBps: serializedTokenIn?.sellFeeBps, + }) const currencyOut = tokenOutIsNative ? nativeOnChain(tokenOutChainId) : parseToken({ @@ -146,6 +154,8 @@ function getTradeCurrencies(args: GetQuoteArgs | GetQuickQuoteArgs, isUniswapXTr chainId: tokenOutChainId, decimals: tokenOutDecimals, symbol: tokenOutSymbol, + buyFeeBps: serializedTokenOut?.buyFeeBps, + sellFeeBps: serializedTokenOut?.sellFeeBps, }) if (!isUniswapXTrade) { @@ -168,8 +178,7 @@ function getSwapFee(data: ClassicQuoteData | URADutchOrderQuoteData): SwapFeeInf } function getClassicTradeDetails( - currencyIn: Currency, - currencyOut: Currency, + args: GetQuoteArgs, data: URAQuoteResponse ): { gasUseEstimate?: number @@ -181,54 +190,43 @@ function getClassicTradeDetails( const classicQuote = data.routing === URAQuoteType.CLASSIC ? data.quote : data.allQuotes.find(isClassicQuoteResponse)?.quote - if (!classicQuote) return {} + if (!classicQuote) { + return {} + } return { gasUseEstimate: classicQuote.gasUseEstimate ? parseFloat(classicQuote.gasUseEstimate) : undefined, gasUseEstimateUSD: classicQuote.gasUseEstimateUSD ? parseFloat(classicQuote.gasUseEstimateUSD) : undefined, blockNumber: classicQuote.blockNumber, - routes: computeRoutes(currencyIn, currencyOut, classicQuote.route), + routes: computeRoutes(args, classicQuote.route), swapFee: getSwapFee(classicQuote), } } export function transformQuickRouteToTrade(args: GetQuickQuoteArgs, data: QuickRouteResponse): PreviewTrade { - const { amount, tradeType, inputTax, outputTax } = args + const { amount, tradeType } = args const [currencyIn, currencyOut] = getTradeCurrencies(args, false) const [rawAmountIn, rawAmountOut] = data.tradeType === 'EXACT_IN' ? [amount, data.quote.amount] : [data.quote.amount, amount] const inputAmount = CurrencyAmount.fromRawAmount(currencyIn, rawAmountIn) const outputAmount = CurrencyAmount.fromRawAmount(currencyOut, rawAmountOut) - return new PreviewTrade({ inputAmount, outputAmount, tradeType, inputTax, outputTax }) + return new PreviewTrade({ inputAmount, outputAmount, tradeType }) } -export async function transformRoutesToTrade( +export async function transformQuoteToTrade( args: GetQuoteArgs, data: URAQuoteResponse, quoteMethod: QuoteMethod ): Promise { - const { - tradeType, - needsWrapIfUniswapX, - routerPreference, - account, - amount, - isUniswapXDefaultEnabled, - inputTax, - outputTax, - } = args + const { tradeType, needsWrapIfUniswapX, routerPreference, account, amount, isUniswapXDefaultEnabled } = args const showUniswapXTrade = data.routing === URAQuoteType.DUTCH_LIMIT && (routerPreference === RouterPreference.X || (isUniswapXDefaultEnabled && routerPreference === RouterPreference.API)) - const [currencyIn, currencyOut] = getTradeCurrencies(args, showUniswapXTrade) - const { gasUseEstimateUSD, blockNumber, routes, gasUseEstimate, swapFee } = getClassicTradeDetails( - currencyIn, - currencyOut, - data - ) + + const { gasUseEstimateUSD, blockNumber, routes, gasUseEstimate, swapFee } = getClassicTradeDetails(args, data) // If the top-level URA quote type is DUTCH_LIMIT, then UniswapX is better for the user const isUniswapXBetter = data.routing === URAQuoteType.DUTCH_LIMIT @@ -272,8 +270,6 @@ export async function transformRoutesToTrade( isUniswapXBetter, requestId: data.quote.requestId, quoteMethod, - inputTax, - outputTax, swapFee, }) @@ -310,8 +306,10 @@ export async function transformRoutesToTrade( return { state: QuoteState.SUCCESS, trade: classicTrade } } -function parseToken({ address, chainId, decimals, symbol }: ClassicQuoteData['route'][0][0]['tokenIn']): Token { - return new Token(chainId, address, parseInt(decimals.toString()), symbol) +function parseToken({ address, chainId, decimals, symbol, buyFeeBps, sellFeeBps }: TokenInRoute): Token { + const buyFeeBpsBN = buyFeeBps ? BigNumber.from(buyFeeBps) : undefined + const sellFeeBpsBN = sellFeeBps ? BigNumber.from(sellFeeBps) : undefined + return new Token(chainId, address, parseInt(decimals.toString()), symbol, undefined, false, buyFeeBpsBN, sellFeeBpsBN) } function parsePool({ fee, sqrtRatioX96, liquidity, tickCurrent, tokenIn, tokenOut }: V3PoolInRoute): Pool { diff --git a/src/state/swap/hooks.tsx b/src/state/swap/hooks.tsx index b058e22a77..b9126a4499 100644 --- a/src/state/swap/hooks.tsx +++ b/src/state/swap/hooks.tsx @@ -2,7 +2,6 @@ import { Trans } from '@lingui/macro' import { ChainId, Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core' import { useWeb3React } from '@web3-react/core' import { useConnectionReady } from 'connection/eagerlyConnect' -import { useFotAdjustmentsEnabled } from 'featureFlags/flags/fotAdjustments' import useAutoSlippageTolerance from 'hooks/useAutoSlippageTolerance' import { useDebouncedTrade } from 'hooks/useDebouncedTrade' import { useSwapTaxes } from 'hooks/useSwapTaxes' @@ -112,15 +111,14 @@ export function useDerivedSwapInfo(state: SwapState, chainId: ChainId | undefine const inputCurrency = useCurrency(inputCurrencyId, chainId) const outputCurrency = useCurrency(outputCurrencyId, chainId) - const fotAdjustmentsEnabled = useFotAdjustmentsEnabled() - const { inputTax, outputTax } = useSwapTaxes( - inputCurrency?.isToken && fotAdjustmentsEnabled ? inputCurrency.address : undefined, - outputCurrency?.isToken && fotAdjustmentsEnabled ? outputCurrency.address : undefined - ) - const recipientLookup = useENS(recipient ?? undefined) const to: string | null = (recipient === null ? account : recipientLookup.address) ?? null + const { inputTax, outputTax } = useSwapTaxes( + inputCurrency?.isToken ? inputCurrency.address : undefined, + outputCurrency?.isToken ? outputCurrency.address : undefined + ) + const relevantTokenBalances = useCurrencyBalances( account ?? undefined, useMemo(() => [inputCurrency ?? undefined, outputCurrency ?? undefined], [inputCurrency, outputCurrency]) @@ -137,9 +135,7 @@ export function useDerivedSwapInfo(state: SwapState, chainId: ChainId | undefine parsedAmount, (isExactIn ? outputCurrency : inputCurrency) ?? undefined, undefined, - account, - inputTax, - outputTax + account ) const { data: outputFeeFiatValue } = useUSDPrice( @@ -222,9 +218,9 @@ export function useDerivedSwapInfo(state: SwapState, chainId: ChainId | undefine trade, autoSlippage, allowedSlippage, + outputFeeFiatValue, inputTax, outputTax, - outputFeeFiatValue, }), [ allowedSlippage, @@ -232,11 +228,11 @@ export function useDerivedSwapInfo(state: SwapState, chainId: ChainId | undefine currencies, currencyBalances, inputError, - inputTax, outputFeeFiatValue, - outputTax, parsedAmount, trade, + inputTax, + outputTax, ] ) } diff --git a/src/test-utils/constants.ts b/src/test-utils/constants.ts index 49cf60fe57..fedc2fc1b6 100644 --- a/src/test-utils/constants.ts +++ b/src/test-utils/constants.ts @@ -3,7 +3,6 @@ import { ChainId, CurrencyAmount, Percent, Token, TradeType } from '@uniswap/sdk // eslint-disable-next-line @typescript-eslint/no-restricted-imports 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' @@ -49,8 +48,6 @@ export const TEST_TRADE_EXACT_INPUT = new ClassicTrade({ gasUseEstimateUSD: 1.0, approveInfo: { needsApprove: false }, quoteMethod: QuoteMethod.CLIENT_SIDE_FALLBACK, - inputTax: ZERO_PERCENT, - outputTax: ZERO_PERCENT, }) export const TEST_TRADE_EXACT_INPUT_API = new ClassicTrade({ @@ -66,8 +63,6 @@ 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({ @@ -82,8 +77,6 @@ export const TEST_TRADE_EXACT_OUTPUT = new ClassicTrade({ tradeType: TradeType.EXACT_OUTPUT, quoteMethod: QuoteMethod.CLIENT_SIDE_FALLBACK, approveInfo: { needsApprove: false }, - inputTax: ZERO_PERCENT, - outputTax: ZERO_PERCENT, }) export const TEST_ALLOWED_SLIPPAGE = new Percent(2, 100) @@ -127,11 +120,29 @@ export const TEST_DUTCH_TRADE_ETH_INPUT = new DutchOrderTrade({ slippageTolerance: new Percent(5, 100), }) +const SELL_FEE_TOKEN = new Token( + 1, + '0x0000000000000000000000000000000000000001', + 18, + 'ABC', + 'Abc', + false, + undefined, + BigNumber.from(300) +) +const TEST_POOL_FOT_1 = new Pool( + SELL_FEE_TOKEN, + TEST_TOKEN_2, + FeeAmount.HIGH, + '2437312313659959819381354528', + '10272714736694327408', + -69633 +) 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), + routev3: new V3Route([TEST_POOL_FOT_1], SELL_FEE_TOKEN, TEST_TOKEN_2), + inputAmount: toCurrencyAmount(SELL_FEE_TOKEN, 1000), outputAmount: toCurrencyAmount(TEST_TOKEN_2, 1000), }, ], @@ -140,16 +151,32 @@ export const TEST_TRADE_FEE_ON_SELL = new ClassicTrade({ gasUseEstimateUSD: 1.0, approveInfo: { needsApprove: false }, quoteMethod: QuoteMethod.ROUTING_API, - inputTax: new Percent(3, 100), - outputTax: ZERO_PERCENT, }) +const BUY_FEE_TOKEN = new Token( + 1, + '0x0000000000000000000000000000000000000002', + 18, + 'DEF', + 'Def', + false, + BigNumber.from(300), + undefined +) +const TEST_POOL_FOT_2 = new Pool( + TEST_TOKEN_1, + BUY_FEE_TOKEN, + FeeAmount.HIGH, + '2437312313659959819381354528', + '10272714736694327408', + -69633 +) export const TEST_TRADE_FEE_ON_BUY = new ClassicTrade({ v3Routes: [ { - routev3: new V3Route([TEST_POOL_12], TEST_TOKEN_1, TEST_TOKEN_2), + routev3: new V3Route([TEST_POOL_FOT_2], TEST_TOKEN_1, BUY_FEE_TOKEN), inputAmount: toCurrencyAmount(TEST_TOKEN_1, 1000), - outputAmount: toCurrencyAmount(TEST_TOKEN_2, 1000), + outputAmount: toCurrencyAmount(BUY_FEE_TOKEN, 1000), }, ], v2Routes: [], @@ -157,14 +184,10 @@ export const TEST_TRADE_FEE_ON_BUY = new ClassicTrade({ gasUseEstimateUSD: 1.0, approveInfo: { needsApprove: false }, quoteMethod: QuoteMethod.ROUTING_API, - inputTax: ZERO_PERCENT, - outputTax: new Percent(3, 100), }) export const PREVIEW_EXACT_IN_TRADE = new PreviewTrade({ inputAmount: toCurrencyAmount(TEST_TOKEN_1, 1000), outputAmount: toCurrencyAmount(TEST_TOKEN_2, 1000), tradeType: TradeType.EXACT_INPUT, - inputTax: new Percent(0, 100), - outputTax: new Percent(0, 100), }) diff --git a/yarn.lock b/yarn.lock index 4bccd17e62..85a3d1dd57 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6112,18 +6112,18 @@ resolved "https://registry.yarnpkg.com/@uniswap/redux-multicall/-/redux-multicall-1.1.8.tgz#9cc5090305b10df68fb6162eb1ba7c2c762f5e7f" integrity sha512-LttOBVJuoRNC6N4MHsb5dF2GszLsj1ddPKKccEw1XOX17bGrFdm2A6GwKgES+v+Hj3lluDbQL6atcQtymP21iw== -"@uniswap/router-sdk@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@uniswap/router-sdk/-/router-sdk-1.6.0.tgz#2f51dbba1b01467244b59500ed0da8aa84323f8c" - integrity sha512-onpAzcvEnrsm8tUtu49IrR9EP3n9j0IDpGc0Ee3FDDkVgXrp9cIrAADC+yb56vgLtJFnshbhyIdjXLMIzWe0Gw== +"@uniswap/router-sdk@^1.6.0", "@uniswap/router-sdk@^1.7.1": + version "1.7.1" + resolved "https://registry.yarnpkg.com/@uniswap/router-sdk/-/router-sdk-1.7.1.tgz#642d5804299cd50b1a3ba2fa0a87963320fb7f93" + integrity sha512-uBN9QX3t5lPLkxlkPoQPZpd0eN+GA0Ab9nq1pcPk/XDFuRnRxxVF629Ecz2SfTVm0gooOPO3aU3ETgyB3vuhYA== dependencies: "@ethersproject/abi" "^5.5.0" - "@uniswap/sdk-core" "^4" - "@uniswap/swap-router-contracts" "1.1.0" + "@uniswap/sdk-core" "^4.0.7" + "@uniswap/swap-router-contracts" "^1.1.0" "@uniswap/v2-sdk" "^3.2.0" "@uniswap/v3-sdk" "^3.10.0" -"@uniswap/sdk-core@4.0.7", "@uniswap/sdk-core@>= 3", "@uniswap/sdk-core@^4", "@uniswap/sdk-core@^4.0.0", "@uniswap/sdk-core@^4.0.2", "@uniswap/sdk-core@^4.0.3", "@uniswap/sdk-core@^4.0.6": +"@uniswap/sdk-core@4.0.7", "@uniswap/sdk-core@>= 3", "@uniswap/sdk-core@^4", "@uniswap/sdk-core@^4.0.0", "@uniswap/sdk-core@^4.0.2", "@uniswap/sdk-core@^4.0.3", "@uniswap/sdk-core@^4.0.6", "@uniswap/sdk-core@^4.0.7": version "4.0.7" resolved "https://registry.npmjs.org/@uniswap/sdk-core/-/sdk-core-4.0.7.tgz#90dfd070d7e44494234618af398da158363ae827" integrity sha512-jscx7KUIWzQatcL5PHY6xy0gEL9IGQcL5h/obxzX9foP2KoNk9cq66Ia8I2Kvpa7zBcPOeW1hU0hJNBq6CzcIQ== @@ -6163,26 +6163,15 @@ node-cache "^5.1.2" stats-lite "^2.2.0" -"@uniswap/swap-router-contracts@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@uniswap/swap-router-contracts/-/swap-router-contracts-1.1.0.tgz#e027b14d4c172f231c53c48e1fd708a78d7d94d8" - integrity sha512-GPmpx1lvjXWloB95+YUabr3UHJYr3scnSS8EzaNXnNrIz9nYZ+XQcMaJxOKe85Yi7IfcUQpj0HzD2TW99dtolA== - dependencies: - "@openzeppelin/contracts" "3.4.1-solc-0.7-2" - "@uniswap/v2-core" "1.0.1" - "@uniswap/v3-core" "1.0.0" - "@uniswap/v3-periphery" "1.3.0" - hardhat-watcher "^2.1.1" - -"@uniswap/swap-router-contracts@^1.2.1", "@uniswap/swap-router-contracts@^1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@uniswap/swap-router-contracts/-/swap-router-contracts-1.3.0.tgz#8d555ca6d74b888d6e02a26ebb806ce315605f1f" - integrity sha512-iKvCuRkHXEe0EMjOf8HFUISTIhlxI57kKFllf3C3PUIE0HmwxrayyoflwAz5u/TRsFGYqJ9IjX2UgzLCsrNa5A== +"@uniswap/swap-router-contracts@^1.1.0", "@uniswap/swap-router-contracts@^1.2.1", "@uniswap/swap-router-contracts@^1.3.0": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@uniswap/swap-router-contracts/-/swap-router-contracts-1.3.1.tgz#0ebbb93eb578625618ed9489872de381f9c66fb4" + integrity sha512-mh/YNbwKb7Mut96VuEtL+Z5bRe0xVIbjjiryn+iMMrK2sFKhR4duk/86mEz0UO5gSx4pQIw9G5276P5heY/7Rg== dependencies: "@openzeppelin/contracts" "3.4.2-solc-0.7" - "@uniswap/v2-core" "1.0.1" - "@uniswap/v3-core" "1.0.0" - "@uniswap/v3-periphery" "1.4.1" + "@uniswap/v2-core" "^1.0.1" + "@uniswap/v3-core" "^1.0.0" + "@uniswap/v3-periphery" "^1.4.4" dotenv "^14.2.0" hardhat-watcher "^2.1.1" @@ -6259,34 +6248,21 @@ resolved "https://registry.npmjs.org/@uniswap/v3-core/-/v3-core-1.0.0.tgz" integrity sha512-kSC4djMGKMHj7sLMYVnn61k9nu+lHjMIxgg9CDQT+s2QYLoA56GbSK9Oxr+qJXzzygbkrmuY6cwgP6cW2JXPFA== -"@uniswap/v3-core@^1.0.1": +"@uniswap/v3-core@^1.0.0", "@uniswap/v3-core@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@uniswap/v3-core/-/v3-core-1.0.1.tgz#b6d2bdc6ba3c3fbd610bdc502395d86cd35264a0" integrity sha512-7pVk4hEm00j9tc71Y9+ssYpO6ytkeI0y7WE9P6UcmNzhxPePwyAxImuhVsTqWK9YFvzgtvzJHi64pBl4jUzKMQ== -"@uniswap/v3-periphery@1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@uniswap/v3-periphery/-/v3-periphery-1.3.0.tgz#37f0a1ef6025221722e50e9f3f2009c2d5d6e4ec" - integrity sha512-HjHdI5RkjBl8zz3bqHShrbULFoZSrjbbrRHoO2vbzn+WRzTa6xY4PWphZv2Tlcb38YEKfKHp6NPl5hVedac8uw== - dependencies: - "@openzeppelin/contracts" "3.4.1-solc-0.7-2" - "@uniswap/lib" "^4.0.1-alpha" - "@uniswap/v2-core" "1.0.1" - "@uniswap/v3-core" "1.0.0" - base64-sol "1.0.1" - hardhat-watcher "^2.1.1" - -"@uniswap/v3-periphery@1.4.1", "@uniswap/v3-periphery@^1.0.1", "@uniswap/v3-periphery@^1.1.1": - version "1.4.1" - resolved "https://registry.yarnpkg.com/@uniswap/v3-periphery/-/v3-periphery-1.4.1.tgz#b90f08b7386163c0abfd7258831caef6339c7862" - integrity sha512-Ab0ZCKOQrQMKIcpBTezTsEhWfQjItd0TtkCG8mPhoQu+wC67nPaf4hYUhM6wGHeFUmDiYY5MpEQuokB0ENvoTg== +"@uniswap/v3-periphery@^1.0.1", "@uniswap/v3-periphery@^1.1.1", "@uniswap/v3-periphery@^1.4.4": + version "1.4.4" + resolved "https://registry.yarnpkg.com/@uniswap/v3-periphery/-/v3-periphery-1.4.4.tgz#d2756c23b69718173c5874f37fd4ad57d2f021b7" + integrity sha512-S4+m+wh8HbWSO3DKk4LwUCPZJTpCugIsHrWR86m/OrUyvSqGDTXKFfc2sMuGXCZrD1ZqO3rhQsKgdWg3Hbb2Kw== dependencies: "@openzeppelin/contracts" "3.4.2-solc-0.7" "@uniswap/lib" "^4.0.1-alpha" - "@uniswap/v2-core" "1.0.1" - "@uniswap/v3-core" "1.0.0" + "@uniswap/v2-core" "^1.0.1" + "@uniswap/v3-core" "^1.0.0" base64-sol "1.0.1" - hardhat-watcher "^2.1.1" "@uniswap/v3-sdk@^3.10.0": version "3.10.0"