Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e1ab5fcd2 | ||
|
|
3f510aabcc | ||
|
|
b29968d014 | ||
|
|
a07556b87d | ||
|
|
5b551af25a | ||
|
|
69f6ca2635 |
1
CODEOWNERS
Normal file
1
CODEOWNERS
Normal file
@ -0,0 +1 @@
|
|||||||
|
* @uniswap/web-admins
|
||||||
@ -9,7 +9,6 @@ describe('Swap settings', () => {
|
|||||||
cy.contains('Max. slippage').should('exist')
|
cy.contains('Max. slippage').should('exist')
|
||||||
cy.contains('Transaction deadline').should('exist')
|
cy.contains('Transaction deadline').should('exist')
|
||||||
cy.contains('UniswapX').should('exist')
|
cy.contains('UniswapX').should('exist')
|
||||||
cy.contains('Local routing').should('exist')
|
|
||||||
cy.get(getTestSelector('open-settings-dialog-button')).click()
|
cy.get(getTestSelector('open-settings-dialog-button')).click()
|
||||||
cy.contains('Settings').should('not.exist')
|
cy.contains('Settings').should('not.exist')
|
||||||
})
|
})
|
||||||
@ -28,7 +27,6 @@ describe('Swap settings', () => {
|
|||||||
.within(() => {
|
.within(() => {
|
||||||
cy.contains('Max. slippage').should('exist')
|
cy.contains('Max. slippage').should('exist')
|
||||||
cy.contains('UniswapX').should('exist')
|
cy.contains('UniswapX').should('exist')
|
||||||
cy.contains('Local routing').should('exist')
|
|
||||||
cy.contains('Transaction deadline').should('exist')
|
cy.contains('Transaction deadline').should('exist')
|
||||||
cy.get(getTestSelector('mobile-settings-close')).click()
|
cy.get(getTestSelector('mobile-settings-close')).click()
|
||||||
})
|
})
|
||||||
|
|||||||
@ -3,27 +3,27 @@
|
|||||||
"relation": ["delegate_permission/common.handle_all_urls"],
|
"relation": ["delegate_permission/common.handle_all_urls"],
|
||||||
"target": {
|
"target": {
|
||||||
"namespace": "android_app",
|
"namespace": "android_app",
|
||||||
"package_name": "com.uniswap",
|
"package_name": "com.uniswap.mobile",
|
||||||
"sha256_cert_fingerprints":
|
"sha256_cert_fingerprints":
|
||||||
["97:A5:81:51:DA:AF:8F:6E:65:3A:90:1E:82:12:6C:FB:61:2D:36:C7:CF:20:61:6B:A3:4C:52:CA:BC:58:43:8E", "F9:E9:E3:F0:04:28:66:62:81:44:50:7E:D6:A9:5F:B9:65:39:02:70:1D:13:74:15:D3:E1:A3:1B:D4:38:3A:1F"]
|
["49:D9:3D:5D:FB:AA:64:A4:64:80:85:0F:39:A8:C1:D9:25:D3:D4:BC:8E:6B:1F:45:0C:EA:AF:B1:0C:27:DF:B8", "F9:E9:E3:F0:04:28:66:62:81:44:50:7E:D6:A9:5F:B9:65:39:02:70:1D:13:74:15:D3:E1:A3:1B:D4:38:3A:1F"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"relation": ["delegate_permission/common.handle_all_urls"],
|
"relation": ["delegate_permission/common.handle_all_urls"],
|
||||||
"target": {
|
"target": {
|
||||||
"namespace": "android_app",
|
"namespace": "android_app",
|
||||||
"package_name": "com.uniswap.beta",
|
"package_name": "com.uniswap.mobile.beta",
|
||||||
"sha256_cert_fingerprints":
|
"sha256_cert_fingerprints":
|
||||||
["E5:39:87:DC:4D:FD:4C:1B:A6:74:36:7D:3A:3B:6B:ED:9E:B3:66:89:92:8A:1B:B8:FC:1B:22:56:56:B4:46:A3", "54:4B:62:33:17:9B:5F:A8:E6:5D:D3:A6:E5:9D:80:5F:A5:02:7F:E2:14:B8:C1:7A:AC:4B:8D:E0:65:49:87:41"]
|
["75:41:9C:2D:01:4A:88:4E:8D:C6:EF:E5:51:54:28:6B:99:05:31:43:AD:84:B4:EB:39:28:B8:C3:C4:CE:48:E3", "54:4B:62:33:17:9B:5F:A8:E6:5D:D3:A6:E5:9D:80:5F:A5:02:7F:E2:14:B8:C1:7A:AC:4B:8D:E0:65:49:87:41"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"relation": ["delegate_permission/common.handle_all_urls"],
|
"relation": ["delegate_permission/common.handle_all_urls"],
|
||||||
"target": {
|
"target": {
|
||||||
"namespace": "android_app",
|
"namespace": "android_app",
|
||||||
"package_name": "com.uniswap.dev",
|
"package_name": "com.uniswap.mobile.dev",
|
||||||
"sha256_cert_fingerprints":
|
"sha256_cert_fingerprints":
|
||||||
["5A:6D:23:50:2F:1E:0D:01:DC:96:65:F3:3A:18:4C:4C:8C:67:E0:09:99:9B:B1:9B:BF:44:99:D0:D1:D0:FC:5E", "02:E6:1C:76:8C:75:C3:78:C8:8C:FE:7B:2E:8F:4B:E1:FA:47:F2:F6:1A:DB:57:69:4A:41:99:C6:71:2C:AB:E3", "FA:C6:17:45:DC:09:03:78:6F:B9:ED:E6:2A:96:2B:39:9F:73:48:F0:BB:6F:89:9B:83:32:66:75:91:03:3B:9C"]
|
["45:F8:15:02:C5:4F:AD:82:E7:51:F0:9C:D1:CA:77:C8:C9:BF:06:A6:D9:5A:55:4F:9E:B8:5F:81:33:2B:D0:DB", "02:E6:1C:76:8C:75:C3:78:C8:8C:FE:7B:2E:8F:4B:E1:FA:47:F2:F6:1A:DB:57:69:4A:41:99:C6:71:2C:AB:E3", "FA:C6:17:45:DC:09:03:78:6F:B9:ED:E6:2A:96:2B:39:9F:73:48:F0:BB:6F:89:9B:83:32:66:75:91:03:3B:9C"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -114,7 +114,7 @@ export function AddRemoveTabs({
|
|||||||
)}
|
)}
|
||||||
</AddRemoveTitleText>
|
</AddRemoveTitleText>
|
||||||
{children && <Box style={{ marginRight: '.5rem' }}>{children}</Box>}
|
{children && <Box style={{ marginRight: '.5rem' }}>{children}</Box>}
|
||||||
<SettingsTab autoSlippage={autoSlippage} chainId={chainId} showRoutingSettings={false} />
|
<SettingsTab autoSlippage={autoSlippage} chainId={chainId} hideRoutingSettings />
|
||||||
</RowBetween>
|
</RowBetween>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -13,8 +13,10 @@ export default function RouterLabel({ trade, color }: { trade: SubmittableTrade;
|
|||||||
</UniswapXRouterLabel>
|
</UniswapXRouterLabel>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (trade.quoteMethod === QuoteMethod.CLIENT_SIDE || trade.quoteMethod === QuoteMethod.CLIENT_SIDE_FALLBACK) {
|
|
||||||
|
if (trade.quoteMethod === QuoteMethod.CLIENT_SIDE_FALLBACK) {
|
||||||
return <ThemedText.BodySmall color={color}>Uniswap Client</ThemedText.BodySmall>
|
return <ThemedText.BodySmall color={color}>Uniswap Client</ThemedText.BodySmall>
|
||||||
}
|
}
|
||||||
|
|
||||||
return <ThemedText.BodySmall color={color}>Uniswap API</ThemedText.BodySmall>
|
return <ThemedText.BodySmall color={color}>Uniswap API</ThemedText.BodySmall>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,18 +24,4 @@ describe('RouterPreferenceSettings', () => {
|
|||||||
expect(uniswapXToggle).toHaveAttribute('aria-selected', 'false')
|
expect(uniswapXToggle).toHaveAttribute('aria-selected', 'false')
|
||||||
expect(store.getState().user.userRouterPreference).toEqual(RouterPreference.API)
|
expect(store.getState().user.userRouterPreference).toEqual(RouterPreference.API)
|
||||||
})
|
})
|
||||||
it('toggles `Local Routing` router preference', () => {
|
|
||||||
render(<RouterPreferenceSettings />)
|
|
||||||
|
|
||||||
const localRoutingToggle = screen.getByTestId('toggle-local-routing-button')
|
|
||||||
|
|
||||||
fireEvent.click(localRoutingToggle)
|
|
||||||
expect(localRoutingToggle).toHaveAttribute('aria-selected', 'true')
|
|
||||||
expect(store.getState().user.userRouterPreference).toEqual(RouterPreference.CLIENT)
|
|
||||||
|
|
||||||
fireEvent.click(localRoutingToggle)
|
|
||||||
|
|
||||||
expect(localRoutingToggle).toHaveAttribute('aria-selected', 'false')
|
|
||||||
expect(store.getState().user.userRouterPreference).toEqual(RouterPreference.API)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,17 +1,15 @@
|
|||||||
import { Trans } from '@lingui/macro'
|
import { Trans } from '@lingui/macro'
|
||||||
import { useWeb3React } from '@web3-react/core'
|
|
||||||
import Column from 'components/Column'
|
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 { isUniswapXSupportedChain } from 'constants/chains'
|
|
||||||
import { useUniswapXDefaultEnabled } from 'featureFlags/flags/uniswapXDefault'
|
import { useUniswapXDefaultEnabled } from 'featureFlags/flags/uniswapXDefault'
|
||||||
import { useAppDispatch } from 'state/hooks'
|
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, useUserOptedOutOfUniswapX } from 'state/user/hooks'
|
||||||
import { updateDisabledUniswapX, updateOptedOutOfUniswapX } from 'state/user/reducer'
|
import { updateDisabledUniswapX, updateOptedOutOfUniswapX } from 'state/user/reducer'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import { Divider, ExternalLink, ThemedText } from 'theme/components'
|
import { ExternalLink, ThemedText } from 'theme/components'
|
||||||
|
|
||||||
const InlineLink = styled(ThemedText.BodySmall)`
|
const InlineLink = styled(ThemedText.BodySmall)`
|
||||||
color: ${({ theme }) => theme.accent1};
|
color: ${({ theme }) => theme.accent1};
|
||||||
@ -23,81 +21,48 @@ const InlineLink = styled(ThemedText.BodySmall)`
|
|||||||
`
|
`
|
||||||
|
|
||||||
export default function RouterPreferenceSettings() {
|
export default function RouterPreferenceSettings() {
|
||||||
const { chainId } = useWeb3React()
|
|
||||||
const [routerPreference, setRouterPreference] = useRouterPreference()
|
const [routerPreference, setRouterPreference] = useRouterPreference()
|
||||||
const uniswapXEnabled = chainId && isUniswapXSupportedChain(chainId)
|
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const userOptedOutOfUniswapX = useUserOptedOutOfUniswapX()
|
const userOptedOutOfUniswapX = useUserOptedOutOfUniswapX()
|
||||||
const isUniswapXDefaultEnabled = useUniswapXDefaultEnabled()
|
const isUniswapXDefaultEnabled = useUniswapXDefaultEnabled()
|
||||||
const isUniswapXOverrideEnabled = isUniswapXDefaultEnabled && !userOptedOutOfUniswapX
|
const isUniswapXOverrideEnabled = isUniswapXDefaultEnabled && !userOptedOutOfUniswapX
|
||||||
|
|
||||||
const uniswapXInEffect =
|
const uniswapXInEffect = routerPreference === RouterPreference.X || isUniswapXOverrideEnabled
|
||||||
routerPreference === RouterPreference.X ||
|
|
||||||
(routerPreference !== RouterPreference.CLIENT && isUniswapXOverrideEnabled)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<RowBetween gap="sm">
|
||||||
{uniswapXEnabled && (
|
<RowFixed>
|
||||||
<>
|
<Column gap="xs">
|
||||||
<RowBetween gap="sm">
|
<ThemedText.BodySecondary>
|
||||||
<RowFixed>
|
<UniswapXBrandMark />
|
||||||
<Column gap="xs">
|
</ThemedText.BodySecondary>
|
||||||
<ThemedText.BodySecondary>
|
<ThemedText.BodySmall color="neutral2">
|
||||||
<UniswapXBrandMark />
|
<Trans>When available, aggregates liquidity sources for better prices and gas free swaps.</Trans>{' '}
|
||||||
</ThemedText.BodySecondary>
|
<ExternalLink href="https://support.uniswap.org/hc/en-us/articles/17515415311501">
|
||||||
<ThemedText.BodySmall color="neutral2">
|
<InlineLink>Learn more</InlineLink>
|
||||||
<Trans>When available, aggregates liquidity sources for better prices and gas free swaps.</Trans>{' '}
|
</ExternalLink>
|
||||||
<ExternalLink href="https://support.uniswap.org/hc/en-us/articles/17515415311501">
|
</ThemedText.BodySmall>
|
||||||
<InlineLink>Learn more</InlineLink>
|
</Column>
|
||||||
</ExternalLink>
|
</RowFixed>
|
||||||
</ThemedText.BodySmall>
|
<Toggle
|
||||||
</Column>
|
id="toggle-uniswap-x-button"
|
||||||
</RowFixed>
|
// If UniswapX-by-default is enabled we need to render this as active even if routerPreference === RouterPreference.API
|
||||||
<Toggle
|
// because we're going to default to the UniswapX quote.
|
||||||
id="toggle-uniswap-x-button"
|
// If the user manually toggles it off, this doesn't apply.
|
||||||
// If UniswapX-by-default is enabled we need to render this as active even if routerPreference === RouterPreference.API
|
isActive={uniswapXInEffect}
|
||||||
// because we're going to default to the UniswapX quote.
|
toggle={() => {
|
||||||
// If the user manually toggles it off, this doesn't apply.
|
if (uniswapXInEffect) {
|
||||||
isActive={uniswapXInEffect}
|
if (isUniswapXDefaultEnabled) {
|
||||||
toggle={() => {
|
// We need to remember if a opts out of UniswapX, so we don't request UniswapX quotes.
|
||||||
if (uniswapXInEffect) {
|
dispatch(updateOptedOutOfUniswapX({ optedOutOfUniswapX: true }))
|
||||||
if (isUniswapXDefaultEnabled) {
|
} else {
|
||||||
// We need to remember if a opts out of UniswapX, so we don't request UniswapX quotes.
|
// We need to remember if a user disables Uniswap X, so we don't show the opt-in flow again.
|
||||||
dispatch(updateOptedOutOfUniswapX({ optedOutOfUniswapX: true }))
|
dispatch(updateDisabledUniswapX({ disabledUniswapX: 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>
|
|
||||||
<Divider />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<RowBetween gap="sm">
|
|
||||||
<RowFixed>
|
|
||||||
<Column gap="xs">
|
|
||||||
<ThemedText.BodySecondary>
|
|
||||||
<Trans>Local routing</Trans>
|
|
||||||
</ThemedText.BodySecondary>
|
|
||||||
</Column>
|
|
||||||
</RowFixed>
|
|
||||||
<Toggle
|
|
||||||
id="toggle-local-routing-button"
|
|
||||||
isActive={routerPreference === RouterPreference.CLIENT}
|
|
||||||
toggle={() =>
|
|
||||||
setRouterPreference(
|
|
||||||
routerPreference === RouterPreference.CLIENT
|
|
||||||
? isUniswapXDefaultEnabled
|
|
||||||
? RouterPreference.X
|
|
||||||
: RouterPreference.API
|
|
||||||
: RouterPreference.CLIENT
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
/>
|
setRouterPreference(uniswapXInEffect ? RouterPreference.API : RouterPreference.X)
|
||||||
</RowBetween>
|
}}
|
||||||
</>
|
/>
|
||||||
|
</RowBetween>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Percent } from '@uniswap/sdk-core'
|
import { Percent } from '@uniswap/sdk-core'
|
||||||
import { isSupportedChain } from 'constants/chains'
|
import { isSupportedChain, isUniswapXSupportedChain } from 'constants/chains'
|
||||||
import { mocked } from 'test-utils/mocked'
|
import { mocked } from 'test-utils/mocked'
|
||||||
import { fireEvent, render, screen, waitFor } from 'test-utils/render'
|
import { fireEvent, render, screen, waitFor } from 'test-utils/render'
|
||||||
|
|
||||||
@ -14,25 +14,38 @@ describe('Settings Tab', () => {
|
|||||||
mocked(isSupportedChain).mockReturnValue(true)
|
mocked(isSupportedChain).mockReturnValue(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders routing settings when showRoutingSettings is true', async () => {
|
it('renders routing settings when hideRoutingSettings is false', async () => {
|
||||||
render(<SettingsTab showRoutingSettings={true} chainId={1} autoSlippage={slippage} />)
|
mocked(isUniswapXSupportedChain).mockReturnValue(true)
|
||||||
|
render(<SettingsTab hideRoutingSettings={false} chainId={1} autoSlippage={slippage} />)
|
||||||
|
|
||||||
const settingsButton = screen.getByTestId('open-settings-dialog-button')
|
const settingsButton = screen.getByTestId('open-settings-dialog-button')
|
||||||
fireEvent.click(settingsButton)
|
fireEvent.click(settingsButton)
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('toggle-local-routing-button')).toBeInTheDocument()
|
expect(screen.getByTestId('toggle-uniswap-x-button')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('does not render routing settings when showRoutingSettings is false', async () => {
|
it('does not render routing settings when hideRoutingSettings is true', async () => {
|
||||||
render(<SettingsTab showRoutingSettings={false} chainId={1} autoSlippage={slippage} />)
|
render(<SettingsTab hideRoutingSettings chainId={1} autoSlippage={slippage} />)
|
||||||
|
|
||||||
const settingsButton = screen.getByTestId('open-settings-dialog-button')
|
const settingsButton = screen.getByTestId('open-settings-dialog-button')
|
||||||
fireEvent.click(settingsButton)
|
fireEvent.click(settingsButton)
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.queryByTestId('toggle-local-routing-button')).not.toBeInTheDocument()
|
expect(screen.queryByTestId('toggle-uniswap-x-button')).not.toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not render routing settings when uniswapx is not enabled', async () => {
|
||||||
|
mocked(isUniswapXSupportedChain).mockReturnValue(false)
|
||||||
|
render(<SettingsTab hideRoutingSettings chainId={1} autoSlippage={slippage} />)
|
||||||
|
|
||||||
|
const settingsButton = screen.getByTestId('open-settings-dialog-button')
|
||||||
|
fireEvent.click(settingsButton)
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByTestId('toggle-uniswap-x-button')).not.toBeInTheDocument()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { Scrim } from 'components/AccountDrawer'
|
|||||||
import AnimatedDropdown from 'components/AnimatedDropdown'
|
import AnimatedDropdown from 'components/AnimatedDropdown'
|
||||||
import Column, { AutoColumn } from 'components/Column'
|
import Column, { AutoColumn } from 'components/Column'
|
||||||
import Row from 'components/Row'
|
import Row from 'components/Row'
|
||||||
import { isSupportedChain, L2_CHAIN_IDS } from 'constants/chains'
|
import { isSupportedChain, isUniswapXSupportedChain, L2_CHAIN_IDS } from 'constants/chains'
|
||||||
import useDisableScrolling from 'hooks/useDisableScrolling'
|
import useDisableScrolling from 'hooks/useDisableScrolling'
|
||||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||||
import { Portal } from 'nft/components/common/Portal'
|
import { Portal } from 'nft/components/common/Portal'
|
||||||
@ -101,12 +101,12 @@ export default function SettingsTab({
|
|||||||
autoSlippage,
|
autoSlippage,
|
||||||
chainId,
|
chainId,
|
||||||
trade,
|
trade,
|
||||||
showRoutingSettings = true,
|
hideRoutingSettings = false,
|
||||||
}: {
|
}: {
|
||||||
autoSlippage: Percent
|
autoSlippage: Percent
|
||||||
chainId?: number
|
chainId?: number
|
||||||
trade?: InterfaceTrade
|
trade?: InterfaceTrade
|
||||||
showRoutingSettings?: boolean
|
hideRoutingSettings?: boolean
|
||||||
}) {
|
}) {
|
||||||
const { chainId: connectedChainId } = useWeb3React()
|
const { chainId: connectedChainId } = useWeb3React()
|
||||||
const showDeadlineSettings = Boolean(chainId && !L2_CHAIN_IDS.includes(chainId))
|
const showDeadlineSettings = Boolean(chainId && !L2_CHAIN_IDS.includes(chainId))
|
||||||
@ -124,6 +124,9 @@ export default function SettingsTab({
|
|||||||
useOnClickOutside(node, isOpenDesktop ? closeMenu : undefined)
|
useOnClickOutside(node, isOpenDesktop ? closeMenu : undefined)
|
||||||
useDisableScrolling(isOpen)
|
useDisableScrolling(isOpen)
|
||||||
|
|
||||||
|
const uniswapXEnabled = chainId && isUniswapXSupportedChain(chainId)
|
||||||
|
const showRoutingSettings = Boolean(uniswapXEnabled && !hideRoutingSettings)
|
||||||
|
|
||||||
const isChainSupported = isSupportedChain(chainId)
|
const isChainSupported = isSupportedChain(chainId)
|
||||||
const Settings = useMemo(
|
const Settings = useMemo(
|
||||||
() => (
|
() => (
|
||||||
|
|||||||
@ -5,8 +5,8 @@ export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
|
|||||||
|
|
||||||
// TODO(WEB-1984): Convert the deadline to minutes and remove unecessary conversions from
|
// TODO(WEB-1984): Convert the deadline to minutes and remove unecessary conversions from
|
||||||
// seconds to minutes in the codebase.
|
// seconds to minutes in the codebase.
|
||||||
// 30 minutes, denominated in seconds
|
// 10 minutes, denominated in seconds
|
||||||
export const DEFAULT_DEADLINE_FROM_NOW = 60 * 30
|
export const DEFAULT_DEADLINE_FROM_NOW = 60 * 10
|
||||||
export const L2_DEADLINE_FROM_NOW = 60 * 5
|
export const L2_DEADLINE_FROM_NOW = 60 * 5
|
||||||
|
|
||||||
// transaction popup dismisal amounts
|
// transaction popup dismisal amounts
|
||||||
|
|||||||
@ -34,7 +34,7 @@ beforeEach(() => {
|
|||||||
|
|
||||||
mocked(useIsWindowVisible).mockReturnValue(true)
|
mocked(useIsWindowVisible).mockReturnValue(true)
|
||||||
mocked(useAutoRouterSupported).mockReturnValue(true)
|
mocked(useAutoRouterSupported).mockReturnValue(true)
|
||||||
mocked(useRouterPreference).mockReturnValue([RouterPreference.CLIENT, () => undefined])
|
mocked(useRouterPreference).mockReturnValue([RouterPreference.API, () => undefined])
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('#useBestV3Trade ExactIn', () => {
|
describe('#useBestV3Trade ExactIn', () => {
|
||||||
@ -49,7 +49,7 @@ describe('#useBestV3Trade ExactIn', () => {
|
|||||||
TradeType.EXACT_INPUT,
|
TradeType.EXACT_INPUT,
|
||||||
USDCAmount,
|
USDCAmount,
|
||||||
DAI,
|
DAI,
|
||||||
RouterPreference.CLIENT,
|
RouterPreference.API,
|
||||||
/* account = */ undefined,
|
/* account = */ undefined,
|
||||||
/* inputTax = */ undefined,
|
/* inputTax = */ undefined,
|
||||||
/* outputTax = */ undefined
|
/* outputTax = */ undefined
|
||||||
@ -69,7 +69,7 @@ describe('#useDebouncedTrade ExactOut', () => {
|
|||||||
TradeType.EXACT_OUTPUT,
|
TradeType.EXACT_OUTPUT,
|
||||||
DAIAmount,
|
DAIAmount,
|
||||||
USDC_MAINNET,
|
USDC_MAINNET,
|
||||||
RouterPreference.CLIENT,
|
RouterPreference.API,
|
||||||
/* account = */ undefined,
|
/* account = */ undefined,
|
||||||
/* inputTax = */ undefined,
|
/* inputTax = */ undefined,
|
||||||
/* outputTax = */ undefined
|
/* outputTax = */ undefined
|
||||||
|
|||||||
@ -37,7 +37,7 @@ export function useDebouncedTrade(
|
|||||||
tradeType: TradeType,
|
tradeType: TradeType,
|
||||||
amountSpecified?: CurrencyAmount<Currency>,
|
amountSpecified?: CurrencyAmount<Currency>,
|
||||||
otherCurrency?: Currency,
|
otherCurrency?: Currency,
|
||||||
routerPreferenceOverride?: RouterPreference.API | RouterPreference.CLIENT,
|
routerPreferenceOverride?: RouterPreference.API,
|
||||||
account?: string,
|
account?: string,
|
||||||
inputTax?: Percent,
|
inputTax?: Percent,
|
||||||
outputTax?: Percent
|
outputTax?: Percent
|
||||||
@ -97,8 +97,7 @@ export function useDebouncedTrade(
|
|||||||
const skipBothFetches = !autoRouterSupported || !isWindowVisible || isWrap
|
const skipBothFetches = !autoRouterSupported || !isWindowVisible || isWrap
|
||||||
const skipRoutingFetch = skipBothFetches || isDebouncing
|
const skipRoutingFetch = skipBothFetches || isDebouncing
|
||||||
|
|
||||||
const skipPreviewTradeFetch =
|
const skipPreviewTradeFetch = skipBothFetches || isPreviewTradeDebouncing
|
||||||
skipBothFetches || routerPreference === RouterPreference.CLIENT || isPreviewTradeDebouncing
|
|
||||||
|
|
||||||
const previewTradeResult = usePreviewTrade(
|
const previewTradeResult = usePreviewTrade(
|
||||||
skipPreviewTradeFetch,
|
skipPreviewTradeFetch,
|
||||||
|
|||||||
3710
src/locales/af-ZA.po
Normal file
3710
src/locales/af-ZA.po
Normal file
File diff suppressed because it is too large
Load Diff
3710
src/locales/ar-SA.po
Normal file
3710
src/locales/ar-SA.po
Normal file
File diff suppressed because it is too large
Load Diff
3710
src/locales/ca-ES.po
Normal file
3710
src/locales/ca-ES.po
Normal file
File diff suppressed because it is too large
Load Diff
3710
src/locales/cs-CZ.po
Normal file
3710
src/locales/cs-CZ.po
Normal file
File diff suppressed because it is too large
Load Diff
3710
src/locales/da-DK.po
Normal file
3710
src/locales/da-DK.po
Normal file
File diff suppressed because it is too large
Load Diff
3710
src/locales/de-DE.po
Normal file
3710
src/locales/de-DE.po
Normal file
File diff suppressed because it is too large
Load Diff
3710
src/locales/el-GR.po
Normal file
3710
src/locales/el-GR.po
Normal file
File diff suppressed because it is too large
Load Diff
3710
src/locales/es-ES.po
Normal file
3710
src/locales/es-ES.po
Normal file
File diff suppressed because it is too large
Load Diff
3710
src/locales/fi-FI.po
Normal file
3710
src/locales/fi-FI.po
Normal file
File diff suppressed because it is too large
Load Diff
3710
src/locales/fr-FR.po
Normal file
3710
src/locales/fr-FR.po
Normal file
File diff suppressed because it is too large
Load Diff
3710
src/locales/he-IL.po
Normal file
3710
src/locales/he-IL.po
Normal file
File diff suppressed because it is too large
Load Diff
3711
src/locales/hu-HU.po
Normal file
3711
src/locales/hu-HU.po
Normal file
File diff suppressed because it is too large
Load Diff
3710
src/locales/id-ID.po
Normal file
3710
src/locales/id-ID.po
Normal file
File diff suppressed because it is too large
Load Diff
3710
src/locales/it-IT.po
Normal file
3710
src/locales/it-IT.po
Normal file
File diff suppressed because it is too large
Load Diff
3710
src/locales/ja-JP.po
Normal file
3710
src/locales/ja-JP.po
Normal file
File diff suppressed because it is too large
Load Diff
3710
src/locales/ko-KR.po
Normal file
3710
src/locales/ko-KR.po
Normal file
File diff suppressed because it is too large
Load Diff
3710
src/locales/nl-NL.po
Normal file
3710
src/locales/nl-NL.po
Normal file
File diff suppressed because it is too large
Load Diff
3710
src/locales/no-NO.po
Normal file
3710
src/locales/no-NO.po
Normal file
File diff suppressed because it is too large
Load Diff
3710
src/locales/pl-PL.po
Normal file
3710
src/locales/pl-PL.po
Normal file
File diff suppressed because it is too large
Load Diff
3710
src/locales/pt-BR.po
Normal file
3710
src/locales/pt-BR.po
Normal file
File diff suppressed because it is too large
Load Diff
3710
src/locales/pt-PT.po
Normal file
3710
src/locales/pt-PT.po
Normal file
File diff suppressed because it is too large
Load Diff
3710
src/locales/ro-RO.po
Normal file
3710
src/locales/ro-RO.po
Normal file
File diff suppressed because it is too large
Load Diff
3710
src/locales/ru-RU.po
Normal file
3710
src/locales/ru-RU.po
Normal file
File diff suppressed because it is too large
Load Diff
3710
src/locales/sl-SI.po
Normal file
3710
src/locales/sl-SI.po
Normal file
File diff suppressed because it is too large
Load Diff
3710
src/locales/sr-SP.po
Normal file
3710
src/locales/sr-SP.po
Normal file
File diff suppressed because it is too large
Load Diff
3710
src/locales/sv-SE.po
Normal file
3710
src/locales/sv-SE.po
Normal file
File diff suppressed because it is too large
Load Diff
3710
src/locales/sw-TZ.po
Normal file
3710
src/locales/sw-TZ.po
Normal file
File diff suppressed because it is too large
Load Diff
3710
src/locales/th-TH.po
Normal file
3710
src/locales/th-TH.po
Normal file
File diff suppressed because it is too large
Load Diff
3710
src/locales/tr-TR.po
Normal file
3710
src/locales/tr-TR.po
Normal file
File diff suppressed because it is too large
Load Diff
3710
src/locales/uk-UA.po
Normal file
3710
src/locales/uk-UA.po
Normal file
File diff suppressed because it is too large
Load Diff
3710
src/locales/vi-VN.po
Normal file
3710
src/locales/vi-VN.po
Normal file
File diff suppressed because it is too large
Load Diff
3710
src/locales/zh-CN.po
Normal file
3710
src/locales/zh-CN.po
Normal file
File diff suppressed because it is too large
Load Diff
3710
src/locales/zh-TW.po
Normal file
3710
src/locales/zh-TW.po
Normal file
File diff suppressed because it is too large
Load Diff
@ -5,7 +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 { useFeatureFlagsIsLoaded } from 'featureFlags'
|
import { FeatureFlag, useFeatureFlagsIsLoaded } from 'featureFlags'
|
||||||
import { useUniswapXDefaultEnabled } from 'featureFlags/flags/uniswapXDefault'
|
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'
|
||||||
@ -16,7 +16,7 @@ import { useAppSelector } from 'state/hooks'
|
|||||||
import { AppState } from 'state/reducer'
|
import { AppState } from 'state/reducer'
|
||||||
import { RouterPreference } from 'state/routing/types'
|
import { RouterPreference } from 'state/routing/types'
|
||||||
import { useRouterPreference, useUserOptedOutOfUniswapX } 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'
|
||||||
@ -97,12 +97,8 @@ export default function App() {
|
|||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const { pathname } = location
|
const { pathname } = location
|
||||||
const currentPage = getCurrentPageFromLocation(pathname)
|
const currentPage = getCurrentPageFromLocation(pathname)
|
||||||
const isDarkMode = useIsDarkMode()
|
|
||||||
const [routerPreference] = useRouterPreference()
|
|
||||||
const [scrollY, setScrollY] = useState(0)
|
const [scrollY, setScrollY] = useState(0)
|
||||||
const scrolledState = scrollY > 0
|
const scrolledState = scrollY > 0
|
||||||
const isUniswapXDefaultEnabled = useUniswapXDefaultEnabled()
|
|
||||||
const userOptedOutOfUniswapX = useUserOptedOutOfUniswapX()
|
|
||||||
const routerConfig = useRouterConfig()
|
const routerConfig = useRouterConfig()
|
||||||
|
|
||||||
const originCountry = useAppSelector((state: AppState) => state.user.originCountry)
|
const originCountry = useAppSelector((state: AppState) => state.user.originCountry)
|
||||||
@ -122,53 +118,6 @@ export default function App() {
|
|||||||
}
|
}
|
||||||
}, [searchParams, setShouldDisableNFTRoutes])
|
}, [searchParams, setShouldDisableNFTRoutes])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// User properties *must* be set before sending corresponding event properties,
|
|
||||||
// so that the event contains the correct and up-to-date user properties.
|
|
||||||
user.set(CustomUserProperties.USER_AGENT, navigator.userAgent)
|
|
||||||
user.set(CustomUserProperties.BROWSER, getBrowser())
|
|
||||||
user.set(CustomUserProperties.SCREEN_RESOLUTION_HEIGHT, window.screen.height)
|
|
||||||
user.set(CustomUserProperties.SCREEN_RESOLUTION_WIDTH, window.screen.width)
|
|
||||||
user.set(CustomUserProperties.GIT_COMMIT_HASH, process.env.REACT_APP_GIT_COMMIT_HASH ?? 'unknown')
|
|
||||||
|
|
||||||
// Service Worker analytics
|
|
||||||
const isServiceWorkerInstalled = Boolean(window.navigator.serviceWorker?.controller)
|
|
||||||
const isServiceWorkerHit = Boolean((window as any).__isDocumentCached)
|
|
||||||
const serviceWorkerProperty = isServiceWorkerInstalled ? (isServiceWorkerHit ? 'hit' : 'miss') : 'uninstalled'
|
|
||||||
|
|
||||||
const pageLoadProperties = { service_worker: serviceWorkerProperty }
|
|
||||||
sendInitializationEvent(SharedEventName.APP_LOADED, pageLoadProperties)
|
|
||||||
const sendWebVital =
|
|
||||||
(metric: string) =>
|
|
||||||
({ delta }: Metric) =>
|
|
||||||
sendAnalyticsEvent(SharedEventName.WEB_VITALS, { ...pageLoadProperties, [metric]: delta })
|
|
||||||
getCLS(sendWebVital('cumulative_layout_shift'))
|
|
||||||
getFCP(sendWebVital('first_contentful_paint_ms'))
|
|
||||||
getFID(sendWebVital('first_input_delay_ms'))
|
|
||||||
getLCP(sendWebVital('largest_contentful_paint_ms'))
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
user.set(CustomUserProperties.DARK_MODE, isDarkMode)
|
|
||||||
}, [isDarkMode])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// 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.
|
|
||||||
user.set(CustomUserProperties.ROUTER_PREFERENCE, routerPreference)
|
|
||||||
}, [routerPreference, isUniswapXDefaultEnabled, userOptedOutOfUniswapX])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const scrollListener = () => {
|
const scrollListener = () => {
|
||||||
setScrollY(window.scrollY)
|
setScrollY(window.scrollY)
|
||||||
@ -221,6 +170,7 @@ export default function App() {
|
|||||||
api: process.env.REACT_APP_STATSIG_PROXY_URL,
|
api: process.env.REACT_APP_STATSIG_PROXY_URL,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<UserPropertyUpdater />
|
||||||
{renderUkBannner && <UkBanner />}
|
{renderUkBannner && <UkBanner />}
|
||||||
<HeaderWrapper transparent={isHeaderTransparent} bannerIsVisible={renderUkBannner} scrollY={scrollY}>
|
<HeaderWrapper transparent={isHeaderTransparent} bannerIsVisible={renderUkBannner} scrollY={scrollY}>
|
||||||
<NavBar blur={isHeaderTransparent} />
|
<NavBar blur={isHeaderTransparent} />
|
||||||
@ -255,3 +205,63 @@ export default function App() {
|
|||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(() => {
|
||||||
|
// User properties *must* be set before sending corresponding event properties,
|
||||||
|
// so that the event contains the correct and up-to-date user properties.
|
||||||
|
user.set(CustomUserProperties.USER_AGENT, navigator.userAgent)
|
||||||
|
user.set(CustomUserProperties.BROWSER, getBrowser())
|
||||||
|
user.set(CustomUserProperties.SCREEN_RESOLUTION_HEIGHT, window.screen.height)
|
||||||
|
user.set(CustomUserProperties.SCREEN_RESOLUTION_WIDTH, window.screen.width)
|
||||||
|
user.set(CustomUserProperties.GIT_COMMIT_HASH, process.env.REACT_APP_GIT_COMMIT_HASH ?? 'unknown')
|
||||||
|
|
||||||
|
// Service Worker analytics
|
||||||
|
const isServiceWorkerInstalled = Boolean(window.navigator.serviceWorker?.controller)
|
||||||
|
const isServiceWorkerHit = Boolean((window as any).__isDocumentCached)
|
||||||
|
const serviceWorkerProperty = isServiceWorkerInstalled ? (isServiceWorkerHit ? 'hit' : 'miss') : 'uninstalled'
|
||||||
|
|
||||||
|
const pageLoadProperties = { service_worker: serviceWorkerProperty }
|
||||||
|
sendInitializationEvent(SharedEventName.APP_LOADED, pageLoadProperties)
|
||||||
|
const sendWebVital =
|
||||||
|
(metric: string) =>
|
||||||
|
({ delta }: Metric) =>
|
||||||
|
sendAnalyticsEvent(SharedEventName.WEB_VITALS, { ...pageLoadProperties, [metric]: delta })
|
||||||
|
getCLS(sendWebVital('cumulative_layout_shift'))
|
||||||
|
getFCP(sendWebVital('first_contentful_paint_ms'))
|
||||||
|
getFID(sendWebVital('first_input_delay_ms'))
|
||||||
|
getLCP(sendWebVital('largest_contentful_paint_ms'))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
user.set(CustomUserProperties.DARK_MODE, isDarkMode)
|
||||||
|
}, [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.
|
||||||
|
user.set(CustomUserProperties.ROUTER_PREFERENCE, routerPreference)
|
||||||
|
}, [routerPreference, isUniswapXDefaultEnabled, userOptedOutOfUniswapX, isUniswapXDefaultLoading, rehydrated])
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|||||||
@ -739,11 +739,7 @@ export default function MigrateV2Pair() {
|
|||||||
<ThemedText.DeprecatedMediumHeader>
|
<ThemedText.DeprecatedMediumHeader>
|
||||||
<Trans>Migrate V2 liquidity</Trans>
|
<Trans>Migrate V2 liquidity</Trans>
|
||||||
</ThemedText.DeprecatedMediumHeader>
|
</ThemedText.DeprecatedMediumHeader>
|
||||||
<SettingsTab
|
<SettingsTab autoSlippage={DEFAULT_MIGRATE_SLIPPAGE_TOLERANCE} chainId={chainId} hideRoutingSettings />
|
||||||
autoSlippage={DEFAULT_MIGRATE_SLIPPAGE_TOLERANCE}
|
|
||||||
chainId={chainId}
|
|
||||||
showRoutingSettings={false}
|
|
||||||
/>
|
|
||||||
</AutoRow>
|
</AutoRow>
|
||||||
|
|
||||||
{!account ? (
|
{!account ? (
|
||||||
|
|||||||
@ -13,7 +13,7 @@ const defaultState = {
|
|||||||
user: {},
|
user: {},
|
||||||
_persist: {
|
_persist: {
|
||||||
rehydrated: true,
|
rehydrated: true,
|
||||||
version: 0,
|
version: 2,
|
||||||
},
|
},
|
||||||
application: {
|
application: {
|
||||||
chainId: null,
|
chainId: null,
|
||||||
|
|||||||
@ -2,19 +2,23 @@ import { createMigrate, MigrationManifest, PersistedState, PersistMigrate } from
|
|||||||
import { MigrationConfig } from 'redux-persist/es/createMigrate'
|
import { MigrationConfig } from 'redux-persist/es/createMigrate'
|
||||||
|
|
||||||
import { migration0 } from './migrations/0'
|
import { migration0 } from './migrations/0'
|
||||||
|
import { migration1 } from './migrations/1'
|
||||||
|
import { migration2 } from './migrations/2'
|
||||||
import { legacyLocalStorageMigration } from './migrations/legacy'
|
import { legacyLocalStorageMigration } from './migrations/legacy'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* These run once per state re-hydration when a version mismatch is detected.
|
* These run once per state re-hydration when a version mismatch is detected.
|
||||||
* Keep them as lightweight as possible.
|
* Keep them as lightweight as possible.
|
||||||
*
|
*
|
||||||
* Migration functions should not assume that any value exists in localStorage previously,
|
* Migration functions should not assume that any value exists in the persisted data previously,
|
||||||
* because a user may be visiting the site for the first time or have cleared their localStorage.
|
* because a user may be visiting the site for the first time or have cleared their data.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// The target version number is the key
|
// The target version number is the key
|
||||||
export const migrations: MigrationManifest = {
|
export const migrations: MigrationManifest = {
|
||||||
0: migration0,
|
0: migration0,
|
||||||
|
1: migration1,
|
||||||
|
2: migration2,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
|||||||
85
src/state/migrations/1.test.ts
Normal file
85
src/state/migrations/1.test.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { createMigrate } from 'redux-persist'
|
||||||
|
import { RouterPreference } from 'state/routing/types'
|
||||||
|
import { SlippageTolerance } from 'state/user/types'
|
||||||
|
|
||||||
|
import { migration1, PersistAppStateV1 } from './1'
|
||||||
|
|
||||||
|
const previousState: PersistAppStateV1 = {
|
||||||
|
user: {
|
||||||
|
userLocale: null,
|
||||||
|
userRouterPreference: RouterPreference.API,
|
||||||
|
userHideClosedPositions: false,
|
||||||
|
userSlippageTolerance: SlippageTolerance.Auto,
|
||||||
|
userSlippageToleranceHasBeenMigratedToAuto: true,
|
||||||
|
userDeadline: 1800,
|
||||||
|
tokens: {},
|
||||||
|
pairs: {},
|
||||||
|
timestamp: Date.now(),
|
||||||
|
hideBaseWalletBanner: false,
|
||||||
|
},
|
||||||
|
_persist: {
|
||||||
|
version: 0,
|
||||||
|
rehydrated: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('migration to v1', () => {
|
||||||
|
it('should migrate the default deadline', async () => {
|
||||||
|
const migrator = createMigrate(
|
||||||
|
{
|
||||||
|
1: migration1,
|
||||||
|
},
|
||||||
|
{ debug: false }
|
||||||
|
)
|
||||||
|
const result: any = await migrator(previousState, 1)
|
||||||
|
expect(result?.user?.userDeadline).toEqual(600)
|
||||||
|
expect(result?._persist.version).toEqual(1)
|
||||||
|
|
||||||
|
expect(result?.user?.userLocale).toEqual(null)
|
||||||
|
expect(result?.user?.userRouterPreference).toEqual(RouterPreference.API)
|
||||||
|
expect(result?.user?.userHideClosedPositions).toEqual(false)
|
||||||
|
expect(result?.user?.userSlippageTolerance).toEqual(SlippageTolerance.Auto)
|
||||||
|
expect(result?.user?.userSlippageToleranceHasBeenMigratedToAuto).toEqual(true)
|
||||||
|
expect(result?.user?.tokens).toEqual({})
|
||||||
|
expect(result?.user?.pairs).toEqual({})
|
||||||
|
expect(result?.user?.timestamp).toEqual(previousState.user?.timestamp)
|
||||||
|
expect(result?.user?.hideBaseWalletBanner).toEqual(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not migrate a non-default value', async () => {
|
||||||
|
const migrator = createMigrate(
|
||||||
|
{
|
||||||
|
1: migration1,
|
||||||
|
},
|
||||||
|
{ debug: false }
|
||||||
|
)
|
||||||
|
const result: any = await migrator(
|
||||||
|
{
|
||||||
|
...previousState,
|
||||||
|
user: {
|
||||||
|
...previousState.user,
|
||||||
|
userDeadline: 300,
|
||||||
|
},
|
||||||
|
} as PersistAppStateV1,
|
||||||
|
1
|
||||||
|
)
|
||||||
|
expect(result?.user?.userDeadline).toEqual(300)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not migrate if user state is not set', async () => {
|
||||||
|
const migrator = createMigrate(
|
||||||
|
{
|
||||||
|
1: migration1,
|
||||||
|
},
|
||||||
|
{ debug: false }
|
||||||
|
)
|
||||||
|
const result: any = await migrator(
|
||||||
|
{
|
||||||
|
...previousState,
|
||||||
|
user: undefined,
|
||||||
|
} as PersistAppStateV1,
|
||||||
|
1
|
||||||
|
)
|
||||||
|
expect(result?.user?.userDeadline).toBeUndefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
28
src/state/migrations/1.ts
Normal file
28
src/state/migrations/1.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { DEFAULT_DEADLINE_FROM_NOW } from 'constants/misc'
|
||||||
|
import { PersistState } from 'redux-persist'
|
||||||
|
import { UserState } from 'state/user/reducer'
|
||||||
|
|
||||||
|
export type PersistAppStateV1 = {
|
||||||
|
_persist: PersistState
|
||||||
|
} & { user?: UserState }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migration to change the default user deadline from 30 minutes to 10 minutes.
|
||||||
|
* We only migrate if the saved deadline is the old default.
|
||||||
|
*/
|
||||||
|
export const migration1 = (state: PersistAppStateV1 | undefined) => {
|
||||||
|
if (state?.user && state.user?.userDeadline === 1800) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
user: {
|
||||||
|
...state.user,
|
||||||
|
userDeadline: DEFAULT_DEADLINE_FROM_NOW,
|
||||||
|
},
|
||||||
|
_persist: {
|
||||||
|
...state._persist,
|
||||||
|
version: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
}
|
||||||
62
src/state/migrations/2.test.ts
Normal file
62
src/state/migrations/2.test.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { createMigrate } from 'redux-persist'
|
||||||
|
import { RouterPreference } from 'state/routing/types'
|
||||||
|
import { SlippageTolerance } from 'state/user/types'
|
||||||
|
|
||||||
|
import { migration1 } from './1'
|
||||||
|
import { migration2, PersistAppStateV2 } from './2'
|
||||||
|
|
||||||
|
const previousState: PersistAppStateV2 = {
|
||||||
|
user: {
|
||||||
|
userLocale: null,
|
||||||
|
// @ts-ignore this is intentionally a string and not the `RouterPreference` enum because `client` is a deprecated option
|
||||||
|
userRouterPreference: 'client',
|
||||||
|
userHideClosedPositions: false,
|
||||||
|
userSlippageTolerance: SlippageTolerance.Auto,
|
||||||
|
userSlippageToleranceHasBeenMigratedToAuto: true,
|
||||||
|
userDeadline: 1800,
|
||||||
|
tokens: {},
|
||||||
|
pairs: {},
|
||||||
|
timestamp: Date.now(),
|
||||||
|
hideBaseWalletBanner: false,
|
||||||
|
},
|
||||||
|
_persist: {
|
||||||
|
version: 1,
|
||||||
|
rehydrated: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('migration to v2', () => {
|
||||||
|
it('should migrate users who currently have `client` router preference', async () => {
|
||||||
|
const migrator = createMigrate(
|
||||||
|
{
|
||||||
|
1: migration1,
|
||||||
|
2: migration2,
|
||||||
|
},
|
||||||
|
{ debug: false }
|
||||||
|
)
|
||||||
|
const result: any = await migrator(previousState, 2)
|
||||||
|
expect(result?.user?.userRouterPreference).toEqual(RouterPreference.API)
|
||||||
|
expect(result?._persist.version).toEqual(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not migrate non-client router preference', async () => {
|
||||||
|
const migrator = createMigrate(
|
||||||
|
{
|
||||||
|
1: migration1,
|
||||||
|
2: migration2,
|
||||||
|
},
|
||||||
|
{ debug: false }
|
||||||
|
)
|
||||||
|
const result: any = await migrator(
|
||||||
|
{
|
||||||
|
...previousState,
|
||||||
|
user: {
|
||||||
|
...previousState.user,
|
||||||
|
userRouterPreference: RouterPreference.X,
|
||||||
|
},
|
||||||
|
} as PersistAppStateV2,
|
||||||
|
2
|
||||||
|
)
|
||||||
|
expect(result?.user?.userRouterPreference).toEqual(RouterPreference.X)
|
||||||
|
})
|
||||||
|
})
|
||||||
29
src/state/migrations/2.ts
Normal file
29
src/state/migrations/2.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { PersistState } from 'redux-persist'
|
||||||
|
import { RouterPreference } from 'state/routing/types'
|
||||||
|
import { UserState } from 'state/user/reducer'
|
||||||
|
|
||||||
|
export type PersistAppStateV2 = {
|
||||||
|
_persist: PersistState
|
||||||
|
} & { user?: UserState }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migration to move users who have local routing as their router preference to API
|
||||||
|
* since forced local routing is now deprecated
|
||||||
|
*/
|
||||||
|
export const migration2 = (state: PersistAppStateV2 | undefined) => {
|
||||||
|
// @ts-ignore this is intentionally a string and not the `RouterPreference` enum because `client` is a deprecated option
|
||||||
|
if (state?.user && state.user?.userRouterPreference === 'client') {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
user: {
|
||||||
|
...state.user,
|
||||||
|
userRouterPreference: RouterPreference.API,
|
||||||
|
},
|
||||||
|
_persist: {
|
||||||
|
...state._persist,
|
||||||
|
version: 2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
}
|
||||||
@ -44,7 +44,7 @@ export type AppState = ReturnType<typeof appReducer>
|
|||||||
|
|
||||||
const persistConfig: PersistConfig<AppState> = {
|
const persistConfig: PersistConfig<AppState> = {
|
||||||
key: 'interface',
|
key: 'interface',
|
||||||
version: 0, // see migrations.ts for more details about this version
|
version: 2, // see migrations.ts for more details about this version
|
||||||
storage: localForage.createInstance({
|
storage: localForage.createInstance({
|
||||||
name: 'redux',
|
name: 'redux',
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -19,7 +19,7 @@ import {
|
|||||||
URAQuoteResponse,
|
URAQuoteResponse,
|
||||||
URAQuoteType,
|
URAQuoteType,
|
||||||
} from './types'
|
} from './types'
|
||||||
import { isExactInput, shouldUseAPIRouter, transformRoutesToTrade } from './utils'
|
import { isExactInput, transformRoutesToTrade } from './utils'
|
||||||
|
|
||||||
const UNISWAP_API_URL = process.env.REACT_APP_UNISWAP_API_URL
|
const UNISWAP_API_URL = process.env.REACT_APP_UNISWAP_API_URL
|
||||||
if (UNISWAP_API_URL === undefined) {
|
if (UNISWAP_API_URL === undefined) {
|
||||||
@ -121,73 +121,69 @@ export const routingApi = createApi({
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
async queryFn(args, _api, _extraOptions, fetch) {
|
async queryFn(args, _api, _extraOptions, fetch) {
|
||||||
let fellBack = false
|
|
||||||
logSwapQuoteRequest(args.tokenInChainId, args.routerPreference, false)
|
logSwapQuoteRequest(args.tokenInChainId, args.routerPreference, false)
|
||||||
const quoteStartMark = performance.mark(`quote-fetch-start-${Date.now()}`)
|
const quoteStartMark = performance.mark(`quote-fetch-start-${Date.now()}`)
|
||||||
if (shouldUseAPIRouter(args)) {
|
|
||||||
fellBack = true
|
|
||||||
try {
|
|
||||||
const { tokenInAddress, tokenInChainId, tokenOutAddress, tokenOutChainId, amount, tradeType } = args
|
|
||||||
const type = isExactInput(tradeType) ? 'EXACT_INPUT' : 'EXACT_OUTPUT'
|
|
||||||
|
|
||||||
const requestBody = {
|
|
||||||
tokenInChainId,
|
|
||||||
tokenIn: tokenInAddress,
|
|
||||||
tokenOutChainId,
|
|
||||||
tokenOut: tokenOutAddress,
|
|
||||||
amount,
|
|
||||||
type,
|
|
||||||
intent: args.routerPreference === INTERNAL_ROUTER_PREFERENCE_PRICE ? 'pricing' : undefined,
|
|
||||||
configs: getRoutingAPIConfig(args),
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch({
|
|
||||||
method: 'POST',
|
|
||||||
url: '/quote',
|
|
||||||
body: JSON.stringify(requestBody),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (response.error) {
|
|
||||||
try {
|
|
||||||
// cast as any here because we do a runtime check on it being an object before indexing into .errorCode
|
|
||||||
const errorData = response.error.data as { errorCode?: string; detail?: string }
|
|
||||||
// NO_ROUTE should be treated as a valid response to prevent retries.
|
|
||||||
if (
|
|
||||||
typeof errorData === 'object' &&
|
|
||||||
(errorData?.errorCode === 'NO_ROUTE' || errorData?.detail === 'No quotes available')
|
|
||||||
) {
|
|
||||||
sendAnalyticsEvent('No quote received from routing API', {
|
|
||||||
requestBody,
|
|
||||||
response,
|
|
||||||
routerPreference: args.routerPreference,
|
|
||||||
})
|
|
||||||
return {
|
|
||||||
data: { state: QuoteState.NOT_FOUND, latencyMs: getQuoteLatencyMeasure(quoteStartMark).duration },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
throw response.error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const uraQuoteResponse = response.data as URAQuoteResponse
|
|
||||||
const tradeResult = await transformRoutesToTrade(args, uraQuoteResponse, QuoteMethod.ROUTING_API)
|
|
||||||
return { data: { ...tradeResult, latencyMs: getQuoteLatencyMeasure(quoteStartMark).duration } }
|
|
||||||
} catch (error: any) {
|
|
||||||
console.warn(
|
|
||||||
`GetQuote failed on Unified Routing API, falling back to client: ${
|
|
||||||
error?.message ?? error?.detail ?? error
|
|
||||||
}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const method = fellBack ? QuoteMethod.CLIENT_SIDE_FALLBACK : QuoteMethod.CLIENT_SIDE
|
const { tokenInAddress, tokenInChainId, tokenOutAddress, tokenOutChainId, amount, tradeType } = args
|
||||||
|
const type = isExactInput(tradeType) ? 'EXACT_INPUT' : 'EXACT_OUTPUT'
|
||||||
|
|
||||||
|
const requestBody = {
|
||||||
|
tokenInChainId,
|
||||||
|
tokenIn: tokenInAddress,
|
||||||
|
tokenOutChainId,
|
||||||
|
tokenOut: tokenOutAddress,
|
||||||
|
amount,
|
||||||
|
type,
|
||||||
|
intent: args.routerPreference === INTERNAL_ROUTER_PREFERENCE_PRICE ? 'pricing' : undefined,
|
||||||
|
configs: getRoutingAPIConfig(args),
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/quote',
|
||||||
|
body: JSON.stringify(requestBody),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
try {
|
||||||
|
// cast as any here because we do a runtime check on it being an object before indexing into .errorCode
|
||||||
|
const errorData = response.error.data as { errorCode?: string; detail?: string }
|
||||||
|
// NO_ROUTE should be treated as a valid response to prevent retries.
|
||||||
|
if (
|
||||||
|
typeof errorData === 'object' &&
|
||||||
|
(errorData?.errorCode === 'NO_ROUTE' || errorData?.detail === 'No quotes available')
|
||||||
|
) {
|
||||||
|
sendAnalyticsEvent('No quote received from routing API', {
|
||||||
|
requestBody,
|
||||||
|
response,
|
||||||
|
routerPreference: args.routerPreference,
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
data: { state: QuoteState.NOT_FOUND, latencyMs: getQuoteLatencyMeasure(quoteStartMark).duration },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
throw response.error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const uraQuoteResponse = response.data as URAQuoteResponse
|
||||||
|
const tradeResult = await transformRoutesToTrade(args, uraQuoteResponse, QuoteMethod.ROUTING_API)
|
||||||
|
return { data: { ...tradeResult, latencyMs: getQuoteLatencyMeasure(quoteStartMark).duration } }
|
||||||
|
} catch (error: any) {
|
||||||
|
console.warn(
|
||||||
|
`GetQuote failed on Unified Routing API, falling back to client: ${
|
||||||
|
error?.message ?? error?.detail ?? error
|
||||||
|
}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
const { getRouter, getClientSideQuote } = await import('lib/hooks/routing/clientSideSmartOrderRouter')
|
const { getRouter, getClientSideQuote } = await import('lib/hooks/routing/clientSideSmartOrderRouter')
|
||||||
const router = getRouter(args.tokenInChainId)
|
const router = getRouter(args.tokenInChainId)
|
||||||
const quoteResult = await getClientSideQuote(args, router, CLIENT_PARAMS)
|
const quoteResult = await getClientSideQuote(args, router, CLIENT_PARAMS)
|
||||||
if (quoteResult.state === QuoteState.SUCCESS) {
|
if (quoteResult.state === QuoteState.SUCCESS) {
|
||||||
const trade = await transformRoutesToTrade(args, quoteResult.data, method)
|
const trade = await transformRoutesToTrade(args, quoteResult.data, QuoteMethod.CLIENT_SIDE_FALLBACK)
|
||||||
return {
|
return {
|
||||||
data: { ...trade, latencyMs: getQuoteLatencyMeasure(quoteStartMark).duration },
|
data: { ...trade, latencyMs: getQuoteLatencyMeasure(quoteStartMark).duration },
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,7 +16,6 @@ export enum TradeState {
|
|||||||
export enum QuoteMethod {
|
export enum QuoteMethod {
|
||||||
ROUTING_API = 'ROUTING_API',
|
ROUTING_API = 'ROUTING_API',
|
||||||
QUICK_ROUTE = 'QUICK_ROUTE',
|
QUICK_ROUTE = 'QUICK_ROUTE',
|
||||||
CLIENT_SIDE = 'CLIENT_SIDE',
|
|
||||||
CLIENT_SIDE_FALLBACK = 'CLIENT_SIDE_FALLBACK', // If client-side was used after the routing-api call failed.
|
CLIENT_SIDE_FALLBACK = 'CLIENT_SIDE_FALLBACK', // If client-side was used after the routing-api call failed.
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,7 +26,6 @@ export const INTERNAL_ROUTER_PREFERENCE_PRICE = 'price' as const
|
|||||||
export enum RouterPreference {
|
export enum RouterPreference {
|
||||||
X = 'uniswapx',
|
X = 'uniswapx',
|
||||||
API = 'api',
|
API = 'api',
|
||||||
CLIENT = 'client',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetQuoteArgs {
|
export interface GetQuoteArgs {
|
||||||
|
|||||||
@ -339,7 +339,3 @@ export function isSubmittableTrade(trade?: InterfaceTrade): trade is Submittable
|
|||||||
export function isUniswapXTrade(trade?: InterfaceTrade): trade is DutchOrderTrade {
|
export function isUniswapXTrade(trade?: InterfaceTrade): trade is DutchOrderTrade {
|
||||||
return trade?.fillType === TradeFillType.UniswapX
|
return trade?.fillType === TradeFillType.UniswapX
|
||||||
}
|
}
|
||||||
|
|
||||||
export function shouldUseAPIRouter(args: GetQuoteArgs): boolean {
|
|
||||||
return args.routerPreference !== RouterPreference.CLIENT
|
|
||||||
}
|
|
||||||
|
|||||||
@ -48,7 +48,7 @@ export const TEST_TRADE_EXACT_INPUT = new ClassicTrade({
|
|||||||
tradeType: TradeType.EXACT_INPUT,
|
tradeType: TradeType.EXACT_INPUT,
|
||||||
gasUseEstimateUSD: 1.0,
|
gasUseEstimateUSD: 1.0,
|
||||||
approveInfo: { needsApprove: false },
|
approveInfo: { needsApprove: false },
|
||||||
quoteMethod: QuoteMethod.CLIENT_SIDE,
|
quoteMethod: QuoteMethod.CLIENT_SIDE_FALLBACK,
|
||||||
inputTax: ZERO_PERCENT,
|
inputTax: ZERO_PERCENT,
|
||||||
outputTax: ZERO_PERCENT,
|
outputTax: ZERO_PERCENT,
|
||||||
})
|
})
|
||||||
@ -80,7 +80,7 @@ export const TEST_TRADE_EXACT_OUTPUT = new ClassicTrade({
|
|||||||
],
|
],
|
||||||
v2Routes: [],
|
v2Routes: [],
|
||||||
tradeType: TradeType.EXACT_OUTPUT,
|
tradeType: TradeType.EXACT_OUTPUT,
|
||||||
quoteMethod: QuoteMethod.CLIENT_SIDE,
|
quoteMethod: QuoteMethod.CLIENT_SIDE_FALLBACK,
|
||||||
approveInfo: { needsApprove: false },
|
approveInfo: { needsApprove: false },
|
||||||
inputTax: ZERO_PERCENT,
|
inputTax: ZERO_PERCENT,
|
||||||
outputTax: ZERO_PERCENT,
|
outputTax: ZERO_PERCENT,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user