feat: x rollout cleanup (#7582)
* feat: cleanup post x rollout * feat: remove feature flag * fix: remove more unused styled components * fix: delete deprecated value from redux store * fix: lint * fix: remove userOptedOutOfUniswapX * fix: migrate verion in edge case, add test
This commit is contained in:
parent
0f4ca592f2
commit
5ded55e061
@ -120,10 +120,7 @@ describe('Swap with fees', () => {
|
|||||||
describe('UniswapX swaps', () => {
|
describe('UniswapX swaps', () => {
|
||||||
it('displays UniswapX fee in UI', () => {
|
it('displays UniswapX fee in UI', () => {
|
||||||
cy.visit('/swap', {
|
cy.visit('/swap', {
|
||||||
featureFlags: [
|
featureFlags: [{ name: FeatureFlag.feesEnabled, value: true }],
|
||||||
{ name: FeatureFlag.feesEnabled, value: true },
|
|
||||||
{ name: FeatureFlag.uniswapXDefaultEnabled, value: true },
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Intercept the trade quote
|
// Intercept the trade quote
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { ChainId, CurrencyAmount } from '@uniswap/sdk-core'
|
import { ChainId, CurrencyAmount } from '@uniswap/sdk-core'
|
||||||
import { CyHttpMessages } from 'cypress/types/net-stubbing'
|
import { CyHttpMessages } from 'cypress/types/net-stubbing'
|
||||||
import { FeatureFlag } from 'featureFlags'
|
|
||||||
|
|
||||||
import { DAI, nativeOnChain, USDC_MAINNET } from '../../../src/constants/tokens'
|
import { DAI, nativeOnChain, USDC_MAINNET } from '../../../src/constants/tokens'
|
||||||
import { getTestSelector } from '../../utils'
|
import { getTestSelector } from '../../utils'
|
||||||
@ -45,9 +44,7 @@ function stubSwapTxReceipt() {
|
|||||||
describe('UniswapX Toggle', () => {
|
describe('UniswapX Toggle', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
stubNonPriceQuoteWith(QuoteWhereUniswapXIsBetter)
|
stubNonPriceQuoteWith(QuoteWhereUniswapXIsBetter)
|
||||||
cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`, {
|
cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`)
|
||||||
featureFlags: [{ name: FeatureFlag.uniswapXDefaultEnabled, value: true }],
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('displays uniswapx ui when setting is on', () => {
|
it('displays uniswapx ui when setting is on', () => {
|
||||||
@ -69,9 +66,7 @@ describe('UniswapX Orders', () => {
|
|||||||
stubSwapTxReceipt()
|
stubSwapTxReceipt()
|
||||||
|
|
||||||
cy.hardhat().then((hardhat) => hardhat.fund(hardhat.wallet, CurrencyAmount.fromRawAmount(USDC_MAINNET, 3e8)))
|
cy.hardhat().then((hardhat) => hardhat.fund(hardhat.wallet, CurrencyAmount.fromRawAmount(USDC_MAINNET, 3e8)))
|
||||||
cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`, {
|
cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`)
|
||||||
featureFlags: [{ name: FeatureFlag.uniswapXDefaultEnabled, value: true }],
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can swap exact-in trades using uniswapX', () => {
|
it('can swap exact-in trades using uniswapX', () => {
|
||||||
@ -159,9 +154,7 @@ describe('UniswapX Eth Input', () => {
|
|||||||
|
|
||||||
stubSwapTxReceipt()
|
stubSwapTxReceipt()
|
||||||
|
|
||||||
cy.visit(`/swap/?inputCurrency=ETH&outputCurrency=${DAI.address}`, {
|
cy.visit(`/swap/?inputCurrency=ETH&outputCurrency=${DAI.address}`)
|
||||||
featureFlags: [{ name: FeatureFlag.uniswapXDefaultEnabled, value: true }],
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can swap using uniswapX with ETH as input', () => {
|
it('can swap using uniswapX with ETH as input', () => {
|
||||||
@ -247,9 +240,7 @@ describe('UniswapX activity history', () => {
|
|||||||
cy.hardhat().then(async (hardhat) => {
|
cy.hardhat().then(async (hardhat) => {
|
||||||
await hardhat.fund(hardhat.wallet, CurrencyAmount.fromRawAmount(USDC_MAINNET, 3e8))
|
await hardhat.fund(hardhat.wallet, CurrencyAmount.fromRawAmount(USDC_MAINNET, 3e8))
|
||||||
})
|
})
|
||||||
cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`, {
|
cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`)
|
||||||
featureFlags: [{ name: FeatureFlag.uniswapXDefaultEnabled, value: true }],
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can view UniswapX order status progress in activity', () => {
|
it('can view UniswapX order status progress in activity', () => {
|
||||||
|
@ -14,7 +14,6 @@ import { useMultichainUXFlag } from 'featureFlags/flags/multichainUx'
|
|||||||
import { useProgressIndicatorV2Flag } from 'featureFlags/flags/progressIndicatorV2'
|
import { useProgressIndicatorV2Flag } from 'featureFlags/flags/progressIndicatorV2'
|
||||||
import { useQuickRouteMainnetFlag } from 'featureFlags/flags/quickRouteMainnet'
|
import { useQuickRouteMainnetFlag } from 'featureFlags/flags/quickRouteMainnet'
|
||||||
import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc'
|
import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc'
|
||||||
import { useUniswapXDefaultEnabledFlag } from 'featureFlags/flags/uniswapXDefault'
|
|
||||||
import { useUniswapXSyntheticQuoteFlag } from 'featureFlags/flags/uniswapXUseSyntheticQuote'
|
import { useUniswapXSyntheticQuoteFlag } from 'featureFlags/flags/uniswapXUseSyntheticQuote'
|
||||||
import { useFeesEnabledFlag } from 'featureFlags/flags/useFees'
|
import { useFeesEnabledFlag } from 'featureFlags/flags/useFees'
|
||||||
import { useUpdateAtom } from 'jotai/utils'
|
import { useUpdateAtom } from 'jotai/utils'
|
||||||
@ -324,12 +323,6 @@ export default function FeatureFlagModal() {
|
|||||||
featureFlag={FeatureFlag.uniswapXSyntheticQuote}
|
featureFlag={FeatureFlag.uniswapXSyntheticQuote}
|
||||||
label="Force synthetic quotes for UniswapX"
|
label="Force synthetic quotes for UniswapX"
|
||||||
/>
|
/>
|
||||||
<FeatureFlagOption
|
|
||||||
variant={BaseVariant}
|
|
||||||
value={useUniswapXDefaultEnabledFlag()}
|
|
||||||
featureFlag={FeatureFlag.uniswapXDefaultEnabled}
|
|
||||||
label="Enable UniswapX by default"
|
|
||||||
/>
|
|
||||||
</FeatureFlagGroup>
|
</FeatureFlagGroup>
|
||||||
<FeatureFlagGroup name="Info Site Migration">
|
<FeatureFlagGroup name="Info Site Migration">
|
||||||
<FeatureFlagOption
|
<FeatureFlagOption
|
||||||
|
@ -20,7 +20,7 @@ const ReferenceElement = styled.div`
|
|||||||
height: inherit;
|
height: inherit;
|
||||||
`
|
`
|
||||||
|
|
||||||
export const Arrow = styled.div`
|
const Arrow = styled.div`
|
||||||
width: 8px;
|
width: 8px;
|
||||||
height: 8px;
|
height: 8px;
|
||||||
z-index: 9998;
|
z-index: 9998;
|
||||||
|
@ -3,11 +3,8 @@ import Column from 'components/Column'
|
|||||||
import UniswapXBrandMark from 'components/Logo/UniswapXBrandMark'
|
import UniswapXBrandMark from 'components/Logo/UniswapXBrandMark'
|
||||||
import { RowBetween, RowFixed } from 'components/Row'
|
import { RowBetween, RowFixed } from 'components/Row'
|
||||||
import Toggle from 'components/Toggle'
|
import Toggle from 'components/Toggle'
|
||||||
import { useUniswapXDefaultEnabled } from 'featureFlags/flags/uniswapXDefault'
|
|
||||||
import { useAppDispatch } from 'state/hooks'
|
|
||||||
import { RouterPreference } from 'state/routing/types'
|
import { RouterPreference } from 'state/routing/types'
|
||||||
import { useRouterPreference, useUserOptedOutOfUniswapX } from 'state/user/hooks'
|
import { useRouterPreference } from 'state/user/hooks'
|
||||||
import { updateDisabledUniswapX, updateOptedOutOfUniswapX } from 'state/user/reducer'
|
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import { ExternalLink, ThemedText } from 'theme/components'
|
import { ExternalLink, ThemedText } from 'theme/components'
|
||||||
|
|
||||||
@ -22,12 +19,6 @@ const InlineLink = styled(ThemedText.BodySmall)`
|
|||||||
|
|
||||||
export default function RouterPreferenceSettings() {
|
export default function RouterPreferenceSettings() {
|
||||||
const [routerPreference, setRouterPreference] = useRouterPreference()
|
const [routerPreference, setRouterPreference] = useRouterPreference()
|
||||||
const dispatch = useAppDispatch()
|
|
||||||
const userOptedOutOfUniswapX = useUserOptedOutOfUniswapX()
|
|
||||||
const isUniswapXDefaultEnabled = useUniswapXDefaultEnabled()
|
|
||||||
const isUniswapXOverrideEnabled = isUniswapXDefaultEnabled && !userOptedOutOfUniswapX
|
|
||||||
|
|
||||||
const uniswapXInEffect = routerPreference === RouterPreference.X || isUniswapXOverrideEnabled
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RowBetween gap="sm">
|
<RowBetween gap="sm">
|
||||||
@ -46,21 +37,9 @@ export default function RouterPreferenceSettings() {
|
|||||||
</RowFixed>
|
</RowFixed>
|
||||||
<Toggle
|
<Toggle
|
||||||
id="toggle-uniswap-x-button"
|
id="toggle-uniswap-x-button"
|
||||||
// If UniswapX-by-default is enabled we need to render this as active even if routerPreference === RouterPreference.API
|
isActive={routerPreference === RouterPreference.X}
|
||||||
// because we're going to default to the UniswapX quote.
|
|
||||||
// If the user manually toggles it off, this doesn't apply.
|
|
||||||
isActive={uniswapXInEffect}
|
|
||||||
toggle={() => {
|
toggle={() => {
|
||||||
if (uniswapXInEffect) {
|
setRouterPreference(routerPreference === RouterPreference.X ? RouterPreference.API : RouterPreference.X)
|
||||||
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)
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</RowBetween>
|
</RowBetween>
|
||||||
|
@ -4,7 +4,6 @@ import { AlertTriangle } from 'react-feather'
|
|||||||
import styled, { css } from 'styled-components'
|
import styled, { css } from 'styled-components'
|
||||||
import { Z_INDEX } from 'theme/zIndex'
|
import { Z_INDEX } from 'theme/zIndex'
|
||||||
|
|
||||||
import { useIsDarkMode } from '../../theme/components/ThemeToggle'
|
|
||||||
import { AutoColumn } from '../Column'
|
import { AutoColumn } from '../Column'
|
||||||
|
|
||||||
export const PageWrapper = styled.div`
|
export const PageWrapper = styled.div`
|
||||||
@ -61,109 +60,6 @@ const SwapWrapperInner = styled.div`
|
|||||||
padding-top: 12px;
|
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 <UniswapXShineInner {...props} style={{ opacity: isDarkMode ? 0.15 : 0.05, ...props.style }} />
|
|
||||||
}
|
|
||||||
|
|
||||||
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 }>`
|
export const ArrowWrapper = styled.div<{ clickable: boolean }>`
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
|
@ -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
|
|
||||||
}
|
|
@ -16,7 +16,6 @@ export enum FeatureFlag {
|
|||||||
infoTDP = 'info_tdp',
|
infoTDP = 'info_tdp',
|
||||||
infoPoolPage = 'info_pool_page',
|
infoPoolPage = 'info_pool_page',
|
||||||
infoLiveViews = 'info_live_views',
|
infoLiveViews = 'info_live_views',
|
||||||
uniswapXDefaultEnabled = 'uniswapx_default_enabled',
|
|
||||||
quickRouteMainnet = 'enable_quick_route_mainnet',
|
quickRouteMainnet = 'enable_quick_route_mainnet',
|
||||||
progressIndicatorV2 = 'progress_indicator_v2',
|
progressIndicatorV2 = 'progress_indicator_v2',
|
||||||
feesEnabled = 'fees_enabled',
|
feesEnabled = 'fees_enabled',
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
import { SkipToken, skipToken } from '@reduxjs/toolkit/query/react'
|
import { SkipToken, skipToken } from '@reduxjs/toolkit/query/react'
|
||||||
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
|
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
|
||||||
import { useUniswapXDefaultEnabled } from 'featureFlags/flags/uniswapXDefault'
|
|
||||||
import { useUniswapXSyntheticQuoteEnabled } from 'featureFlags/flags/uniswapXUseSyntheticQuote'
|
import { useUniswapXSyntheticQuoteEnabled } from 'featureFlags/flags/uniswapXUseSyntheticQuote'
|
||||||
import { useFeesEnabled } from 'featureFlags/flags/useFees'
|
import { useFeesEnabled } from 'featureFlags/flags/useFees'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { GetQuoteArgs, INTERNAL_ROUTER_PREFERENCE_PRICE, RouterPreference } from 'state/routing/types'
|
import { GetQuoteArgs, INTERNAL_ROUTER_PREFERENCE_PRICE, RouterPreference } from 'state/routing/types'
|
||||||
import { currencyAddressForSwapQuote } from 'state/routing/utils'
|
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
|
* 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
|
routerPreference: RouterPreference | typeof INTERNAL_ROUTER_PREFERENCE_PRICE
|
||||||
}): GetQuoteArgs | SkipToken {
|
}): GetQuoteArgs | SkipToken {
|
||||||
const uniswapXForceSyntheticQuotes = useUniswapXSyntheticQuoteEnabled()
|
const uniswapXForceSyntheticQuotes = useUniswapXSyntheticQuoteEnabled()
|
||||||
const userDisabledUniswapX = useUserDisabledUniswapX()
|
|
||||||
const userOptedOutOfUniswapX = useUserOptedOutOfUniswapX()
|
|
||||||
const isUniswapXDefaultEnabled = useUniswapXDefaultEnabled()
|
|
||||||
|
|
||||||
const feesEnabled = useFeesEnabled()
|
const feesEnabled = useFeesEnabled()
|
||||||
// Don't enable fee logic if this is a quote for pricing
|
// Don't enable fee logic if this is a quote for pricing
|
||||||
@ -56,23 +51,8 @@ export function useRoutingAPIArguments({
|
|||||||
tradeType,
|
tradeType,
|
||||||
needsWrapIfUniswapX: tokenIn.isNative,
|
needsWrapIfUniswapX: tokenIn.isNative,
|
||||||
uniswapXForceSyntheticQuotes,
|
uniswapXForceSyntheticQuotes,
|
||||||
userDisabledUniswapX,
|
|
||||||
userOptedOutOfUniswapX,
|
|
||||||
isUniswapXDefaultEnabled,
|
|
||||||
sendPortionEnabled,
|
sendPortionEnabled,
|
||||||
},
|
},
|
||||||
[
|
[account, amount, routerPreference, tokenIn, tokenOut, tradeType, uniswapXForceSyntheticQuotes, sendPortionEnabled]
|
||||||
account,
|
|
||||||
amount,
|
|
||||||
routerPreference,
|
|
||||||
tokenIn,
|
|
||||||
tokenOut,
|
|
||||||
tradeType,
|
|
||||||
uniswapXForceSyntheticQuotes,
|
|
||||||
userDisabledUniswapX,
|
|
||||||
userOptedOutOfUniswapX,
|
|
||||||
isUniswapXDefaultEnabled,
|
|
||||||
sendPortionEnabled,
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,7 @@ import ErrorBoundary from 'components/ErrorBoundary'
|
|||||||
import Loader from 'components/Icons/LoadingSpinner'
|
import Loader from 'components/Icons/LoadingSpinner'
|
||||||
import NavBar, { PageTabs } from 'components/NavBar'
|
import NavBar, { PageTabs } from 'components/NavBar'
|
||||||
import { UK_BANNER_HEIGHT, UK_BANNER_HEIGHT_MD, UK_BANNER_HEIGHT_SM, UkBanner } from 'components/NavBar/UkBanner'
|
import { UK_BANNER_HEIGHT, UK_BANNER_HEIGHT_MD, UK_BANNER_HEIGHT_SM, UkBanner } from 'components/NavBar/UkBanner'
|
||||||
import { FeatureFlag, useFeatureFlagsIsLoaded } from 'featureFlags'
|
import { useFeatureFlagsIsLoaded } from 'featureFlags'
|
||||||
import { useUniswapXDefaultEnabled } from 'featureFlags/flags/uniswapXDefault'
|
|
||||||
import { useAtom } from 'jotai'
|
import { useAtom } from 'jotai'
|
||||||
import { useBag } from 'nft/hooks/useBag'
|
import { useBag } from 'nft/hooks/useBag'
|
||||||
import { lazy, Suspense, useEffect, useLayoutEffect, useMemo, useState } from 'react'
|
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 { shouldDisableNFTRoutesAtom } from 'state/application/atoms'
|
||||||
import { useAppSelector } from 'state/hooks'
|
import { useAppSelector } from 'state/hooks'
|
||||||
import { AppState } from 'state/reducer'
|
import { AppState } from 'state/reducer'
|
||||||
import { RouterPreference } from 'state/routing/types'
|
import { useRouterPreference } from 'state/user/hooks'
|
||||||
import { useRouterPreference, useUserOptedOutOfUniswapX } from 'state/user/hooks'
|
import { StatsigProvider, StatsigUser } from 'statsig-react'
|
||||||
import { StatsigProvider, StatsigUser, useGate } from 'statsig-react'
|
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import DarkModeQueryParamReader from 'theme/components/DarkModeQueryParamReader'
|
import DarkModeQueryParamReader from 'theme/components/DarkModeQueryParamReader'
|
||||||
import { useIsDarkMode } from 'theme/components/ThemeToggle'
|
import { useIsDarkMode } from 'theme/components/ThemeToggle'
|
||||||
@ -212,9 +210,6 @@ function UserPropertyUpdater() {
|
|||||||
const isDarkMode = useIsDarkMode()
|
const isDarkMode = useIsDarkMode()
|
||||||
|
|
||||||
const [routerPreference] = useRouterPreference()
|
const [routerPreference] = useRouterPreference()
|
||||||
const userOptedOutOfUniswapX = useUserOptedOutOfUniswapX()
|
|
||||||
const isUniswapXDefaultEnabled = useUniswapXDefaultEnabled()
|
|
||||||
const { isLoading: isUniswapXDefaultLoading } = useGate(FeatureFlag.uniswapXDefaultEnabled)
|
|
||||||
const rehydrated = useAppSelector((state) => state._persist.rehydrated)
|
const rehydrated = useAppSelector((state) => state._persist.rehydrated)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -248,22 +243,8 @@ function UserPropertyUpdater() {
|
|||||||
}, [isDarkMode])
|
}, [isDarkMode])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isUniswapXDefaultLoading || !rehydrated) return
|
if (!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)
|
user.set(CustomUserProperties.ROUTER_PREFERENCE, routerPreference)
|
||||||
return
|
}, [routerPreference, rehydrated])
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
user.set(CustomUserProperties.ROUTER_PREFERENCE, routerPreference)
|
|
||||||
}, [routerPreference, isUniswapXDefaultEnabled, userOptedOutOfUniswapX, isUniswapXDefaultLoading, rehydrated])
|
|
||||||
return null
|
return 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 (
|
|
||||||
<Trace
|
|
||||||
shouldLogImpression
|
|
||||||
name="UniswapX Opt In Impression"
|
|
||||||
properties={trade ? formatCommonPropertiesForTrade(trade, allowedSlippage) : undefined}
|
|
||||||
>
|
|
||||||
<OptInContents isOnClassic={isOnClassic} {...props} />
|
|
||||||
</Trace>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = (
|
|
||||||
<ThemedText.BodySecondary
|
|
||||||
color="accent1"
|
|
||||||
fontSize={14}
|
|
||||||
fontWeight="500"
|
|
||||||
onClick={() => {
|
|
||||||
// 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
|
|
||||||
</ThemedText.BodySecondary>
|
|
||||||
)
|
|
||||||
|
|
||||||
const containerRef = useRef<HTMLDivElement>()
|
|
||||||
|
|
||||||
if (isSmall || location.pathname.includes('/tokens/')) {
|
|
||||||
return (
|
|
||||||
<SwapOptInSmallContainer ref={containerRef as any} visible={isVisible}>
|
|
||||||
<SwapMustache>
|
|
||||||
<UniswapXShine />
|
|
||||||
<SwapMustacheShadow />
|
|
||||||
<Row justify="space-between" align="center" flexWrap="wrap">
|
|
||||||
<Text fontSize={14} fontWeight={485} lineHeight="20px">
|
|
||||||
<Trans>Try gas free swaps with the</Trans>
|
|
||||||
<br />
|
|
||||||
<UniswapXBrandMark fontWeight="bold" style={{ transform: `translateY(1px)`, margin: '0 2px' }} />{' '}
|
|
||||||
<Trans>Beta</Trans>
|
|
||||||
</Text>
|
|
||||||
{tryItNowElement}
|
|
||||||
</Row>
|
|
||||||
</SwapMustache>
|
|
||||||
</SwapOptInSmallContainer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{/* first popover: intro */}
|
|
||||||
<UniswapXOptInPopover shiny visible={isVisible && !showYoureIn}>
|
|
||||||
<CloseIcon
|
|
||||||
size={18}
|
|
||||||
onClick={() => {
|
|
||||||
if (!trade) return
|
|
||||||
sendAnalyticsEvent('UniswapX Opt In Toggled', {
|
|
||||||
...formatCommonPropertiesForTrade(trade, allowedSlippage),
|
|
||||||
new_preference: RouterPreference.API,
|
|
||||||
})
|
|
||||||
setRouterPreference(RouterPreference.API)
|
|
||||||
dispatch(updateDisabledUniswapX({ disabledUniswapX: true }))
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Column>
|
|
||||||
<Text fontSize={14} fontWeight={485} lineHeight="20px">
|
|
||||||
<Trans>Try the</Trans>{' '}
|
|
||||||
<UniswapXBrandMark fontWeight="bold" style={{ transform: `translateY(2px)`, margin: '0 1px' }} />{' '}
|
|
||||||
<Trans>Beta</Trans>
|
|
||||||
<ul style={{ margin: '5px 0 12px 24px', lineHeight: '24px', padding: 0 }}>
|
|
||||||
<li>
|
|
||||||
<Trans>Gas free swaps</Trans>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Trans>MEV protection</Trans>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Trans>Better prices and more liquidity</Trans>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</Text>
|
|
||||||
</Column>
|
|
||||||
|
|
||||||
{tryItNowElement}
|
|
||||||
</UniswapXOptInPopover>
|
|
||||||
|
|
||||||
{/* second popover: you're in! */}
|
|
||||||
<UniswapXOptInPopover visible={showYoureIn}>
|
|
||||||
<UniswapXRouterLabel disableTextGradient>
|
|
||||||
<Text fontSize={14} fontWeight={535} lineHeight="20px">
|
|
||||||
<Trans>You're in!</Trans>
|
|
||||||
</Text>
|
|
||||||
</UniswapXRouterLabel>
|
|
||||||
|
|
||||||
<ThemedText.BodySecondary style={{ marginTop: 8 }} fontSize={14}>
|
|
||||||
<Trans>You can turn it off at anytime in settings</Trans>
|
|
||||||
</ThemedText.BodySecondary>
|
|
||||||
</UniswapXOptInPopover>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
<UniswapXOptInLargeContainerPositioner>
|
|
||||||
<UniswapXOptInLargeContainer visible={props.visible}>
|
|
||||||
<Arrow className="arrow-right" style={{ position: 'absolute', bottom: '50%', left: -3.5, zIndex: 100 }} />
|
|
||||||
<UniswapPopoverContainer>
|
|
||||||
{props.shiny && <UniswapXShine style={{ zIndex: 0 }} />}
|
|
||||||
{props.children}
|
|
||||||
</UniswapPopoverContainer>
|
|
||||||
</UniswapXOptInLargeContainer>
|
|
||||||
</UniswapXOptInLargeContainerPositioner>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const CloseIcon = styled(X)`
|
|
||||||
color: ${({ theme }) => theme.neutral3};
|
|
||||||
cursor: pointer;
|
|
||||||
position: absolute;
|
|
||||||
top: 14px;
|
|
||||||
right: 14px;
|
|
||||||
`
|
|
@ -33,7 +33,6 @@ import { useConnectionReady } from 'connection/eagerlyConnect'
|
|||||||
import { getChainInfo } from 'constants/chainInfo'
|
import { getChainInfo } from 'constants/chainInfo'
|
||||||
import { asSupportedChain, isSupportedChain } from 'constants/chains'
|
import { asSupportedChain, isSupportedChain } from 'constants/chains'
|
||||||
import { getSwapCurrencyId, TOKEN_SHORTHANDS } from 'constants/tokens'
|
import { getSwapCurrencyId, TOKEN_SHORTHANDS } from 'constants/tokens'
|
||||||
import { useUniswapXDefaultEnabled } from 'featureFlags/flags/uniswapXDefault'
|
|
||||||
import { useCurrency, useDefaultActiveTokens } from 'hooks/Tokens'
|
import { useCurrency, useDefaultActiveTokens } from 'hooks/Tokens'
|
||||||
import { useIsSwapUnsupported } from 'hooks/useIsSwapUnsupported'
|
import { useIsSwapUnsupported } from 'hooks/useIsSwapUnsupported'
|
||||||
import { useMaxAmountIn } from 'hooks/useMaxAmountIn'
|
import { useMaxAmountIn } from 'hooks/useMaxAmountIn'
|
||||||
@ -64,10 +63,8 @@ import { maxAmountSpend } from 'utils/maxAmountSpend'
|
|||||||
import { computeRealizedPriceImpact, warningSeverity } from 'utils/prices'
|
import { computeRealizedPriceImpact, warningSeverity } from 'utils/prices'
|
||||||
import { didUserReject } from 'utils/swapErrorToUserReadableMessage'
|
import { didUserReject } from 'utils/swapErrorToUserReadableMessage'
|
||||||
|
|
||||||
import { useScreenSize } from '../../hooks/useScreenSize'
|
|
||||||
import { useIsDarkMode } from '../../theme/components/ThemeToggle'
|
import { useIsDarkMode } from '../../theme/components/ThemeToggle'
|
||||||
import { OutputTaxTooltipBody } from './TaxTooltipBody'
|
import { OutputTaxTooltipBody } from './TaxTooltipBody'
|
||||||
import { UniswapXOptIn } from './UniswapXOptIn'
|
|
||||||
|
|
||||||
export const ArrowContainer = styled.div`
|
export const ArrowContainer = styled.div`
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
@ -597,9 +594,7 @@ export function Swap({
|
|||||||
const inputCurrency = currencies[Field.INPUT] ?? undefined
|
const inputCurrency = currencies[Field.INPUT] ?? undefined
|
||||||
const switchChain = useSwitchChain()
|
const switchChain = useSwitchChain()
|
||||||
const switchingChain = useAppSelector((state) => state.wallets.switchingChain)
|
const switchingChain = useAppSelector((state) => state.wallets.switchingChain)
|
||||||
const showOptInSmall = !useScreenSize().navSearchInputVisible
|
|
||||||
const isDark = useIsDarkMode()
|
const isDark = useIsDarkMode()
|
||||||
const isUniswapXDefaultEnabled = useUniswapXDefaultEnabled()
|
|
||||||
|
|
||||||
const [currentTab, setCurrentTab] = useState<SwapTab>(SwapTab.Swap)
|
const [currentTab, setCurrentTab] = useState<SwapTab>(SwapTab.Swap)
|
||||||
|
|
||||||
@ -832,7 +827,6 @@ export function Swap({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</AutoColumn>
|
</AutoColumn>
|
||||||
{!showOptInSmall && !isUniswapXDefaultEnabled && <UniswapXOptIn isSmall={false} swapInfo={swapInfo} />}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -849,7 +843,6 @@ export function Swap({
|
|||||||
/>
|
/>
|
||||||
{/* todo: build Limit UI */}
|
{/* todo: build Limit UI */}
|
||||||
{currentTab === SwapTab.Swap ? swapElement : undefined}
|
{currentTab === SwapTab.Swap ? swapElement : undefined}
|
||||||
{showOptInSmall && !isUniswapXDefaultEnabled && <UniswapXOptIn isSmall swapInfo={swapInfo} />}
|
|
||||||
</SwapWrapper>
|
</SwapWrapper>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ const defaultState = {
|
|||||||
user: {},
|
user: {},
|
||||||
_persist: {
|
_persist: {
|
||||||
rehydrated: true,
|
rehydrated: true,
|
||||||
version: 4,
|
version: 5,
|
||||||
},
|
},
|
||||||
application: {
|
application: {
|
||||||
chainId: null,
|
chainId: null,
|
||||||
|
@ -6,6 +6,7 @@ import { migration1 } from './migrations/1'
|
|||||||
import { migration2 } from './migrations/2'
|
import { migration2 } from './migrations/2'
|
||||||
import { migration3 } from './migrations/3'
|
import { migration3 } from './migrations/3'
|
||||||
import { migration4 } from './migrations/4'
|
import { migration4 } from './migrations/4'
|
||||||
|
import { migration5 } from './migrations/5'
|
||||||
import { legacyLocalStorageMigration } from './migrations/legacy'
|
import { legacyLocalStorageMigration } from './migrations/legacy'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -23,6 +24,7 @@ export const migrations: MigrationManifest = {
|
|||||||
2: migration2,
|
2: migration2,
|
||||||
3: migration3,
|
3: migration3,
|
||||||
4: migration4,
|
4: migration4,
|
||||||
|
5: migration5,
|
||||||
}
|
}
|
||||||
|
|
||||||
// We use a custom migration function for the initial state, because redux-persist
|
// We use a custom migration function for the initial state, because redux-persist
|
||||||
|
98
src/state/migrations/5.test.ts
Normal file
98
src/state/migrations/5.test.ts
Normal file
@ -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)
|
||||||
|
})
|
||||||
|
})
|
43
src/state/migrations/5.ts
Normal file
43
src/state/migrations/5.ts
Normal file
@ -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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
@ -44,7 +44,7 @@ export type AppState = ReturnType<typeof appReducer>
|
|||||||
|
|
||||||
const persistConfig: PersistConfig<AppState> = {
|
const persistConfig: PersistConfig<AppState> = {
|
||||||
key: 'interface',
|
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({
|
storage: localForage.createInstance({
|
||||||
name: 'redux',
|
name: 'redux',
|
||||||
}),
|
}),
|
||||||
|
@ -89,8 +89,6 @@ interface ExpectedUserState {
|
|||||||
timestamp: number
|
timestamp: number
|
||||||
hideAndroidAnnouncementBanner: boolean
|
hideAndroidAnnouncementBanner: boolean
|
||||||
showSurveyPopup?: boolean
|
showSurveyPopup?: boolean
|
||||||
disabledUniswapX?: boolean
|
|
||||||
optedOutOfUniswapX?: boolean
|
|
||||||
originCountry?: string
|
originCountry?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,9 +63,9 @@ function getRoutingAPIConfig(args: GetQuoteArgs): RoutingConfig {
|
|||||||
|
|
||||||
if (
|
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.
|
// 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) ||
|
routerPreference === RouterPreference.API ||
|
||||||
!isUniswapXSupportedChain(tokenInChainId) ||
|
routerPreference === INTERNAL_ROUTER_PREFERENCE_PRICE ||
|
||||||
routerPreference === INTERNAL_ROUTER_PREFERENCE_PRICE
|
!isUniswapXSupportedChain(tokenInChainId)
|
||||||
) {
|
) {
|
||||||
return [classic]
|
return [classic]
|
||||||
}
|
}
|
||||||
|
@ -43,11 +43,6 @@ export interface GetQuoteArgs {
|
|||||||
tradeType: TradeType
|
tradeType: TradeType
|
||||||
needsWrapIfUniswapX: boolean
|
needsWrapIfUniswapX: boolean
|
||||||
uniswapXForceSyntheticQuotes: 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
|
sendPortionEnabled: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,7 +192,6 @@ export class ClassicTrade extends Trade<Currency, Currency, TradeType> {
|
|||||||
approveInfo: ApproveInfo
|
approveInfo: ApproveInfo
|
||||||
gasUseEstimateUSD?: number // gas estimate for swaps
|
gasUseEstimateUSD?: number // gas estimate for swaps
|
||||||
blockNumber: string | null | undefined
|
blockNumber: string | null | undefined
|
||||||
isUniswapXBetter: boolean | undefined
|
|
||||||
requestId: string | undefined
|
requestId: string | undefined
|
||||||
quoteMethod: QuoteMethod
|
quoteMethod: QuoteMethod
|
||||||
swapFee: SwapFeeInfo | undefined
|
swapFee: SwapFeeInfo | undefined
|
||||||
@ -205,7 +199,6 @@ export class ClassicTrade extends Trade<Currency, Currency, TradeType> {
|
|||||||
constructor({
|
constructor({
|
||||||
gasUseEstimateUSD,
|
gasUseEstimateUSD,
|
||||||
blockNumber,
|
blockNumber,
|
||||||
isUniswapXBetter,
|
|
||||||
requestId,
|
requestId,
|
||||||
quoteMethod,
|
quoteMethod,
|
||||||
approveInfo,
|
approveInfo,
|
||||||
@ -215,7 +208,6 @@ export class ClassicTrade extends Trade<Currency, Currency, TradeType> {
|
|||||||
gasUseEstimateUSD?: number
|
gasUseEstimateUSD?: number
|
||||||
totalGasUseEstimateUSD?: number
|
totalGasUseEstimateUSD?: number
|
||||||
blockNumber?: string | null
|
blockNumber?: string | null
|
||||||
isUniswapXBetter?: boolean
|
|
||||||
requestId?: string
|
requestId?: string
|
||||||
quoteMethod: QuoteMethod
|
quoteMethod: QuoteMethod
|
||||||
approveInfo: ApproveInfo
|
approveInfo: ApproveInfo
|
||||||
@ -240,7 +232,6 @@ export class ClassicTrade extends Trade<Currency, Currency, TradeType> {
|
|||||||
super(routes)
|
super(routes)
|
||||||
this.blockNumber = blockNumber
|
this.blockNumber = blockNumber
|
||||||
this.gasUseEstimateUSD = gasUseEstimateUSD
|
this.gasUseEstimateUSD = gasUseEstimateUSD
|
||||||
this.isUniswapXBetter = isUniswapXBetter
|
|
||||||
this.requestId = requestId
|
this.requestId = requestId
|
||||||
this.quoteMethod = quoteMethod
|
this.quoteMethod = quoteMethod
|
||||||
this.approveInfo = approveInfo
|
this.approveInfo = approveInfo
|
||||||
|
@ -3,13 +3,12 @@ import { renderHook } from '@testing-library/react'
|
|||||||
import { CurrencyAmount, TradeType } from '@uniswap/sdk-core'
|
import { CurrencyAmount, TradeType } from '@uniswap/sdk-core'
|
||||||
import { AVERAGE_L1_BLOCK_TIME } from 'constants/chainInfo'
|
import { AVERAGE_L1_BLOCK_TIME } from 'constants/chainInfo'
|
||||||
import { USDC_MAINNET } from 'constants/tokens'
|
import { USDC_MAINNET } from 'constants/tokens'
|
||||||
import { useUniswapXDefaultEnabled } from 'featureFlags/flags/uniswapXDefault'
|
|
||||||
import { useUniswapXSyntheticQuoteEnabled } from 'featureFlags/flags/uniswapXUseSyntheticQuote'
|
import { useUniswapXSyntheticQuoteEnabled } from 'featureFlags/flags/uniswapXUseSyntheticQuote'
|
||||||
import { useFeesEnabled } from 'featureFlags/flags/useFees'
|
import { useFeesEnabled } from 'featureFlags/flags/useFees'
|
||||||
import useIsWindowVisible from 'hooks/useIsWindowVisible'
|
import useIsWindowVisible from 'hooks/useIsWindowVisible'
|
||||||
import ms from 'ms'
|
import ms from 'ms'
|
||||||
import { GetQuoteArgs, INTERNAL_ROUTER_PREFERENCE_PRICE, RouterPreference } from 'state/routing/types'
|
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 { ETH_MAINNET } from 'test-utils/constants'
|
||||||
import { mocked } from 'test-utils/mocked'
|
import { mocked } from 'test-utils/mocked'
|
||||||
|
|
||||||
@ -29,16 +28,12 @@ jest.mock('./slice', () => {
|
|||||||
})
|
})
|
||||||
jest.mock('state/user/hooks')
|
jest.mock('state/user/hooks')
|
||||||
jest.mock('featureFlags/flags/uniswapXUseSyntheticQuote')
|
jest.mock('featureFlags/flags/uniswapXUseSyntheticQuote')
|
||||||
jest.mock('featureFlags/flags/uniswapXDefault')
|
|
||||||
jest.mock('featureFlags/flags/useFees')
|
jest.mock('featureFlags/flags/useFees')
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mocked(useIsWindowVisible).mockReturnValue(true)
|
mocked(useIsWindowVisible).mockReturnValue(true)
|
||||||
mocked(useRouterPreference).mockReturnValue([RouterPreference.API, () => undefined])
|
mocked(useRouterPreference).mockReturnValue([RouterPreference.API, () => undefined])
|
||||||
mocked(useUniswapXSyntheticQuoteEnabled).mockReturnValue(false)
|
mocked(useUniswapXSyntheticQuoteEnabled).mockReturnValue(false)
|
||||||
mocked(useUserDisabledUniswapX).mockReturnValue(false)
|
|
||||||
mocked(useUserOptedOutOfUniswapX).mockReturnValue(false)
|
|
||||||
mocked(useUniswapXDefaultEnabled).mockReturnValue(false)
|
|
||||||
mocked(useFeesEnabled).mockReturnValue(true)
|
mocked(useFeesEnabled).mockReturnValue(true)
|
||||||
// @ts-ignore we dont use the response from this hook in useRoutingAPITrade so fine to mock as undefined
|
// @ts-ignore we dont use the response from this hook in useRoutingAPITrade so fine to mock as undefined
|
||||||
mocked(useGetQuoteQuery).mockReturnValue(undefined)
|
mocked(useGetQuoteQuery).mockReturnValue(undefined)
|
||||||
@ -66,9 +61,6 @@ const MOCK_ARGS: GetQuoteArgs = {
|
|||||||
tradeType: TradeType.EXACT_INPUT,
|
tradeType: TradeType.EXACT_INPUT,
|
||||||
needsWrapIfUniswapX: USDCAmount.currency.isNative,
|
needsWrapIfUniswapX: USDCAmount.currency.isNative,
|
||||||
uniswapXForceSyntheticQuotes: false,
|
uniswapXForceSyntheticQuotes: false,
|
||||||
userDisabledUniswapX: false,
|
|
||||||
userOptedOutOfUniswapX: false,
|
|
||||||
isUniswapXDefaultEnabled: false,
|
|
||||||
sendPortionEnabled: true,
|
sendPortionEnabled: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,18 +219,14 @@ export async function transformQuoteToTrade(
|
|||||||
data: URAQuoteResponse,
|
data: URAQuoteResponse,
|
||||||
quoteMethod: QuoteMethod
|
quoteMethod: QuoteMethod
|
||||||
): Promise<TradeResult> {
|
): Promise<TradeResult> {
|
||||||
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 [currencyIn, currencyOut] = getTradeCurrencies(args, showUniswapXTrade)
|
||||||
|
|
||||||
const { gasUseEstimateUSD, blockNumber, routes, gasUseEstimate, swapFee } = getClassicTradeDetails(args, 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
|
|
||||||
|
|
||||||
// Some sus javascript float math but it's ok because its just an estimate for display purposes
|
// Some sus javascript float math but it's ok because its just an estimate for display purposes
|
||||||
const usdCostPerGas = gasUseEstimateUSD && gasUseEstimate ? gasUseEstimateUSD / gasUseEstimate : undefined
|
const usdCostPerGas = gasUseEstimateUSD && gasUseEstimate ? gasUseEstimateUSD / gasUseEstimate : undefined
|
||||||
|
|
||||||
@ -267,15 +263,14 @@ export async function transformQuoteToTrade(
|
|||||||
gasUseEstimateUSD,
|
gasUseEstimateUSD,
|
||||||
approveInfo,
|
approveInfo,
|
||||||
blockNumber,
|
blockNumber,
|
||||||
isUniswapXBetter,
|
|
||||||
requestId: data.quote.requestId,
|
requestId: data.quote.requestId,
|
||||||
quoteMethod,
|
quoteMethod,
|
||||||
swapFee,
|
swapFee,
|
||||||
})
|
})
|
||||||
|
|
||||||
// During the opt-in period, only return UniswapX quotes if the user has turned on the setting,
|
// If the top-level URA quote type is DUTCH_LIMIT, then UniswapX is better for the user
|
||||||
// even if it is the better quote.
|
const isUniswapXBetter = data.routing === URAQuoteType.DUTCH_LIMIT
|
||||||
if (isUniswapXBetter && (routerPreference === RouterPreference.X || isUniswapXDefaultEnabled)) {
|
if (isUniswapXBetter) {
|
||||||
const orderInfo = toDutchOrderInfo(data.quote.orderInfo)
|
const orderInfo = toDutchOrderInfo(data.quote.orderInfo)
|
||||||
const swapFee = getSwapFee(data.quote)
|
const swapFee = getSwapFee(data.quote)
|
||||||
const wrapInfo = await getWrapInfo(needsWrapIfUniswapX, account, currencyIn.chainId, amount, usdCostPerGas)
|
const wrapInfo = await getWrapInfo(needsWrapIfUniswapX, account, currencyIn.chainId, amount, usdCostPerGas)
|
||||||
|
@ -77,7 +77,7 @@ const BAD_RECIPIENT_ADDRESSES: { [address: string]: true } = {
|
|||||||
'0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D': true, // v2 router 02
|
'0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D': true, // v2 router 02
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SwapInfo = {
|
type SwapInfo = {
|
||||||
currencies: { [field in Field]?: Currency }
|
currencies: { [field in Field]?: Currency }
|
||||||
currencyBalances: { [field in Field]?: CurrencyAmount<Currency> }
|
currencyBalances: { [field in Field]?: CurrencyAmount<Currency> }
|
||||||
inputTax: Percent
|
inputTax: Percent
|
||||||
|
@ -5,13 +5,7 @@ import store from 'state'
|
|||||||
import { RouterPreference } from 'state/routing/types'
|
import { RouterPreference } from 'state/routing/types'
|
||||||
import { renderHook } from 'test-utils/render'
|
import { renderHook } from 'test-utils/render'
|
||||||
|
|
||||||
import {
|
import { deserializeToken, serializeToken, useRouterPreference, useUserSlippageTolerance } from './hooks'
|
||||||
deserializeToken,
|
|
||||||
serializeToken,
|
|
||||||
useRouterPreference,
|
|
||||||
useUserDisabledUniswapX,
|
|
||||||
useUserSlippageTolerance,
|
|
||||||
} from './hooks'
|
|
||||||
import { updateUserSlippageTolerance } from './reducer'
|
import { updateUserSlippageTolerance } from './reducer'
|
||||||
import { SlippageTolerance } from './types'
|
import { SlippageTolerance } from './types'
|
||||||
|
|
||||||
@ -81,12 +75,3 @@ describe('useRouterPreference', () => {
|
|||||||
expect(routerPreference).toBe(RouterPreference.X)
|
expect(routerPreference).toBe(RouterPreference.X)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('useUserDisabledUniswapX', () => {
|
|
||||||
it('returns `false` by default', () => {
|
|
||||||
const {
|
|
||||||
result: { current: disabledUniswapX },
|
|
||||||
} = renderHook(() => useUserDisabledUniswapX())
|
|
||||||
expect(disabledUniswapX).toBe(false)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
@ -217,14 +217,6 @@ export function useHideAndroidAnnouncementBanner(): [boolean, () => void] {
|
|||||||
return [hideAndroidAnnouncementBanner, toggleHideAndroidAnnouncementBanner]
|
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
|
* Given two tokens return the liquidity token that represents its liquidity shares
|
||||||
* @param tokenA one of the two tokens
|
* @param tokenA one of the two tokens
|
||||||
|
@ -48,10 +48,6 @@ export interface UserState {
|
|||||||
|
|
||||||
timestamp: number
|
timestamp: number
|
||||||
hideAndroidAnnouncementBanner: boolean
|
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
|
// undefined means has not gone through A/B split yet
|
||||||
showSurveyPopup?: boolean
|
showSurveyPopup?: boolean
|
||||||
|
|
||||||
@ -111,12 +107,6 @@ const userSlice = createSlice({
|
|||||||
updateHideAndroidAnnouncementBanner(state, action) {
|
updateHideAndroidAnnouncementBanner(state, action) {
|
||||||
state.hideAndroidAnnouncementBanner = action.payload.hideAndroidAnnouncementBanner
|
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 } }) {
|
addSerializedToken(state, { payload: { serializedToken } }) {
|
||||||
if (!state.tokens) {
|
if (!state.tokens) {
|
||||||
state.tokens = {}
|
state.tokens = {}
|
||||||
@ -153,7 +143,5 @@ export const {
|
|||||||
updateUserLocale,
|
updateUserLocale,
|
||||||
updateUserSlippageTolerance,
|
updateUserSlippageTolerance,
|
||||||
updateHideAndroidAnnouncementBanner,
|
updateHideAndroidAnnouncementBanner,
|
||||||
updateDisabledUniswapX,
|
|
||||||
updateOptedOutOfUniswapX,
|
|
||||||
} = userSlice.actions
|
} = userSlice.actions
|
||||||
export default userSlice.reducer
|
export default userSlice.reducer
|
||||||
|
Loading…
Reference in New Issue
Block a user