diff --git a/cypress/e2e/swap/fees.test.ts b/cypress/e2e/swap/fees.test.ts index aaae86b2c8..af811f81e6 100644 --- a/cypress/e2e/swap/fees.test.ts +++ b/cypress/e2e/swap/fees.test.ts @@ -120,10 +120,7 @@ describe('Swap with fees', () => { describe('UniswapX swaps', () => { it('displays UniswapX fee in UI', () => { cy.visit('/swap', { - featureFlags: [ - { name: FeatureFlag.feesEnabled, value: true }, - { name: FeatureFlag.uniswapXDefaultEnabled, value: true }, - ], + featureFlags: [{ name: FeatureFlag.feesEnabled, value: true }], }) // Intercept the trade quote diff --git a/cypress/e2e/swap/uniswapx.test.ts b/cypress/e2e/swap/uniswapx.test.ts index c282568c2e..ec77ca0c36 100644 --- a/cypress/e2e/swap/uniswapx.test.ts +++ b/cypress/e2e/swap/uniswapx.test.ts @@ -1,6 +1,5 @@ import { ChainId, CurrencyAmount } from '@uniswap/sdk-core' import { CyHttpMessages } from 'cypress/types/net-stubbing' -import { FeatureFlag } from 'featureFlags' import { DAI, nativeOnChain, USDC_MAINNET } from '../../../src/constants/tokens' import { getTestSelector } from '../../utils' @@ -45,9 +44,7 @@ function stubSwapTxReceipt() { describe('UniswapX Toggle', () => { beforeEach(() => { stubNonPriceQuoteWith(QuoteWhereUniswapXIsBetter) - cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`, { - featureFlags: [{ name: FeatureFlag.uniswapXDefaultEnabled, value: true }], - }) + cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`) }) it('displays uniswapx ui when setting is on', () => { @@ -69,9 +66,7 @@ describe('UniswapX Orders', () => { stubSwapTxReceipt() cy.hardhat().then((hardhat) => hardhat.fund(hardhat.wallet, CurrencyAmount.fromRawAmount(USDC_MAINNET, 3e8))) - cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`, { - featureFlags: [{ name: FeatureFlag.uniswapXDefaultEnabled, value: true }], - }) + cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`) }) it('can swap exact-in trades using uniswapX', () => { @@ -159,9 +154,7 @@ describe('UniswapX Eth Input', () => { stubSwapTxReceipt() - cy.visit(`/swap/?inputCurrency=ETH&outputCurrency=${DAI.address}`, { - featureFlags: [{ name: FeatureFlag.uniswapXDefaultEnabled, value: true }], - }) + cy.visit(`/swap/?inputCurrency=ETH&outputCurrency=${DAI.address}`) }) it('can swap using uniswapX with ETH as input', () => { @@ -247,9 +240,7 @@ describe('UniswapX activity history', () => { cy.hardhat().then(async (hardhat) => { await hardhat.fund(hardhat.wallet, CurrencyAmount.fromRawAmount(USDC_MAINNET, 3e8)) }) - cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`, { - featureFlags: [{ name: FeatureFlag.uniswapXDefaultEnabled, value: true }], - }) + cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`) }) it('can view UniswapX order status progress in activity', () => { diff --git a/src/components/FeatureFlagModal/FeatureFlagModal.tsx b/src/components/FeatureFlagModal/FeatureFlagModal.tsx index aca7d0f8e1..ae94013c72 100644 --- a/src/components/FeatureFlagModal/FeatureFlagModal.tsx +++ b/src/components/FeatureFlagModal/FeatureFlagModal.tsx @@ -14,7 +14,6 @@ import { useMultichainUXFlag } from 'featureFlags/flags/multichainUx' import { useProgressIndicatorV2Flag } from 'featureFlags/flags/progressIndicatorV2' import { useQuickRouteMainnetFlag } from 'featureFlags/flags/quickRouteMainnet' import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc' -import { useUniswapXDefaultEnabledFlag } from 'featureFlags/flags/uniswapXDefault' import { useUniswapXSyntheticQuoteFlag } from 'featureFlags/flags/uniswapXUseSyntheticQuote' import { useFeesEnabledFlag } from 'featureFlags/flags/useFees' import { useUpdateAtom } from 'jotai/utils' @@ -324,12 +323,6 @@ export default function FeatureFlagModal() { featureFlag={FeatureFlag.uniswapXSyntheticQuote} label="Force synthetic quotes for UniswapX" /> - @@ -46,21 +37,9 @@ export default function RouterPreferenceSettings() { { - if (uniswapXInEffect) { - if (isUniswapXDefaultEnabled) { - // We need to remember if a opts out of UniswapX, so we don't request UniswapX quotes. - dispatch(updateOptedOutOfUniswapX({ optedOutOfUniswapX: true })) - } else { - // We need to remember if a user disables Uniswap X, so we don't show the opt-in flow again. - dispatch(updateDisabledUniswapX({ disabledUniswapX: true })) - } - } - setRouterPreference(uniswapXInEffect ? RouterPreference.API : RouterPreference.X) + setRouterPreference(routerPreference === RouterPreference.X ? RouterPreference.API : RouterPreference.X) }} /> diff --git a/src/components/swap/styled.tsx b/src/components/swap/styled.tsx index 2176852a95..5c4cbda6d3 100644 --- a/src/components/swap/styled.tsx +++ b/src/components/swap/styled.tsx @@ -4,7 +4,6 @@ import { AlertTriangle } from 'react-feather' import styled, { css } from 'styled-components' import { Z_INDEX } from 'theme/zIndex' -import { useIsDarkMode } from '../../theme/components/ThemeToggle' import { AutoColumn } from '../Column' export const PageWrapper = styled.div` @@ -61,109 +60,6 @@ const SwapWrapperInner = styled.div` padding-top: 12px; ` -export const UniswapPopoverContainer = styled.div` - padding: 18px; - color: ${({ theme }) => theme.neutral1}; - font-weight: 485; - font-size: 12px; - line-height: 16px; - word-break: break-word; - background: ${({ theme }) => theme.surface1}; - border-radius: 20px; - border: 1px solid ${({ theme }) => theme.surface3}; - box-shadow: 0 4px 8px 0 ${({ theme }) => transparentize(0.9, theme.shadow1)}; - position: relative; - overflow: hidden; -` - -const springDownKeyframes = `@keyframes spring-down { - 0% { transform: translateY(-80px); } - 25% { transform: translateY(4px); } - 50% { transform: translateY(-1px); } - 75% { transform: translateY(0px); } - 100% { transform: translateY(0px); } -}` - -const backUpKeyframes = `@keyframes back-up { - 0% { transform: translateY(0px); } - 100% { transform: translateY(-80px); } -}` - -export const UniswapXShine = (props: any) => { - const isDarkMode = useIsDarkMode() - return -} - -const UniswapXShineInner = styled.div` - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - z-index: -1; - pointer-events: none; - background: linear-gradient(130deg, transparent 20%, ${({ theme }) => theme.accent1}, transparent 80%); - opacity: 0.15; -` - -// overflow hidden to hide the SwapMustacheShadow -export const SwapOptInSmallContainer = styled.div<{ visible: boolean }>` - visibility: ${({ visible }) => (visible ? 'visible' : 'hidden')}; - overflow: hidden; - margin-top: -14px; - transform: translateY(${({ visible }) => (visible ? 0 : -80)}px); - transition: all ease 400ms; - animation: ${({ visible }) => (visible ? `spring-down 900ms ease forwards` : 'back-up 200ms ease forwards')}; - - ${springDownKeyframes} - ${backUpKeyframes} -` - -export const UniswapXOptInLargeContainerPositioner = styled.div` - position: absolute; - top: 211px; - right: ${-320 - 15}px; - width: 320px; - align-items: center; - min-height: 170px; - display: flex; - pointer-events: none; -` - -export const UniswapXOptInLargeContainer = styled.div<{ visible: boolean }>` - opacity: ${({ visible }) => (visible ? 1 : 0)}; - transform: ${({ visible }) => `translateY(${visible ? 0 : -6}px)`}; - transition: all ease-in 300ms; - transition-delay: ${({ visible }) => (visible ? '350ms' : '0')}; - pointer-events: ${({ visible }) => (visible ? 'auto' : 'none')}; -` - -export const SwapMustache = styled.main` - position: relative; - background: ${({ theme }) => theme.surface1}; - border-radius: 16px; - border-top-left-radius: 0; - border-top-right-radius: 0; - border: 1px solid ${({ theme }) => theme.surface3}; - border-top-width: 0; - padding: 18px; - padding-top: calc(12px + 18px); - z-index: 0; - transition: transform 250ms ease; -` - -export const SwapMustacheShadow = styled.main` - position: absolute; - top: 0; - left: 0; - border-radius: 16px; - height: 100%; - width: 100%; - transform: translateY(-100%); - box-shadow: 0 0 20px 20px ${({ theme }) => theme.surface2}; - background: red; -` - export const ArrowWrapper = styled.div<{ clickable: boolean }>` border-radius: 12px; height: 40px; diff --git a/src/featureFlags/flags/uniswapXDefault.ts b/src/featureFlags/flags/uniswapXDefault.ts deleted file mode 100644 index 2b34e8c2f9..0000000000 --- a/src/featureFlags/flags/uniswapXDefault.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { BaseVariant, FeatureFlag, useBaseFlag } from '../index' - -export function useUniswapXDefaultEnabledFlag(): BaseVariant { - return useBaseFlag(FeatureFlag.uniswapXDefaultEnabled) -} - -export function useUniswapXDefaultEnabled(): boolean { - return useUniswapXDefaultEnabledFlag() === BaseVariant.Enabled -} diff --git a/src/featureFlags/index.tsx b/src/featureFlags/index.tsx index cc3cda3b0b..0f4a30bfcd 100644 --- a/src/featureFlags/index.tsx +++ b/src/featureFlags/index.tsx @@ -16,7 +16,6 @@ export enum FeatureFlag { infoTDP = 'info_tdp', infoPoolPage = 'info_pool_page', infoLiveViews = 'info_live_views', - uniswapXDefaultEnabled = 'uniswapx_default_enabled', quickRouteMainnet = 'enable_quick_route_mainnet', progressIndicatorV2 = 'progress_indicator_v2', feesEnabled = 'fees_enabled', diff --git a/src/lib/hooks/routing/useRoutingAPIArguments.ts b/src/lib/hooks/routing/useRoutingAPIArguments.ts index 484feef7dd..dcac3b7b5f 100644 --- a/src/lib/hooks/routing/useRoutingAPIArguments.ts +++ b/src/lib/hooks/routing/useRoutingAPIArguments.ts @@ -1,12 +1,10 @@ import { SkipToken, skipToken } from '@reduxjs/toolkit/query/react' import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core' -import { useUniswapXDefaultEnabled } from 'featureFlags/flags/uniswapXDefault' import { useUniswapXSyntheticQuoteEnabled } from 'featureFlags/flags/uniswapXUseSyntheticQuote' import { useFeesEnabled } from 'featureFlags/flags/useFees' import { useMemo } from 'react' import { GetQuoteArgs, INTERNAL_ROUTER_PREFERENCE_PRICE, RouterPreference } from 'state/routing/types' import { currencyAddressForSwapQuote } from 'state/routing/utils' -import { useUserDisabledUniswapX, useUserOptedOutOfUniswapX } from 'state/user/hooks' /** * Returns query arguments for the Routing API query or undefined if the @@ -29,9 +27,6 @@ export function useRoutingAPIArguments({ routerPreference: RouterPreference | typeof INTERNAL_ROUTER_PREFERENCE_PRICE }): GetQuoteArgs | SkipToken { const uniswapXForceSyntheticQuotes = useUniswapXSyntheticQuoteEnabled() - const userDisabledUniswapX = useUserDisabledUniswapX() - const userOptedOutOfUniswapX = useUserOptedOutOfUniswapX() - const isUniswapXDefaultEnabled = useUniswapXDefaultEnabled() const feesEnabled = useFeesEnabled() // Don't enable fee logic if this is a quote for pricing @@ -56,23 +51,8 @@ export function useRoutingAPIArguments({ tradeType, needsWrapIfUniswapX: tokenIn.isNative, uniswapXForceSyntheticQuotes, - userDisabledUniswapX, - userOptedOutOfUniswapX, - isUniswapXDefaultEnabled, sendPortionEnabled, }, - [ - account, - amount, - routerPreference, - tokenIn, - tokenOut, - tradeType, - uniswapXForceSyntheticQuotes, - userDisabledUniswapX, - userOptedOutOfUniswapX, - isUniswapXDefaultEnabled, - sendPortionEnabled, - ] + [account, amount, routerPreference, tokenIn, tokenOut, tradeType, uniswapXForceSyntheticQuotes, sendPortionEnabled] ) } diff --git a/src/pages/App.tsx b/src/pages/App.tsx index 87e1bd67eb..7fdc75ce2a 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -5,8 +5,7 @@ import ErrorBoundary from 'components/ErrorBoundary' import Loader from 'components/Icons/LoadingSpinner' import NavBar, { PageTabs } from 'components/NavBar' import { UK_BANNER_HEIGHT, UK_BANNER_HEIGHT_MD, UK_BANNER_HEIGHT_SM, UkBanner } from 'components/NavBar/UkBanner' -import { FeatureFlag, useFeatureFlagsIsLoaded } from 'featureFlags' -import { useUniswapXDefaultEnabled } from 'featureFlags/flags/uniswapXDefault' +import { useFeatureFlagsIsLoaded } from 'featureFlags' import { useAtom } from 'jotai' import { useBag } from 'nft/hooks/useBag' import { lazy, Suspense, useEffect, useLayoutEffect, useMemo, useState } from 'react' @@ -14,9 +13,8 @@ import { Navigate, Route, Routes, useLocation, useSearchParams } from 'react-rou import { shouldDisableNFTRoutesAtom } from 'state/application/atoms' import { useAppSelector } from 'state/hooks' import { AppState } from 'state/reducer' -import { RouterPreference } from 'state/routing/types' -import { useRouterPreference, useUserOptedOutOfUniswapX } from 'state/user/hooks' -import { StatsigProvider, StatsigUser, useGate } from 'statsig-react' +import { useRouterPreference } from 'state/user/hooks' +import { StatsigProvider, StatsigUser } from 'statsig-react' import styled from 'styled-components' import DarkModeQueryParamReader from 'theme/components/DarkModeQueryParamReader' import { useIsDarkMode } from 'theme/components/ThemeToggle' @@ -212,9 +210,6 @@ function UserPropertyUpdater() { const isDarkMode = useIsDarkMode() const [routerPreference] = useRouterPreference() - const userOptedOutOfUniswapX = useUserOptedOutOfUniswapX() - const isUniswapXDefaultEnabled = useUniswapXDefaultEnabled() - const { isLoading: isUniswapXDefaultLoading } = useGate(FeatureFlag.uniswapXDefaultEnabled) const rehydrated = useAppSelector((state) => state._persist.rehydrated) useEffect(() => { @@ -248,22 +243,8 @@ function UserPropertyUpdater() { }, [isDarkMode]) useEffect(() => { - if (isUniswapXDefaultLoading || !rehydrated) return - - // If we're not in the transition period to UniswapX opt-out, set the router preference to whatever is specified. - if (!isUniswapXDefaultEnabled) { - user.set(CustomUserProperties.ROUTER_PREFERENCE, routerPreference) - return - } - - // In the transition period, override the stored API preference to UniswapX if the user hasn't opted out. - if (routerPreference === RouterPreference.API && !userOptedOutOfUniswapX) { - user.set(CustomUserProperties.ROUTER_PREFERENCE, RouterPreference.X) - return - } - - // Otherwise, the user has opted out or their preference is UniswapX/client, so set the preference to whatever is specified. + if (!rehydrated) return user.set(CustomUserProperties.ROUTER_PREFERENCE, routerPreference) - }, [routerPreference, isUniswapXDefaultEnabled, userOptedOutOfUniswapX, isUniswapXDefaultLoading, rehydrated]) + }, [routerPreference, rehydrated]) return null } diff --git a/src/pages/Swap/UniswapXOptIn.tsx b/src/pages/Swap/UniswapXOptIn.tsx deleted file mode 100644 index f75480ad6f..0000000000 --- a/src/pages/Swap/UniswapXOptIn.tsx +++ /dev/null @@ -1,207 +0,0 @@ -import { Trans } from '@lingui/macro' -import { sendAnalyticsEvent, Trace } from 'analytics' -import Column from 'components/Column' -import UniswapXBrandMark from 'components/Logo/UniswapXBrandMark' -import { Arrow } from 'components/Popover' -import UniswapXRouterLabel from 'components/RouterLabel/UniswapXRouterLabel' -import Row from 'components/Row' -import { - SwapMustache, - SwapMustacheShadow, - SwapOptInSmallContainer, - UniswapPopoverContainer, - UniswapXOptInLargeContainer, - UniswapXOptInLargeContainerPositioner, - UniswapXShine, -} from 'components/swap/styled' -import { formatCommonPropertiesForTrade } from 'lib/utils/analytics' -import { PropsWithChildren, useRef, useState } from 'react' -import { X } from 'react-feather' -import { useLocation } from 'react-router-dom' -import { Text } from 'rebass' -import { useAppDispatch } from 'state/hooks' -import { RouterPreference } from 'state/routing/types' -import { isClassicTrade } from 'state/routing/utils' -import { SwapInfo } from 'state/swap/hooks' -import { useRouterPreference, useUserDisabledUniswapX } from 'state/user/hooks' -import { updateDisabledUniswapX } from 'state/user/reducer' -import styled from 'styled-components' -import { ThemedText } from 'theme/components' - -export const UniswapXOptIn = (props: { swapInfo: SwapInfo; isSmall: boolean }) => { - const { - trade: { trade }, - allowedSlippage, - } = props.swapInfo - const userDisabledUniswapX = useUserDisabledUniswapX() - const isOnClassic = Boolean(trade && isClassicTrade(trade) && trade.isUniswapXBetter && !userDisabledUniswapX) - const [hasEverShown, setHasEverShown] = useState(false) - - if (isOnClassic && !hasEverShown) { - setHasEverShown(true) - } - - // avoid some work if never needed to show - if (!hasEverShown) { - return null - } - - return ( - - - - ) -} - -const OptInContents = ({ - swapInfo, - isOnClassic, - isSmall, -}: { - swapInfo: SwapInfo - isOnClassic: boolean - isSmall: boolean -}) => { - const { - trade: { trade }, - allowedSlippage, - } = swapInfo - const [, setRouterPreference] = useRouterPreference() - const dispatch = useAppDispatch() - const [showYoureIn, setShowYoureIn] = useState(false) - const isVisible = isOnClassic - const location = useLocation() - - const tryItNowElement = ( - { - // slight delay before hiding - setTimeout(() => { - setShowYoureIn(true) - setTimeout(() => { - setShowYoureIn(false) - }, 5000) - }, 200) - - if (!trade) return - sendAnalyticsEvent('UniswapX Opt In Toggled', { - ...formatCommonPropertiesForTrade(trade, allowedSlippage), - new_preference: RouterPreference.X, - }) - setRouterPreference(RouterPreference.X) - }} - style={{ - cursor: 'pointer', - }} - > - Try it now - - ) - - const containerRef = useRef() - - if (isSmall || location.pathname.includes('/tokens/')) { - return ( - - - - - - - Try gas free swaps with the -
- {' '} - Beta -
- {tryItNowElement} -
-
-
- ) - } - - return ( - <> - {/* first popover: intro */} - - { - if (!trade) return - sendAnalyticsEvent('UniswapX Opt In Toggled', { - ...formatCommonPropertiesForTrade(trade, allowedSlippage), - new_preference: RouterPreference.API, - }) - setRouterPreference(RouterPreference.API) - dispatch(updateDisabledUniswapX({ disabledUniswapX: true })) - }} - /> - - - - Try the{' '} - {' '} - Beta -
    -
  • - Gas free swaps -
  • -
  • - MEV protection -
  • -
  • - Better prices and more liquidity -
  • -
-
-
- - {tryItNowElement} -
- - {/* second popover: you're in! */} - - - - You're in! - - - - - You can turn it off at anytime in settings - - - - ) -} - -const UniswapXOptInPopover = (props: PropsWithChildren<{ visible: boolean; shiny?: boolean }>) => { - return ( - // positioner ensures no matter the height of the inner content - // it sits at the same position from the top of the swap area - - - - - {props.shiny && } - {props.children} - - - - ) -} - -const CloseIcon = styled(X)` - color: ${({ theme }) => theme.neutral3}; - cursor: pointer; - position: absolute; - top: 14px; - right: 14px; -` diff --git a/src/pages/Swap/index.tsx b/src/pages/Swap/index.tsx index 02ba2109a5..f65819ca8a 100644 --- a/src/pages/Swap/index.tsx +++ b/src/pages/Swap/index.tsx @@ -33,7 +33,6 @@ import { useConnectionReady } from 'connection/eagerlyConnect' import { getChainInfo } from 'constants/chainInfo' import { asSupportedChain, isSupportedChain } from 'constants/chains' import { getSwapCurrencyId, TOKEN_SHORTHANDS } from 'constants/tokens' -import { useUniswapXDefaultEnabled } from 'featureFlags/flags/uniswapXDefault' import { useCurrency, useDefaultActiveTokens } from 'hooks/Tokens' import { useIsSwapUnsupported } from 'hooks/useIsSwapUnsupported' import { useMaxAmountIn } from 'hooks/useMaxAmountIn' @@ -64,10 +63,8 @@ import { maxAmountSpend } from 'utils/maxAmountSpend' import { computeRealizedPriceImpact, warningSeverity } from 'utils/prices' import { didUserReject } from 'utils/swapErrorToUserReadableMessage' -import { useScreenSize } from '../../hooks/useScreenSize' import { useIsDarkMode } from '../../theme/components/ThemeToggle' import { OutputTaxTooltipBody } from './TaxTooltipBody' -import { UniswapXOptIn } from './UniswapXOptIn' export const ArrowContainer = styled.div` display: inline-flex; @@ -597,9 +594,7 @@ export function Swap({ const inputCurrency = currencies[Field.INPUT] ?? undefined const switchChain = useSwitchChain() const switchingChain = useAppSelector((state) => state.wallets.switchingChain) - const showOptInSmall = !useScreenSize().navSearchInputVisible const isDark = useIsDarkMode() - const isUniswapXDefaultEnabled = useUniswapXDefaultEnabled() const [currentTab, setCurrentTab] = useState(SwapTab.Swap) @@ -832,7 +827,6 @@ export function Swap({ )} - {!showOptInSmall && !isUniswapXDefaultEnabled && } ) @@ -849,7 +843,6 @@ export function Swap({ /> {/* todo: build Limit UI */} {currentTab === SwapTab.Swap ? swapElement : undefined} - {showOptInSmall && !isUniswapXDefaultEnabled && } ) } diff --git a/src/state/migrations.test.ts b/src/state/migrations.test.ts index cec883cd38..e50ba03829 100644 --- a/src/state/migrations.test.ts +++ b/src/state/migrations.test.ts @@ -14,7 +14,7 @@ const defaultState = { user: {}, _persist: { rehydrated: true, - version: 4, + version: 5, }, application: { chainId: null, diff --git a/src/state/migrations.ts b/src/state/migrations.ts index 476edd6629..6c58c5e341 100644 --- a/src/state/migrations.ts +++ b/src/state/migrations.ts @@ -6,6 +6,7 @@ import { migration1 } from './migrations/1' import { migration2 } from './migrations/2' import { migration3 } from './migrations/3' import { migration4 } from './migrations/4' +import { migration5 } from './migrations/5' import { legacyLocalStorageMigration } from './migrations/legacy' /** @@ -23,6 +24,7 @@ export const migrations: MigrationManifest = { 2: migration2, 3: migration3, 4: migration4, + 5: migration5, } // We use a custom migration function for the initial state, because redux-persist diff --git a/src/state/migrations/5.test.ts b/src/state/migrations/5.test.ts new file mode 100644 index 0000000000..2988a80a67 --- /dev/null +++ b/src/state/migrations/5.test.ts @@ -0,0 +1,98 @@ +import { createMigrate } from 'redux-persist' +import { RouterPreference } from 'state/routing/types' +import { SlippageTolerance } from 'state/user/types' + +import { migration1 } from './1' +import { migration2 } from './2' +import { migration3 } from './3' +import { migration4 } from './4' +import { migration5, PersistAppStateV5 } from './5' + +const previousState: PersistAppStateV5 = { + user: { + userRouterPreference: RouterPreference.API, + optedOutOfUniswapX: false, + disabledUniswapX: false, + userLocale: null, + userHideClosedPositions: false, + userSlippageTolerance: SlippageTolerance.Auto, + userSlippageToleranceHasBeenMigratedToAuto: true, + userDeadline: 1800, + tokens: {}, + pairs: {}, + timestamp: Date.now(), + hideAndroidAnnouncementBanner: false, + }, + _persist: { + version: 4, + rehydrated: true, + }, +} + +describe('migration to v5', () => { + it('should migrate users who currently have `API` router preference', async () => { + const migrator = createMigrate( + { + 1: migration1, + 2: migration2, + 3: migration3, + 4: migration4, + 5: migration5, + }, + { debug: false } + ) + const result: any = await migrator(previousState, 5) + expect(result?.user?.userRouterPreference).toEqual(RouterPreference.X) + expect(result?.user?.disabledUniswapX).toBeUndefined() + expect(result?.user?.optedOutOfUniswapX).toBeUndefined() + expect(result?._persist.version).toEqual(5) + }) + + it('should not migrate routerPreference if user disabled during rollout', async () => { + const migrator = createMigrate( + { + 1: migration1, + 2: migration2, + 3: migration3, + 4: migration4, + 5: migration5, + }, + { debug: false } + ) + const result: any = await migrator( + { + ...previousState, + user: { + ...previousState.user, + optedOutOfUniswapX: true, + }, + } as PersistAppStateV5, + 5 + ) + expect(result?.user?.userRouterPreference).toEqual(RouterPreference.API) + expect(result?.user?.optedOutOfUniswapX).toBeUndefined() + expect(result?._persist.version).toEqual(5) + }) + + it('should not migrate user if user does not exist', async () => { + const migrator = createMigrate( + { + 1: migration1, + 2: migration2, + 3: migration3, + 4: migration4, + 5: migration5, + }, + { debug: false } + ) + const result: any = await migrator( + { + ...previousState, + user: undefined, + } as PersistAppStateV5, + 5 + ) + expect(result?.user).toBeUndefined() + expect(result?._persist.version).toEqual(5) + }) +}) diff --git a/src/state/migrations/5.ts b/src/state/migrations/5.ts new file mode 100644 index 0000000000..3b73839773 --- /dev/null +++ b/src/state/migrations/5.ts @@ -0,0 +1,43 @@ +import { PersistState } from 'redux-persist' +import { RouterPreference } from 'state/routing/types' +import { UserState } from 'state/user/reducer' + +export type PersistAppStateV5 = { + _persist: PersistState +} & { user?: UserState & { disabledUniswapX?: boolean; optedOutOfUniswapX?: boolean } } + +/** + * Migration to migrate users to UniswapX by default. + */ +export const migration5 = (state: PersistAppStateV5 | undefined) => { + if (!state) return state + // Remove a previously-persisted variable + if (state?.user && 'disabledUniswapX' in state.user) { + delete state.user['disabledUniswapX'] + } + const userOptedOutOfUniswapX = state?.user?.optedOutOfUniswapX + if (state?.user && 'optedOutOfUniswapX' in state.user) { + delete state.user['optedOutOfUniswapX'] + } + // If the the user has previously disabled UniswapX *during the opt-out rollout period*, we respect that preference. + if (state?.user && !userOptedOutOfUniswapX) { + return { + ...state, + user: { + ...state.user, + userRouterPreference: RouterPreference.X, + }, + _persist: { + ...state._persist, + version: 5, + }, + } + } + return { + ...state, + _persist: { + ...state._persist, + version: 5, + }, + } +} diff --git a/src/state/reducer.ts b/src/state/reducer.ts index 6591c1ffed..d4ec93ed3c 100644 --- a/src/state/reducer.ts +++ b/src/state/reducer.ts @@ -44,7 +44,7 @@ export type AppState = ReturnType const persistConfig: PersistConfig = { key: 'interface', - version: 4, // see migrations.ts for more details about this version + version: 5, // see migrations.ts for more details about this version storage: localForage.createInstance({ name: 'redux', }), diff --git a/src/state/reducerTypeTest.ts b/src/state/reducerTypeTest.ts index fd32c3bbe7..28bc6469f0 100644 --- a/src/state/reducerTypeTest.ts +++ b/src/state/reducerTypeTest.ts @@ -89,8 +89,6 @@ interface ExpectedUserState { timestamp: number hideAndroidAnnouncementBanner: boolean showSurveyPopup?: boolean - disabledUniswapX?: boolean - optedOutOfUniswapX?: boolean originCountry?: string } diff --git a/src/state/routing/slice.ts b/src/state/routing/slice.ts index 2661674649..0b691f83ed 100644 --- a/src/state/routing/slice.ts +++ b/src/state/routing/slice.ts @@ -63,9 +63,9 @@ function getRoutingAPIConfig(args: GetQuoteArgs): RoutingConfig { if ( // If the user has opted out of UniswapX during the opt-out transition period, we should respect that preference and only request classic quotes. - (args.userOptedOutOfUniswapX && routerPreference !== RouterPreference.X) || - !isUniswapXSupportedChain(tokenInChainId) || - routerPreference === INTERNAL_ROUTER_PREFERENCE_PRICE + routerPreference === RouterPreference.API || + routerPreference === INTERNAL_ROUTER_PREFERENCE_PRICE || + !isUniswapXSupportedChain(tokenInChainId) ) { return [classic] } diff --git a/src/state/routing/types.ts b/src/state/routing/types.ts index 44b84c4e8b..7fb6fc98a4 100644 --- a/src/state/routing/types.ts +++ b/src/state/routing/types.ts @@ -43,11 +43,6 @@ export interface GetQuoteArgs { tradeType: TradeType needsWrapIfUniswapX: boolean uniswapXForceSyntheticQuotes: boolean - // legacy field indicating the user disabled UniswapX during the opt-in period, or dismissed the UniswapX opt-in modal. - userDisabledUniswapX: boolean - // temporary field indicating the user disabled UniswapX during the transition to the opt-out model - userOptedOutOfUniswapX: boolean - isUniswapXDefaultEnabled: boolean sendPortionEnabled: boolean } @@ -197,7 +192,6 @@ export class ClassicTrade extends Trade { approveInfo: ApproveInfo gasUseEstimateUSD?: number // gas estimate for swaps blockNumber: string | null | undefined - isUniswapXBetter: boolean | undefined requestId: string | undefined quoteMethod: QuoteMethod swapFee: SwapFeeInfo | undefined @@ -205,7 +199,6 @@ export class ClassicTrade extends Trade { constructor({ gasUseEstimateUSD, blockNumber, - isUniswapXBetter, requestId, quoteMethod, approveInfo, @@ -215,7 +208,6 @@ export class ClassicTrade extends Trade { gasUseEstimateUSD?: number totalGasUseEstimateUSD?: number blockNumber?: string | null - isUniswapXBetter?: boolean requestId?: string quoteMethod: QuoteMethod approveInfo: ApproveInfo @@ -240,7 +232,6 @@ export class ClassicTrade extends Trade { super(routes) this.blockNumber = blockNumber this.gasUseEstimateUSD = gasUseEstimateUSD - this.isUniswapXBetter = isUniswapXBetter this.requestId = requestId this.quoteMethod = quoteMethod this.approveInfo = approveInfo diff --git a/src/state/routing/useRoutingAPITrade.test.ts b/src/state/routing/useRoutingAPITrade.test.ts index 93f027b8f8..e5b29473c3 100644 --- a/src/state/routing/useRoutingAPITrade.test.ts +++ b/src/state/routing/useRoutingAPITrade.test.ts @@ -3,13 +3,12 @@ import { renderHook } from '@testing-library/react' import { CurrencyAmount, TradeType } from '@uniswap/sdk-core' import { AVERAGE_L1_BLOCK_TIME } from 'constants/chainInfo' import { USDC_MAINNET } from 'constants/tokens' -import { useUniswapXDefaultEnabled } from 'featureFlags/flags/uniswapXDefault' import { useUniswapXSyntheticQuoteEnabled } from 'featureFlags/flags/uniswapXUseSyntheticQuote' import { useFeesEnabled } from 'featureFlags/flags/useFees' import useIsWindowVisible from 'hooks/useIsWindowVisible' import ms from 'ms' import { GetQuoteArgs, INTERNAL_ROUTER_PREFERENCE_PRICE, RouterPreference } from 'state/routing/types' -import { useRouterPreference, useUserDisabledUniswapX, useUserOptedOutOfUniswapX } from 'state/user/hooks' +import { useRouterPreference } from 'state/user/hooks' import { ETH_MAINNET } from 'test-utils/constants' import { mocked } from 'test-utils/mocked' @@ -29,16 +28,12 @@ jest.mock('./slice', () => { }) jest.mock('state/user/hooks') jest.mock('featureFlags/flags/uniswapXUseSyntheticQuote') -jest.mock('featureFlags/flags/uniswapXDefault') jest.mock('featureFlags/flags/useFees') beforeEach(() => { mocked(useIsWindowVisible).mockReturnValue(true) mocked(useRouterPreference).mockReturnValue([RouterPreference.API, () => undefined]) mocked(useUniswapXSyntheticQuoteEnabled).mockReturnValue(false) - mocked(useUserDisabledUniswapX).mockReturnValue(false) - mocked(useUserOptedOutOfUniswapX).mockReturnValue(false) - mocked(useUniswapXDefaultEnabled).mockReturnValue(false) mocked(useFeesEnabled).mockReturnValue(true) // @ts-ignore we dont use the response from this hook in useRoutingAPITrade so fine to mock as undefined mocked(useGetQuoteQuery).mockReturnValue(undefined) @@ -66,9 +61,6 @@ const MOCK_ARGS: GetQuoteArgs = { tradeType: TradeType.EXACT_INPUT, needsWrapIfUniswapX: USDCAmount.currency.isNative, uniswapXForceSyntheticQuotes: false, - userDisabledUniswapX: false, - userOptedOutOfUniswapX: false, - isUniswapXDefaultEnabled: false, sendPortionEnabled: true, } diff --git a/src/state/routing/utils.ts b/src/state/routing/utils.ts index a5bf04a8b9..b5c0af4c59 100644 --- a/src/state/routing/utils.ts +++ b/src/state/routing/utils.ts @@ -219,18 +219,14 @@ export async function transformQuoteToTrade( data: URAQuoteResponse, quoteMethod: QuoteMethod ): Promise { - const { tradeType, needsWrapIfUniswapX, routerPreference, account, amount, isUniswapXDefaultEnabled } = args + const { tradeType, needsWrapIfUniswapX, routerPreference, account, amount } = args + + const showUniswapXTrade = data.routing === URAQuoteType.DUTCH_LIMIT && routerPreference === RouterPreference.X - 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(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 - // Some sus javascript float math but it's ok because its just an estimate for display purposes const usdCostPerGas = gasUseEstimateUSD && gasUseEstimate ? gasUseEstimateUSD / gasUseEstimate : undefined @@ -267,15 +263,14 @@ export async function transformQuoteToTrade( gasUseEstimateUSD, approveInfo, blockNumber, - isUniswapXBetter, requestId: data.quote.requestId, quoteMethod, swapFee, }) - // During the opt-in period, only return UniswapX quotes if the user has turned on the setting, - // even if it is the better quote. - if (isUniswapXBetter && (routerPreference === RouterPreference.X || isUniswapXDefaultEnabled)) { + // If the top-level URA quote type is DUTCH_LIMIT, then UniswapX is better for the user + const isUniswapXBetter = data.routing === URAQuoteType.DUTCH_LIMIT + if (isUniswapXBetter) { const orderInfo = toDutchOrderInfo(data.quote.orderInfo) const swapFee = getSwapFee(data.quote) const wrapInfo = await getWrapInfo(needsWrapIfUniswapX, account, currencyIn.chainId, amount, usdCostPerGas) diff --git a/src/state/swap/hooks.tsx b/src/state/swap/hooks.tsx index b9126a4499..aba04e0244 100644 --- a/src/state/swap/hooks.tsx +++ b/src/state/swap/hooks.tsx @@ -77,7 +77,7 @@ const BAD_RECIPIENT_ADDRESSES: { [address: string]: true } = { '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D': true, // v2 router 02 } -export type SwapInfo = { +type SwapInfo = { currencies: { [field in Field]?: Currency } currencyBalances: { [field in Field]?: CurrencyAmount } inputTax: Percent diff --git a/src/state/user/hooks.test.tsx b/src/state/user/hooks.test.tsx index 0c176748df..c86a37d099 100644 --- a/src/state/user/hooks.test.tsx +++ b/src/state/user/hooks.test.tsx @@ -5,13 +5,7 @@ import store from 'state' import { RouterPreference } from 'state/routing/types' import { renderHook } from 'test-utils/render' -import { - deserializeToken, - serializeToken, - useRouterPreference, - useUserDisabledUniswapX, - useUserSlippageTolerance, -} from './hooks' +import { deserializeToken, serializeToken, useRouterPreference, useUserSlippageTolerance } from './hooks' import { updateUserSlippageTolerance } from './reducer' import { SlippageTolerance } from './types' @@ -81,12 +75,3 @@ describe('useRouterPreference', () => { expect(routerPreference).toBe(RouterPreference.X) }) }) - -describe('useUserDisabledUniswapX', () => { - it('returns `false` by default', () => { - const { - result: { current: disabledUniswapX }, - } = renderHook(() => useUserDisabledUniswapX()) - expect(disabledUniswapX).toBe(false) - }) -}) diff --git a/src/state/user/hooks.tsx b/src/state/user/hooks.tsx index 5072b4da4f..cd2ca0302d 100644 --- a/src/state/user/hooks.tsx +++ b/src/state/user/hooks.tsx @@ -217,14 +217,6 @@ export function useHideAndroidAnnouncementBanner(): [boolean, () => void] { return [hideAndroidAnnouncementBanner, toggleHideAndroidAnnouncementBanner] } -export function useUserDisabledUniswapX(): boolean { - return useAppSelector((state) => state.user.disabledUniswapX) ?? false -} - -export function useUserOptedOutOfUniswapX(): boolean { - return useAppSelector((state) => state.user.optedOutOfUniswapX) ?? false -} - /** * Given two tokens return the liquidity token that represents its liquidity shares * @param tokenA one of the two tokens diff --git a/src/state/user/reducer.ts b/src/state/user/reducer.ts index 6b4d42b6d0..e9fd25aed1 100644 --- a/src/state/user/reducer.ts +++ b/src/state/user/reducer.ts @@ -48,10 +48,6 @@ export interface UserState { timestamp: number hideAndroidAnnouncementBanner: boolean - // legacy field indicating the user disabled UniswapX during the opt-in period, or dismissed the UniswapX opt-in modal. - disabledUniswapX?: boolean - // temporary field indicating the user disabled UniswapX during the transition to the opt-out model - optedOutOfUniswapX?: boolean // undefined means has not gone through A/B split yet showSurveyPopup?: boolean @@ -111,12 +107,6 @@ const userSlice = createSlice({ updateHideAndroidAnnouncementBanner(state, action) { state.hideAndroidAnnouncementBanner = action.payload.hideAndroidAnnouncementBanner }, - updateDisabledUniswapX(state, action) { - state.disabledUniswapX = action.payload.disabledUniswapX - }, - updateOptedOutOfUniswapX(state, action) { - state.optedOutOfUniswapX = action.payload.optedOutOfUniswapX - }, addSerializedToken(state, { payload: { serializedToken } }) { if (!state.tokens) { state.tokens = {} @@ -153,7 +143,5 @@ export const { updateUserLocale, updateUserSlippageTolerance, updateHideAndroidAnnouncementBanner, - updateDisabledUniswapX, - updateOptedOutOfUniswapX, } = userSlice.actions export default userSlice.reducer