ci(release): publish latest release

parent 0f3c158e
* @uniswap/web-admins
IPFS hash of the deployment: We are back with some new updates! Here’s the latest:
- CIDv0: `QmemEFGpbyCk7BDVXisJ6TzpqXXoEVpACm1kZxyz5FFGNo`
- CIDv1: `bafybeihua3m55nwz3am7z7j7yeoi35vrnmohvrkyi5gd4obsax72anky3q`
The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org). Token Warnings: See more information about the tokens you’re attempting to swap, enriched with data from Blockaid.
You can also access the Uniswap Interface from an IPFS gateway.
**BEWARE**: The Uniswap interface uses [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) to remember your settings, such as which tokens you have imported.
**You should always use an IPFS gateway that enforces origin separation**, or our hosted deployment of the latest release at [app.uniswap.org](https://app.uniswap.org).
Your Uniswap settings are never remembered across different URLs.
IPFS gateways:
- https://bafybeihua3m55nwz3am7z7j7yeoi35vrnmohvrkyi5gd4obsax72anky3q.ipfs.dweb.link/
- https://bafybeihua3m55nwz3am7z7j7yeoi35vrnmohvrkyi5gd4obsax72anky3q.ipfs.cf-ipfs.com/
- [ipfs://QmemEFGpbyCk7BDVXisJ6TzpqXXoEVpACm1kZxyz5FFGNo/](ipfs://QmemEFGpbyCk7BDVXisJ6TzpqXXoEVpACm1kZxyz5FFGNo/)
### 5.61.5 (2024-12-10)
### Bug Fixes
* **web:** wrap positions in multichain context (#14468) 4a007d9
Other changes:
- Increased manual slippage tolerance up to 50%
- Support for toggling between fiat and token input for fiat onramp
- Better dapp signing support
- Various bug fixes and performance improvements
\ No newline at end of file
web/5.61.5 extension/1.11.0
\ No newline at end of file \ No newline at end of file
...@@ -126,4 +126,16 @@ ...@@ -126,4 +126,16 @@
<changefreq>weekly</changefreq> <changefreq>weekly</changefreq>
<priority>0.5</priority> <priority>0.5</priority>
</url> </url>
<url>
<loc>https://app.uniswap.org/positions/create</loc>
<lastmod>2024-09-17T19:57:27.976Z</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/positions</loc>
<lastmod>2024-09-17T19:57:27.976Z</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
</urlset> </urlset>
<!DOCTYPE html> <!DOCTYPE html>
<html translate="no" style="overflow-x: hidden;"> <html translate="no">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
import AnimatedDropdown from 'components/AnimatedDropdown'
import { render, screen, waitFor } from 'test-utils/render'
describe('AnimatedDropdown', () => {
it('does not render children when closed', () => {
render(<AnimatedDropdown open={false}>Body</AnimatedDropdown>)
expect(screen.getByText('Body')).not.toBeVisible()
})
it('renders children when open', () => {
render(<AnimatedDropdown open={true}>Body</AnimatedDropdown>)
expect(screen.getByText('Body')).toBeVisible()
})
it('animates when open changes', async () => {
const { rerender } = render(<AnimatedDropdown open={false}>Body</AnimatedDropdown>)
const body = screen.getByText('Body')
expect(body).not.toBeVisible()
rerender(<AnimatedDropdown open={true}>Body</AnimatedDropdown>)
expect(body).not.toBeVisible()
// wait for React Spring animation to finish
await waitFor(() => {
expect(body).toBeVisible()
})
})
})
import { useRef } from 'react'
import { UseSpringProps, animated, easings, useSpring } from 'react-spring'
import { TRANSITION_DURATIONS } from 'theme/styles'
import useResizeObserver from 'use-resize-observer'
type AnimatedDropdownProps = React.PropsWithChildren<{ open: boolean; springProps?: UseSpringProps }>
/**
* @param open conditional to show content or hide
* @param springProps additional props to include in spring animation
* @returns Wrapper to smoothly hide and expand content
*/
export default function AnimatedDropdown({ open, springProps, children }: AnimatedDropdownProps) {
const wasOpen = useRef(open)
const { ref, height } = useResizeObserver()
const props = useSpring({
// On initial render, `height` will be undefined as ref has not been set yet.
// If the dropdown should be open, we fallback to `auto` to avoid flickering.
// Otherwise, we just animate between actual height (when open) and 0 (when closed).
height: open ? height ?? 'auto' : 0,
config: {
easing: open ? easings.easeInCubic : easings.easeOutCubic,
// Avoid animating if `open` is unchanged, so that nested AnimatedDropdowns don't stack and delay animations.
duration: open === wasOpen.current ? 0 : TRANSITION_DURATIONS.medium,
},
onStart: () => {
wasOpen.current = open
},
...springProps,
})
return (
<animated.div
style={{ ...props, overflow: 'hidden', width: '100%', minWidth: 'min-content', willChange: 'height' }}
>
<div ref={ref}>{children}</div>
</animated.div>
)
}
import { InterfaceModalName } from '@uniswap/analytics-events' import { InterfaceModalName } from '@uniswap/analytics-events'
import { ConfirmModalState } from 'components/ConfirmSwapModal'
import Modal from 'components/Modal' import Modal from 'components/Modal'
import { AutoColumn } from 'components/deprecated/Column' import { AutoColumn } from 'components/deprecated/Column'
import styled from 'lib/styled-components' import styled from 'lib/styled-components'
import { PropsWithChildren } from 'react' import { PropsWithChildren, useRef } from 'react'
import { HeightAnimator } from 'ui/src' import { animated, easings, useSpring } from 'react-spring'
import { TRANSITION_DURATIONS } from 'theme/styles'
import Trace from 'uniswap/src/features/telemetry/Trace' import Trace from 'uniswap/src/features/telemetry/Trace'
import useResizeObserver from 'use-resize-observer'
const AnimatedContainer = styled(animated.div)`
width: 100%;
height: auto;
min-width: min-content;
will-change: height;
overflow: hidden;
border-radius: 20px;
background-color: ${({ theme }) => theme.surface1};
@media screen and (max-width: ${({ theme }) => theme.breakpoint.sm}px) {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
`
const Content = styled(AutoColumn)` const Content = styled(AutoColumn)`
background-color: ${({ theme }) => theme.surface1}; background-color: ${({ theme }) => theme.surface1};
width: 100%; width: 100%;
...@@ -15,27 +31,36 @@ const Content = styled(AutoColumn)` ...@@ -15,27 +31,36 @@ const Content = styled(AutoColumn)`
export function SwapModal({ export function SwapModal({
children, children,
confirmModalState,
onDismiss, onDismiss,
}: PropsWithChildren<{ }: PropsWithChildren<{
confirmModalState: ConfirmModalState
onDismiss: () => void onDismiss: () => void
}>) { }>) {
const prevConfirmModalState = useRef(confirmModalState)
const { ref, height } = useResizeObserver()
const springProps = useSpring({
height,
onRest: () => (prevConfirmModalState.current = confirmModalState),
config: {
mass: 1.2,
tension: 300,
friction: 30,
clamp: true,
velocity: 0.01,
duration: TRANSITION_DURATIONS.medium,
easing: easings.easeInOutCubic,
},
})
return ( return (
<Trace modal={InterfaceModalName.CONFIRM_SWAP}> <Trace modal={InterfaceModalName.CONFIRM_SWAP}>
<Modal isOpen $scrollOverlay onDismiss={onDismiss} maxHeight="90vh" slideIn> <Modal isOpen $scrollOverlay onDismiss={onDismiss} maxHeight="90vh" slideIn>
<HeightAnimator <AnimatedContainer style={prevConfirmModalState.current !== confirmModalState ? springProps : undefined}>
open={true} <div ref={ref}>
width="100%" <Content>{children}</Content>
minWidth="min-content" </div>
overflow="hidden" </AnimatedContainer>
borderRadius="$rounded20"
backgroundColor="$surface1"
$md={{
borderBottomLeftRadius: 0,
borderBottomRightRadius: 0,
}}
>
<Content>{children}</Content>
</HeightAnimator>
</Modal> </Modal>
</Trace> </Trace>
) )
......
...@@ -194,7 +194,7 @@ export function ConfirmSwapModal({ ...@@ -194,7 +194,7 @@ export function ConfirmSwapModal({
return ( return (
// Wrapping in a new theme provider resets any color extraction overriding on the current page. Swap modal should use default/non-overridden theme. // Wrapping in a new theme provider resets any color extraction overriding on the current page. Swap modal should use default/non-overridden theme.
<ThemeProvider> <ThemeProvider>
<SwapModal onDismiss={onModalDismiss}> <SwapModal confirmModalState={confirmModalState} onDismiss={onModalDismiss}>
{/* Head section displays title, help button, close icon */} {/* Head section displays title, help button, close icon */}
<Container $height="24px" $padding="6px 12px 4px 12px"> <Container $height="24px" $padding="6px 12px 4px 12px">
<SwapHead <SwapHead
......
...@@ -18,7 +18,7 @@ describe('Expand', () => { ...@@ -18,7 +18,7 @@ describe('Expand', () => {
Body Body
</Expand>, </Expand>,
) )
expect(screen.queryByText('Body')).not.toBeNull() expect(screen.queryByText('Body')).toBeVisible()
}) })
it('calls `onToggle` when button is pressed', () => { it('calls `onToggle` when button is pressed', () => {
......
import AnimatedDropdown from 'components/AnimatedDropdown'
import Column from 'components/deprecated/Column' import Column from 'components/deprecated/Column'
import Row, { RowBetween } from 'components/deprecated/Row' import Row, { RowBetween } from 'components/deprecated/Row'
import styled from 'lib/styled-components' import styled from 'lib/styled-components'
import { PropsWithChildren, ReactElement } from 'react' import { PropsWithChildren, ReactElement } from 'react'
import { ChevronDown } from 'react-feather' import { ChevronDown } from 'react-feather'
import { HeightAnimator } from 'ui/src'
import { iconSizes } from 'ui/src/theme' import { iconSizes } from 'ui/src/theme'
const ButtonContainer = styled(Row)` const ButtonContainer = styled(Row)`
...@@ -53,9 +53,9 @@ export default function Expand({ ...@@ -53,9 +53,9 @@ export default function Expand({
<ExpandIcon $isOpen={isOpen} size={iconSizes[iconSize]} /> <ExpandIcon $isOpen={isOpen} size={iconSizes[iconSize]} />
</ButtonContainer> </ButtonContainer>
</RowBetween> </RowBetween>
<HeightAnimator open={isOpen}> <AnimatedDropdown open={isOpen}>
<Content gap="md">{children}</Content> <Content gap="md">{children}</Content>
</HeightAnimator> </AnimatedDropdown>
</Wrapper> </Wrapper>
) )
} }
...@@ -14,6 +14,7 @@ import { ...@@ -14,6 +14,7 @@ import {
IncreasePositionTxAndGasInfo, IncreasePositionTxAndGasInfo,
LiquidityTransactionType, LiquidityTransactionType,
} from 'uniswap/src/features/transactions/liquidity/types' } from 'uniswap/src/features/transactions/liquidity/types'
import { getTradeSettingsDeadline } from 'uniswap/src/features/transactions/swap/form/utils'
import { useSwapSettingsContext } from 'uniswap/src/features/transactions/swap/settings/contexts/SwapSettingsContext' import { useSwapSettingsContext } from 'uniswap/src/features/transactions/swap/settings/contexts/SwapSettingsContext'
import { validatePermit, validateTransactionRequest } from 'uniswap/src/features/transactions/swap/utils/trade' import { validatePermit, validateTransactionRequest } from 'uniswap/src/features/transactions/swap/utils/trade'
import { ONE_SECOND_MS } from 'utilities/src/time/time' import { ONE_SECOND_MS } from 'utilities/src/time/time'
...@@ -104,6 +105,8 @@ export function IncreaseLiquidityTxContextProvider({ children }: PropsWithChildr ...@@ -104,6 +105,8 @@ export function IncreaseLiquidityTxContextProvider({ children }: PropsWithChildr
return undefined return undefined
} }
const deadline = getTradeSettingsDeadline(customDeadline)
return { return {
simulateTransaction: !approvalsNeeded, simulateTransaction: !approvalsNeeded,
protocol: apiProtocolItems, protocol: apiProtocolItems,
...@@ -130,9 +133,10 @@ export function IncreaseLiquidityTxContextProvider({ children }: PropsWithChildr ...@@ -130,9 +133,10 @@ export function IncreaseLiquidityTxContextProvider({ children }: PropsWithChildr
hooks: positionInfo.v4hook, hooks: positionInfo.v4hook,
}, },
}, },
deadline,
slippageTolerance: customSlippageTolerance, slippageTolerance: customSlippageTolerance,
} }
}, [account, positionInfo, pool, currencyAmounts, approvalsNeeded, customSlippageTolerance]) }, [account, positionInfo, pool, currencyAmounts, approvalsNeeded, customDeadline, customSlippageTolerance])
const { const {
data: increaseCalldata, data: increaseCalldata,
...@@ -141,8 +145,7 @@ export function IncreaseLiquidityTxContextProvider({ children }: PropsWithChildr ...@@ -141,8 +145,7 @@ export function IncreaseLiquidityTxContextProvider({ children }: PropsWithChildr
refetch: calldataRefetch, refetch: calldataRefetch,
} = useIncreaseLpPositionCalldataQuery({ } = useIncreaseLpPositionCalldataQuery({
params: increaseCalldataQueryParams, params: increaseCalldataQueryParams,
deadlineInMinutes: customDeadline, staleTime: 5 * ONE_SECOND_MS,
refetchInterval: 5 * ONE_SECOND_MS,
}) })
const { increase, gasFee: actualGasFee } = increaseCalldata || {} const { increase, gasFee: actualGasFee } = increaseCalldata || {}
......
...@@ -30,9 +30,7 @@ export function useDerivedIncreaseLiquidityInfo(state: IncreaseLiquidityState): ...@@ -30,9 +30,7 @@ export function useDerivedIncreaseLiquidityInfo(state: IncreaseLiquidityState):
token0, token0,
token1, token1,
exactField, exactField,
exactAmounts: { exactAmount,
[exactField]: exactAmount,
},
deposit0Disabled: false, deposit0Disabled: false,
deposit1Disabled: false, deposit1Disabled: false,
} }
...@@ -55,9 +53,7 @@ export function useDerivedIncreaseLiquidityInfo(state: IncreaseLiquidityState): ...@@ -55,9 +53,7 @@ export function useDerivedIncreaseLiquidityInfo(state: IncreaseLiquidityState):
token0, token0,
token1, token1,
exactField, exactField,
exactAmounts: { exactAmount,
[exactField]: exactAmount,
},
deposit0Disabled, deposit0Disabled,
deposit1Disabled, deposit1Disabled,
} }
...@@ -73,9 +69,7 @@ export function useDerivedIncreaseLiquidityInfo(state: IncreaseLiquidityState): ...@@ -73,9 +69,7 @@ export function useDerivedIncreaseLiquidityInfo(state: IncreaseLiquidityState):
token0: currency0, token0: currency0,
token1: currency1, token1: currency1,
exactField, exactField,
exactAmounts: { exactAmount,
[exactField]: exactAmount,
},
deposit0Disabled, deposit0Disabled,
deposit1Disabled, deposit1Disabled,
} }
...@@ -84,7 +78,6 @@ export function useDerivedIncreaseLiquidityInfo(state: IncreaseLiquidityState): ...@@ -84,7 +78,6 @@ export function useDerivedIncreaseLiquidityInfo(state: IncreaseLiquidityState):
return { return {
protocolVersion: ProtocolVersion.UNSPECIFIED, protocolVersion: ProtocolVersion.UNSPECIFIED,
exactField, exactField,
exactAmounts: {},
} }
}, [account.address, exactAmount, exactField, positionInfo, currency0, currency1, token0, token1]) }, [account.address, exactAmount, exactField, positionInfo, currency0, currency1, token0, token1])
......
...@@ -162,7 +162,7 @@ export function HookModal({ ...@@ -162,7 +162,7 @@ export function HookModal({
<Text variant="body2" color="$neutral2" textAlign="center" my="$padding8"> <Text variant="body2" color="$neutral2" textAlign="center" my="$padding8">
{hasDangerous ? t('position.hook.warningInfo') : t('position.addingHook.disclaimer')} {hasDangerous ? t('position.hook.warningInfo') : t('position.addingHook.disclaimer')}
</Text> </Text>
<LearnMoreLink centered url={uniswapUrls.helpArticleUrls.addingV4Hooks} textVariant="buttonLabel3" /> <LearnMoreLink centered url={uniswapUrls.helpArticleUrls.v4HooksInfo} textVariant="buttonLabel3" />
</Flex> </Flex>
<Flex borderRadius="$rounded16" backgroundColor="$surface2" py="$gap12" px="$gap16"> <Flex borderRadius="$rounded16" backgroundColor="$surface2" py="$gap12" px="$gap16">
......
...@@ -11,9 +11,7 @@ import { UniverseChainId } from 'uniswap/src/features/chains/types' ...@@ -11,9 +11,7 @@ import { UniverseChainId } from 'uniswap/src/features/chains/types'
export interface DepositState { export interface DepositState {
exactField: PositionField exactField: PositionField
exactAmounts: { exactAmount?: string
[field in PositionField]?: string
}
} }
export type DepositContextType = { export type DepositContextType = {
......
...@@ -15,6 +15,7 @@ import { ...@@ -15,6 +15,7 @@ import {
ProtocolItems, ProtocolItems,
} from 'uniswap/src/data/tradingApi/__generated__' } from 'uniswap/src/data/tradingApi/__generated__'
import { useTransactionGasFee, useUSDCurrencyAmountOfGasFee } from 'uniswap/src/features/gas/hooks' import { useTransactionGasFee, useUSDCurrencyAmountOfGasFee } from 'uniswap/src/features/gas/hooks'
import { getTradeSettingsDeadline } from 'uniswap/src/features/transactions/swap/form/utils'
import { useSwapSettingsContext } from 'uniswap/src/features/transactions/swap/settings/contexts/SwapSettingsContext' import { useSwapSettingsContext } from 'uniswap/src/features/transactions/swap/settings/contexts/SwapSettingsContext'
import { ONE_SECOND_MS } from 'utilities/src/time/time' import { ONE_SECOND_MS } from 'utilities/src/time/time'
...@@ -67,6 +68,8 @@ export function useRemoveLiquidityTxAndGasInfo({ account }: { account?: string } ...@@ -67,6 +68,8 @@ export function useRemoveLiquidityTxAndGasInfo({ account }: { account?: string }
return undefined return undefined
} }
const deadline = getTradeSettingsDeadline(customDeadline)
return { return {
simulateTransaction: !approvalsNeeded, simulateTransaction: !approvalsNeeded,
protocol: apiProtocolItems, protocol: apiProtocolItems,
...@@ -104,6 +107,7 @@ export function useRemoveLiquidityTxAndGasInfo({ account }: { account?: string } ...@@ -104,6 +107,7 @@ export function useRemoveLiquidityTxAndGasInfo({ account }: { account?: string }
hooks: positionInfo.v4hook, hooks: positionInfo.v4hook,
}, },
}, },
deadline,
slippageTolerance: customSlippageTolerance, slippageTolerance: customSlippageTolerance,
} }
}, [ }, [
...@@ -115,6 +119,7 @@ export function useRemoveLiquidityTxAndGasInfo({ account }: { account?: string } ...@@ -115,6 +119,7 @@ export function useRemoveLiquidityTxAndGasInfo({ account }: { account?: string }
approvalsNeeded, approvalsNeeded,
feeValue0, feeValue0,
feeValue1, feeValue1,
customDeadline,
customSlippageTolerance, customSlippageTolerance,
]) ])
...@@ -125,8 +130,7 @@ export function useRemoveLiquidityTxAndGasInfo({ account }: { account?: string } ...@@ -125,8 +130,7 @@ export function useRemoveLiquidityTxAndGasInfo({ account }: { account?: string }
refetch: calldataRefetch, refetch: calldataRefetch,
} = useDecreaseLpPositionCalldataQuery({ } = useDecreaseLpPositionCalldataQuery({
params: decreaseCalldataQueryParams, params: decreaseCalldataQueryParams,
deadlineInMinutes: customDeadline, staleTime: 5 * ONE_SECOND_MS,
refetchInterval: 5 * ONE_SECOND_MS,
}) })
const { value: estimatedGasFee } = useTransactionGasFee(decreaseCalldata?.decrease, !!decreaseCalldata?.gasFee) const { value: estimatedGasFee } = useTransactionGasFee(decreaseCalldata?.decrease, !!decreaseCalldata?.gasFee)
......
...@@ -31,7 +31,7 @@ describe('MaxSlippageSettings', () => { ...@@ -31,7 +31,7 @@ describe('MaxSlippageSettings', () => {
it('is expanded by default when custom slippage is set', () => { it('is expanded by default when custom slippage is set', () => {
store.dispatch(updateUserSlippageTolerance({ userSlippageTolerance: 10 })) store.dispatch(updateUserSlippageTolerance({ userSlippageTolerance: 10 }))
renderSlippageSettings() renderSlippageSettings()
expect(getSlippageInput()).not.toBeNull() expect(getSlippageInput()).toBeVisible()
}) })
it('does not render auto slippage as a value, but a placeholder', () => { it('does not render auto slippage as a value, but a placeholder', () => {
renderSlippageSettings() renderSlippageSettings()
......
...@@ -23,7 +23,7 @@ describe('TransactionDeadlineSettings', () => { ...@@ -23,7 +23,7 @@ describe('TransactionDeadlineSettings', () => {
it('is expanded by default when custom deadline is set', () => { it('is expanded by default when custom deadline is set', () => {
store.dispatch(updateUserDeadline({ userDeadline: DEFAULT_DEADLINE_FROM_NOW * 2 })) store.dispatch(updateUserDeadline({ userDeadline: DEFAULT_DEADLINE_FROM_NOW * 2 }))
renderTransactionDeadlineSettings() renderTransactionDeadlineSettings()
expect(getDeadlineInput()).not.toBeNull() expect(getDeadlineInput()).toBeVisible()
}) })
it('does not render default deadline as a value, but a placeholder', () => { it('does not render default deadline as a value, but a placeholder', () => {
renderTransactionDeadlineSettings() renderTransactionDeadlineSettings()
......
import { Percent } from '@uniswap/sdk-core' import { Percent } from '@uniswap/sdk-core'
import { Scrim } from 'components/AccountDrawer' import { Scrim } from 'components/AccountDrawer'
import AnimatedDropdown from 'components/AnimatedDropdown'
import Column, { AutoColumn } from 'components/deprecated/Column'
import Row from 'components/deprecated/Row'
import MaxSlippageSettings from 'components/Settings/MaxSlippageSettings' import MaxSlippageSettings from 'components/Settings/MaxSlippageSettings'
import MenuButton from 'components/Settings/MenuButton' import MenuButton from 'components/Settings/MenuButton'
import MultipleRoutingOptions from 'components/Settings/MultipleRoutingOptions' import MultipleRoutingOptions from 'components/Settings/MultipleRoutingOptions'
import RouterPreferenceSettings from 'components/Settings/RouterPreferenceSettings' import RouterPreferenceSettings from 'components/Settings/RouterPreferenceSettings'
import TransactionDeadlineSettings from 'components/Settings/TransactionDeadlineSettings' import TransactionDeadlineSettings from 'components/Settings/TransactionDeadlineSettings'
import Column, { AutoColumn } from 'components/deprecated/Column'
import Row from 'components/deprecated/Row'
import { useIsMobile } from 'hooks/screenSize/useIsMobile' import { useIsMobile } from 'hooks/screenSize/useIsMobile'
import useDisableScrolling from 'hooks/useDisableScrolling' import useDisableScrolling from 'hooks/useDisableScrolling'
import { useIsUniswapXSupportedChain } from 'hooks/useIsUniswapXSupportedChain' import { useIsUniswapXSupportedChain } from 'hooks/useIsUniswapXSupportedChain'
...@@ -21,7 +22,6 @@ import { InterfaceTrade } from 'state/routing/types' ...@@ -21,7 +22,6 @@ import { InterfaceTrade } from 'state/routing/types'
import { isUniswapXTrade } from 'state/routing/utils' import { isUniswapXTrade } from 'state/routing/utils'
import { Divider, ThemedText } from 'theme/components' import { Divider, ThemedText } from 'theme/components'
import { Z_INDEX } from 'theme/zIndex' import { Z_INDEX } from 'theme/zIndex'
import { HeightAnimator } from 'ui/src'
import { useIsSupportedChainId } from 'uniswap/src/features/chains/hooks/useSupportedChainId' import { useIsSupportedChainId } from 'uniswap/src/features/chains/hooks/useSupportedChainId'
import { isL2ChainId } from 'uniswap/src/features/chains/utils' import { isL2ChainId } from 'uniswap/src/features/chains/utils'
import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { FeatureFlags } from 'uniswap/src/features/gating/flags'
...@@ -149,13 +149,13 @@ export default function SettingsTab({ ...@@ -149,13 +149,13 @@ export default function SettingsTab({
<RouterPreferenceSettings /> <RouterPreferenceSettings />
</AutoColumn> </AutoColumn>
)} )}
<HeightAnimator open={!isUniswapXTrade(trade)}> <AnimatedDropdown open={!isUniswapXTrade(trade)}>
<ExpandColumn $padTop={showRoutingSettings}> <ExpandColumn $padTop={showRoutingSettings}>
{showRoutingSettings && <Divider />} {showRoutingSettings && <Divider />}
<MaxSlippageSettings autoSlippage={autoSlippage} /> <MaxSlippageSettings autoSlippage={autoSlippage} />
{showDeadlineSettings && <TransactionDeadlineSettings />} {showDeadlineSettings && <TransactionDeadlineSettings />}
</ExpandColumn> </ExpandColumn>
</HeightAnimator> </AnimatedDropdown>
{multipleRoutingOptionsEnabled && ( {multipleRoutingOptionsEnabled && (
<> <>
{!isUniswapXTrade(trade) && <StyledDivider />} {!isUniswapXTrade(trade) && <StyledDivider />}
......
...@@ -2,6 +2,7 @@ import clsx, { ClassValue } from 'clsx' ...@@ -2,6 +2,7 @@ import clsx, { ClassValue } from 'clsx'
import { Atoms, atoms } from 'nft/css/atoms' import { Atoms, atoms } from 'nft/css/atoms'
import { sprinkles } from 'nft/css/sprinkles.css' import { sprinkles } from 'nft/css/sprinkles.css'
import * as React from 'react' import * as React from 'react'
import { animated } from 'react-spring'
type HTMLProperties<T = HTMLElement> = Omit< type HTMLProperties<T = HTMLElement> = Omit<
React.AllHTMLAttributes<T>, React.AllHTMLAttributes<T>,
...@@ -39,6 +40,11 @@ export const Box = React.forwardRef<HTMLElement, Props>(({ as = 'div', className ...@@ -39,6 +40,11 @@ export const Box = React.forwardRef<HTMLElement, Props>(({ as = 'div', className
}) })
}) })
// We get this error around the codebase: https://github.com/microsoft/TypeScript/issues/34933
// so you see ts-ignore almost everywhere this component is used
// since we are going to deprecate vanilla-extract, this will be `any` for now
export const AnimatedBox: any = animated(Box) as any
export type BoxProps = Parameters<typeof Box>[0] export type BoxProps = Parameters<typeof Box>[0]
Box.displayName = 'Box' Box.displayName = 'Box'
...@@ -2,6 +2,7 @@ import { InterfaceElementName, SwapEventName } from '@uniswap/analytics-events' ...@@ -2,6 +2,7 @@ import { InterfaceElementName, SwapEventName } from '@uniswap/analytics-events'
import { Percent } from '@uniswap/sdk-core' import { Percent } from '@uniswap/sdk-core'
import { ReactComponent as ExpandoIconClosed } from 'assets/svg/expando-icon-closed.svg' import { ReactComponent as ExpandoIconClosed } from 'assets/svg/expando-icon-closed.svg'
import { ReactComponent as ExpandoIconOpened } from 'assets/svg/expando-icon-opened.svg' import { ReactComponent as ExpandoIconOpened } from 'assets/svg/expando-icon-opened.svg'
import AnimatedDropdown from 'components/AnimatedDropdown'
import { ButtonError, SmallButtonPrimary } from 'components/Button/buttons' import { ButtonError, SmallButtonPrimary } from 'components/Button/buttons'
import Column from 'components/deprecated/Column' import Column from 'components/deprecated/Column'
import Row, { AutoRow, RowBetween, RowFixed } from 'components/deprecated/Row' import Row, { AutoRow, RowBetween, RowFixed } from 'components/deprecated/Row'
...@@ -20,7 +21,7 @@ import { InterfaceTrade, LimitOrderTrade, RouterPreference } from 'state/routing ...@@ -20,7 +21,7 @@ import { InterfaceTrade, LimitOrderTrade, RouterPreference } from 'state/routing
import { isClassicTrade, isLimitTrade } from 'state/routing/utils' import { isClassicTrade, isLimitTrade } from 'state/routing/utils'
import { useRouterPreference, useUserSlippageTolerance } from 'state/user/hooks' import { useRouterPreference, useUserSlippageTolerance } from 'state/user/hooks'
import { ExternalLink, Separator, ThemedText } from 'theme/components' import { ExternalLink, Separator, ThemedText } from 'theme/components'
import { HeightAnimator, SpinningLoader } from 'ui/src' import { SpinningLoader } from 'ui/src'
import Trace from 'uniswap/src/features/telemetry/Trace' import Trace from 'uniswap/src/features/telemetry/Trace'
import { Trans, t } from 'uniswap/src/i18n' import { Trans, t } from 'uniswap/src/i18n'
import { useTrace } from 'utilities/src/telemetry/trace/TraceContext' import { useTrace } from 'utilities/src/telemetry/trace/TraceContext'
...@@ -306,14 +307,23 @@ function ExpandableLineItems(props: { ...@@ -306,14 +307,23 @@ function ExpandableLineItems(props: {
const lineItemProps = { trade, allowedSlippage, syncing: false, open, priceImpact } const lineItemProps = { trade, allowedSlippage, syncing: false, open, priceImpact }
return ( return (
<HeightAnimator open={open} mt={open ? 0 : -8}> <AnimatedDropdown
open={open}
springProps={{
marginTop: open ? 0 : -8,
config: {
duration: ms('200ms'),
easing: easings.easeOutSine,
},
}}
>
<Column gap="sm"> <Column gap="sm">
<AnimatedLineItem {...lineItemProps} type={SwapLineItemType.PRICE_IMPACT} delay={ms('50ms')} /> <AnimatedLineItem {...lineItemProps} type={SwapLineItemType.PRICE_IMPACT} delay={ms('50ms')} />
<AnimatedLineItem {...lineItemProps} type={SwapLineItemType.MAX_SLIPPAGE} delay={ms('100ms')} /> <AnimatedLineItem {...lineItemProps} type={SwapLineItemType.MAX_SLIPPAGE} delay={ms('100ms')} />
<AnimatedLineItem {...lineItemProps} type={SwapLineItemType.MINIMUM_OUTPUT} delay={ms('120ms')} /> <AnimatedLineItem {...lineItemProps} type={SwapLineItemType.MINIMUM_OUTPUT} delay={ms('120ms')} />
<AnimatedLineItem {...lineItemProps} type={SwapLineItemType.MAXIMUM_INPUT} delay={ms('120ms')} /> <AnimatedLineItem {...lineItemProps} type={SwapLineItemType.MAXIMUM_INPUT} delay={ms('120ms')} />
</Column> </Column>
</HeightAnimator> </AnimatedDropdown>
) )
} }
......
import { InterfaceElementName, SwapEventName } from '@uniswap/analytics-events' import { InterfaceElementName, SwapEventName } from '@uniswap/analytics-events'
import { Percent } from '@uniswap/sdk-core' import { Percent } from '@uniswap/sdk-core'
import AnimatedDropdown from 'components/AnimatedDropdown'
import { LoadingOpacityContainer } from 'components/Loader/styled' import { LoadingOpacityContainer } from 'components/Loader/styled'
import Column from 'components/deprecated/Column' import Column from 'components/deprecated/Column'
import { RowBetween, RowFixed } from 'components/deprecated/Row' import { RowBetween, RowFixed } from 'components/deprecated/Row'
...@@ -13,7 +14,6 @@ import { ChevronDown } from 'react-feather' ...@@ -13,7 +14,6 @@ import { ChevronDown } from 'react-feather'
import { InterfaceTrade } from 'state/routing/types' import { InterfaceTrade } from 'state/routing/types'
import { isSubmittableTrade } from 'state/routing/utils' import { isSubmittableTrade } from 'state/routing/utils'
import { ThemedText } from 'theme/components' import { ThemedText } from 'theme/components'
import { HeightAnimator } from 'ui/src'
import Trace from 'uniswap/src/features/telemetry/Trace' import Trace from 'uniswap/src/features/telemetry/Trace'
import { Trans } from 'uniswap/src/i18n' import { Trans } from 'uniswap/src/i18n'
import { useTrace } from 'utilities/src/telemetry/trace/TraceContext' import { useTrace } from 'utilities/src/telemetry/trace/TraceContext'
...@@ -106,7 +106,7 @@ function AdvancedSwapDetails(props: SwapDetailsProps & { open: boolean }) { ...@@ -106,7 +106,7 @@ function AdvancedSwapDetails(props: SwapDetailsProps & { open: boolean }) {
const lineItemProps = { trade, allowedSlippage, format, syncing, priceImpact } const lineItemProps = { trade, allowedSlippage, format, syncing, priceImpact }
return ( return (
<HeightAnimator open={open}> <AnimatedDropdown open={open}>
<SwapDetailsWrapper gap="sm" data-testid="advanced-swap-details"> <SwapDetailsWrapper gap="sm" data-testid="advanced-swap-details">
<SwapLineItem {...lineItemProps} type={SwapLineItemType.PRICE_IMPACT} /> <SwapLineItem {...lineItemProps} type={SwapLineItemType.PRICE_IMPACT} />
<SwapLineItem {...lineItemProps} type={SwapLineItemType.MAX_SLIPPAGE} /> <SwapLineItem {...lineItemProps} type={SwapLineItemType.MAX_SLIPPAGE} />
...@@ -117,6 +117,6 @@ function AdvancedSwapDetails(props: SwapDetailsProps & { open: boolean }) { ...@@ -117,6 +117,6 @@ function AdvancedSwapDetails(props: SwapDetailsProps & { open: boolean }) {
<SwapLineItem {...lineItemProps} type={SwapLineItemType.NETWORK_COST} /> <SwapLineItem {...lineItemProps} type={SwapLineItemType.NETWORK_COST} />
<SwapLineItem {...lineItemProps} type={SwapLineItemType.ROUTING_INFO} /> <SwapLineItem {...lineItemProps} type={SwapLineItemType.ROUTING_INFO} />
</SwapDetailsWrapper> </SwapDetailsWrapper>
</HeightAnimator> </AnimatedDropdown>
) )
} }
...@@ -432,12 +432,9 @@ exports[`SwapDetails.tsx matches base snapshot, test trade exact input 1`] = ` ...@@ -432,12 +432,9 @@ exports[`SwapDetails.tsx matches base snapshot, test trade exact input 1`] = `
</div> </div>
</div> </div>
<div <div
class="css-view-175oi2r" style="height: 0px; margin-top: -8px; overflow: hidden; width: 100%; min-width: min-content; will-change: height;"
style="display: flex; align-items: stretch; flex-direction: column; flex-basis: auto; box-sizing: border-box; position: relative; min-height: 0px; min-width: 0px; flex-shrink: 0; opacity: 0; height: 0px; overflow-x: hidden; overflow-y: hidden; width: 100%; margin-top: -8px;"
> >
<div <div>
class="_display-flex _alignItems-stretch _flexDirection-column _flexBasis-auto _boxSizing-border-box _minHeight-0px _minWidth-0px _flexShrink-0 _position-absolute _width-10037"
>
<div <div
class="c0" class="c0"
> >
...@@ -1036,12 +1033,9 @@ exports[`SwapDetails.tsx renders a preview trade while disabling submission 1`] ...@@ -1036,12 +1033,9 @@ exports[`SwapDetails.tsx renders a preview trade while disabling submission 1`]
</div> </div>
</div> </div>
<div <div
class="css-view-175oi2r" style="height: 0px; margin-top: -8px; overflow: hidden; width: 100%; min-width: min-content; will-change: height;"
style="display: flex; align-items: stretch; flex-direction: column; flex-basis: auto; box-sizing: border-box; position: relative; min-height: 0px; min-width: 0px; flex-shrink: 0; opacity: 0; height: 0px; overflow-x: hidden; overflow-y: hidden; width: 100%; margin-top: -8px;"
> >
<div <div>
class="_display-flex _alignItems-stretch _flexDirection-column _flexBasis-auto _boxSizing-border-box _minHeight-0px _minWidth-0px _flexShrink-0 _position-absolute _width-10037"
>
<div <div
class="c0" class="c0"
> >
......
...@@ -341,12 +341,9 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = ` ...@@ -341,12 +341,9 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
</div> </div>
</div> </div>
<div <div
class="css-view-175oi2r" style="height: 0px; overflow: hidden; width: 100%; min-width: min-content; will-change: height;"
style="display: flex; align-items: stretch; flex-direction: column; flex-basis: auto; box-sizing: border-box; position: relative; min-height: 0px; min-width: 0px; flex-shrink: 0; opacity: 0; height: 0px; overflow-x: hidden; overflow-y: hidden; width: 100%;"
> >
<div <div>
class="_display-flex _alignItems-stretch _flexDirection-column _flexBasis-auto _boxSizing-border-box _minHeight-0px _minWidth-0px _flexShrink-0 _position-absolute _width-10037"
>
<div <div
class="c17 c18" class="c17 c18"
data-testid="advanced-swap-details" data-testid="advanced-swap-details"
......
...@@ -21,11 +21,11 @@ describe('Routing', () => { ...@@ -21,11 +21,11 @@ describe('Routing', () => {
}) })
it('contains all coins for celo', () => { it('contains all coins for celo', () => {
const symbols = COMMON_BASES[UniverseChainId.Celo].map((coin) => coin.currency.symbol) const symbols = COMMON_BASES[UniverseChainId.Celo].map((coin) => coin.currency.symbol)
expect(symbols).toEqual(['CELO', 'USDC']) expect(symbols).toEqual(['CELO', 'cEUR', 'cUSD', 'ETH', 'USDC', 'WBTC'])
}) })
it('contains all coins for bsc', () => { it('contains all coins for bsc', () => {
const symbols = COMMON_BASES[UniverseChainId.Bnb].map((coin) => coin.currency.symbol) const symbols = COMMON_BASES[UniverseChainId.Bnb].map((coin) => coin.currency.symbol)
expect(symbols).toEqual(['BNB', 'DAI', 'USDC', 'USDT', 'ETH', 'BUSD']) expect(symbols).toEqual(['BNB', 'DAI', 'USDC', 'USDT', 'ETH', 'BTCB', 'BUSD'])
}) })
}) })
}) })
...@@ -19,7 +19,6 @@ export function useCurrency(address?: string, chainId?: UniverseChainId, skip?: ...@@ -19,7 +19,6 @@ export function useCurrency(address?: string, chainId?: UniverseChainId, skip?:
} }
/** /**
* @deprecated useCurrencyInfo from packages/uniswap instead
* Returns a CurrencyInfo from the tokenAddress+chainId pair. * Returns a CurrencyInfo from the tokenAddress+chainId pair.
*/ */
export function useCurrencyInfo(currency?: Currency, chainId?: UniverseChainId, skip?: boolean): Maybe<CurrencyInfo> export function useCurrencyInfo(currency?: Currency, chainId?: UniverseChainId, skip?: boolean): Maybe<CurrencyInfo>
......
...@@ -243,25 +243,7 @@ export function usePoolActiveLiquidity({ ...@@ -243,25 +243,7 @@ export function usePoolActiveLiquidity({
} }
} }
let sdkPrice const sdkPrice = tickToPrice(token0, token1, activeTick)
try {
sdkPrice = tickToPrice(token0, token1, activeTick)
} catch (e) {
logger.debug('usePoolTickData', 'usePoolActiveLiquidity', 'Error getting price', {
error: e,
token0: token0.address,
token1: token1.address,
chainId: token0.chainId,
})
return {
isLoading,
error,
activeTick,
data: undefined,
}
}
const activeTickProcessed: TickProcessed = { const activeTickProcessed: TickProcessed = {
liquidityActive: JSBI.BigInt(pool?.liquidity ?? 0), liquidityActive: JSBI.BigInt(pool?.liquidity ?? 0),
tick: activeTick, tick: activeTick,
......
import { BigNumber } from '@ethersproject/bignumber'
// eslint-disable-next-line no-restricted-imports
import { ProtocolVersion } from '@uniswap/client-pools/dist/pools/v1/types_pb'
import { useTokenContract } from 'hooks/useContract'
import { TokenId, useNFTPositionManagerContract } from 'hooks/usePositionTokenURI'
import JSBI from 'jsbi'
import { useSingleCallResult } from 'lib/hooks/multicall'
import { UniverseChainId } from 'uniswap/src/features/chains/types'
import { Address } from 'viem'
export function usePositionOwner(
tokenId: TokenId | undefined,
chainId?: UniverseChainId,
version?: ProtocolVersion,
): string | undefined {
const contract = useNFTPositionManagerContract(version ?? ProtocolVersion.V3, chainId)
const input = tokenId instanceof BigNumber ? tokenId.toHexString() : tokenId?.toString(16)
return useSingleCallResult(tokenId ? contract : null, 'ownerOf', [input]).result?.[0]
}
export function usePositionOwnerV2(
account: Address | undefined,
address: string | null,
chainId?: UniverseChainId,
): boolean {
const contract = useTokenContract(address ?? undefined, false, chainId)
const resultBalance = useSingleCallResult(contract, 'balanceOf', [account ?? undefined]).result?.[0].toString()
const isOwner = resultBalance ? JSBI.GT(JSBI.BigInt(resultBalance), JSBI.BigInt(0)) : false
return isOwner
}
...@@ -9,7 +9,7 @@ import { Erc721 } from 'uniswap/src/abis/types/Erc721' ...@@ -9,7 +9,7 @@ import { Erc721 } from 'uniswap/src/abis/types/Erc721'
import { NonfungiblePositionManager } from 'uniswap/src/abis/types/v3/NonfungiblePositionManager' import { NonfungiblePositionManager } from 'uniswap/src/abis/types/v3/NonfungiblePositionManager'
import { UniverseChainId } from 'uniswap/src/features/chains/types' import { UniverseChainId } from 'uniswap/src/features/chains/types'
export type TokenId = number | JSBI | BigNumber type TokenId = number | JSBI | BigNumber
const STARTS_WITH = 'data:application/json;base64,' const STARTS_WITH = 'data:application/json;base64,'
...@@ -32,7 +32,7 @@ type UsePositionTokenURIResult = ...@@ -32,7 +32,7 @@ type UsePositionTokenURIResult =
loading: true loading: true
} }
export function useNFTPositionManagerContract( function useNFTPositionManagerContract(
version: ProtocolVersion, version: ProtocolVersion,
chainId?: UniverseChainId, chainId?: UniverseChainId,
): NonfungiblePositionManager | Erc721 | null { ): NonfungiblePositionManager | Erc721 | null {
......
...@@ -3,7 +3,7 @@ import { parseEther } from '@ethersproject/units' ...@@ -3,7 +3,7 @@ import { parseEther } from '@ethersproject/units'
import { InterfaceElementName, NFTEventName } from '@uniswap/analytics-events' import { InterfaceElementName, NFTEventName } from '@uniswap/analytics-events'
import clsx from 'clsx' import clsx from 'clsx'
import { OpacityHoverState } from 'components/Common/styles' import { OpacityHoverState } from 'components/Common/styles'
import { Box } from 'components/deprecated/Box' import { AnimatedBox, Box } from 'components/deprecated/Box'
import { ASSET_PAGE_SIZE, AssetFetcherParams, useNftAssets } from 'graphql/data/nft/Asset' import { ASSET_PAGE_SIZE, AssetFetcherParams, useNftAssets } from 'graphql/data/nft/Asset'
import { useIsMobile } from 'hooks/screenSize/useIsMobile' import { useIsMobile } from 'hooks/screenSize/useIsMobile'
import { useScreenSize } from 'hooks/screenSize/useScreenSize' import { useScreenSize } from 'hooks/screenSize/useScreenSize'
...@@ -489,7 +489,7 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie ...@@ -489,7 +489,7 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
return ( return (
<> <>
<Box <AnimatedBox
backgroundColor="surface1" backgroundColor="surface1"
position="sticky" position="sticky"
top="72" top="72"
...@@ -594,7 +594,7 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie ...@@ -594,7 +594,7 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
)} )}
</Row> </Row>
</InfiniteScrollWrapper> </InfiniteScrollWrapper>
</Box> </AnimatedBox>
<InfiniteScrollWrapper> <InfiniteScrollWrapper>
{loading ? ( {loading ? (
<CollectionNftsLoading height={renderedHeight} /> <CollectionNftsLoading height={renderedHeight} />
......
import { ScrollBarStyles } from 'components/Common/styles' import { ScrollBarStyles } from 'components/Common/styles'
import { LoadingBubble } from 'components/Tokens/loading' import { LoadingBubble } from 'components/Tokens/loading'
import { Box } from 'components/deprecated/Box' import { AnimatedBox, Box } from 'components/deprecated/Box'
import { useIsMobile } from 'hooks/screenSize/useIsMobile' import { useIsMobile } from 'hooks/screenSize/useIsMobile'
import styled from 'lib/styled-components' import styled from 'lib/styled-components'
import { Column, Row } from 'nft/components/Flex' import { Column, Row } from 'nft/components/Flex'
...@@ -13,10 +13,12 @@ import { themeVars } from 'nft/css/sprinkles.css' ...@@ -13,10 +13,12 @@ import { themeVars } from 'nft/css/sprinkles.css'
import { useFiltersExpanded, useWalletCollections } from 'nft/hooks' import { useFiltersExpanded, useWalletCollections } from 'nft/hooks'
import { WalletCollection } from 'nft/types' import { WalletCollection } from 'nft/types'
import { CSSProperties, Dispatch, FormEvent, SetStateAction, useCallback, useEffect, useMemo, useState } from 'react' import { CSSProperties, Dispatch, FormEvent, SetStateAction, useCallback, useEffect, useMemo, useState } from 'react'
import { easings, useSpring } from 'react-spring'
import AutoSizer from 'react-virtualized-auto-sizer' import AutoSizer from 'react-virtualized-auto-sizer'
import { FixedSizeList, ListOnItemsRenderedProps } from 'react-window' import { FixedSizeList, ListOnItemsRenderedProps } from 'react-window'
import InfiniteLoader from 'react-window-infinite-loader' import InfiniteLoader from 'react-window-infinite-loader'
import { ThemedText } from 'theme/components' import { ThemedText } from 'theme/components'
import { TRANSITION_DURATIONS } from 'theme/styles'
import { LabeledCheckbox } from 'ui/src' import { LabeledCheckbox } from 'ui/src'
import noop from 'utilities/src/react/noop' import noop from 'utilities/src/react/noop'
...@@ -79,13 +81,22 @@ export const FilterSidebar = ({ ...@@ -79,13 +81,22 @@ export const FilterSidebar = ({
const [isFiltersExpanded, setFiltersExpanded] = useFiltersExpanded() const [isFiltersExpanded, setFiltersExpanded] = useFiltersExpanded()
const isMobile = useIsMobile() const isMobile = useIsMobile()
const { sidebarX } = useSpring({
sidebarX: isFiltersExpanded ? 0 : -360,
config: {
duration: TRANSITION_DURATIONS.medium,
easing: easings.easeOutSine,
},
})
const hideSearch = useMemo( const hideSearch = useMemo(
() => (walletCollections && walletCollections?.length >= WALLET_COLLECTIONS_PAGINATION_LIMIT) || isFetchingNextPage, () => (walletCollections && walletCollections?.length >= WALLET_COLLECTIONS_PAGINATION_LIMIT) || isFetchingNextPage,
[walletCollections, isFetchingNextPage], [walletCollections, isFetchingNextPage],
) )
return ( return (
<Box // @ts-ignore
<AnimatedBox
position={{ sm: 'fixed', md: 'sticky' }} position={{ sm: 'fixed', md: 'sticky' }}
top={{ sm: '0', md: '72' }} top={{ sm: '0', md: '72' }}
left={{ sm: '0', md: 'unset' }} left={{ sm: '0', md: 'unset' }}
...@@ -93,7 +104,8 @@ export const FilterSidebar = ({ ...@@ -93,7 +104,8 @@ export const FilterSidebar = ({
height={{ sm: 'full', md: 'auto' }} height={{ sm: 'full', md: 'auto' }}
zIndex={{ sm: 'modal', md: 'auto' }} zIndex={{ sm: 'modal', md: 'auto' }}
display={isFiltersExpanded ? 'flex' : 'none'} display={isFiltersExpanded ? 'flex' : 'none'}
style={{ transform: isMobile ? undefined : `translateX(${isFiltersExpanded ? 0 : -360}px)` }} style={{ transform: isMobile ? undefined : sidebarX.to((x) => `translateX(${x}px)`) }}
background="surface2"
> >
<Box <Box
paddingTop={{ sm: '24', md: '0' }} paddingTop={{ sm: '24', md: '0' }}
...@@ -122,7 +134,7 @@ export const FilterSidebar = ({ ...@@ -122,7 +134,7 @@ export const FilterSidebar = ({
hideSearch={hideSearch} hideSearch={hideSearch}
/> />
</Box> </Box>
</Box> </AnimatedBox>
) )
} }
......
import { useInfiniteQuery } from '@tanstack/react-query' import { useInfiniteQuery } from '@tanstack/react-query'
import { Box } from 'components/deprecated/Box' import { AnimatedBox, Box } from 'components/deprecated/Box'
import { useNftBalance } from 'graphql/data/nft/NftBalance' import { useNftBalance } from 'graphql/data/nft/NftBalance'
import { useIsMobile } from 'hooks/screenSize/useIsMobile' import { useIsMobile } from 'hooks/screenSize/useIsMobile'
import { useAccount } from 'hooks/useAccount' import { useAccount } from 'hooks/useAccount'
...@@ -22,6 +22,7 @@ import { getOSCollectionsInfiniteQueryOptions } from 'nft/queries/openSea/OSColl ...@@ -22,6 +22,7 @@ import { getOSCollectionsInfiniteQueryOptions } from 'nft/queries/openSea/OSColl
import { WalletCollection } from 'nft/types' import { WalletCollection } from 'nft/types'
import { Dispatch, SetStateAction, Suspense, useCallback, useEffect, useMemo, useState } from 'react' import { Dispatch, SetStateAction, Suspense, useCallback, useEffect, useMemo, useState } from 'react'
import InfiniteScroll from 'react-infinite-scroll-component' import InfiniteScroll from 'react-infinite-scroll-component'
import { easings, useSpring } from 'react-spring'
const ProfilePageColumn = styled(Column)` const ProfilePageColumn = styled(Column)`
${ScreenBreakpointsPaddings} ${ScreenBreakpointsPaddings}
...@@ -181,6 +182,14 @@ const ProfilePageNfts = ({ ...@@ -181,6 +182,14 @@ const ProfilePageNfts = ({
first: DEFAULT_WALLET_ASSET_QUERY_AMOUNT, first: DEFAULT_WALLET_ASSET_QUERY_AMOUNT,
}) })
const { gridX } = useSpring({
gridX: isFiltersExpanded ? FILTER_SIDEBAR_WIDTH : -PADDING,
config: {
duration: 250,
easing: easings.easeOutSine,
},
})
if (loading) { if (loading) {
return <ProfileBodyLoadingSkeleton /> return <ProfileBodyLoadingSkeleton />
} }
...@@ -192,11 +201,13 @@ const ProfilePageNfts = ({ ...@@ -192,11 +201,13 @@ const ProfilePageNfts = ({
<EmptyWalletModule /> <EmptyWalletModule />
</EmptyStateContainer> </EmptyStateContainer>
) : ( ) : (
<Box <AnimatedBox
flexShrink="0" flexShrink="0"
position={isMobile && isBagExpanded ? 'fixed' : 'static'} position={isMobile && isBagExpanded ? 'fixed' : 'static'}
style={{ style={{
transform: `translate(${Number(isFiltersExpanded ? FILTER_SIDEBAR_WIDTH : -PADDING) - (!isMobile && isFiltersExpanded ? FILTER_SIDEBAR_WIDTH : -PADDING)}px)`, transform: gridX.to(
(x) => `translate(${Number(x) - (!isMobile && isFiltersExpanded ? FILTER_SIDEBAR_WIDTH : -PADDING)}px)`,
),
}} }}
paddingY="20" paddingY="20"
> >
...@@ -238,7 +249,7 @@ const ProfilePageNfts = ({ ...@@ -238,7 +249,7 @@ const ProfilePageNfts = ({
)) ))
: null} : null}
</InfiniteScroll> </InfiniteScroll>
</Box> </AnimatedBox>
)} )}
</Column> </Column>
) )
......
...@@ -7,6 +7,7 @@ import { BREAKPOINTS } from 'theme' ...@@ -7,6 +7,7 @@ import { BREAKPOINTS } from 'theme'
const AppContainer = styled.div` const AppContainer = styled.div`
min-height: 100vh; min-height: 100vh;
max-width: 100vw; max-width: 100vw;
overflow-x: hidden;
// grid container settings // grid container settings
display: grid; display: grid;
......
...@@ -100,7 +100,7 @@ export function ClaimFeeModal() { ...@@ -100,7 +100,7 @@ export function ClaimFeeModal() {
<Modal name={ModalName.ClaimFee} onClose={onClose} isDismissible> <Modal name={ModalName.ClaimFee} onClose={onClose} isDismissible>
<Flex gap="$gap16"> <Flex gap="$gap16">
<GetHelpHeader <GetHelpHeader
link={uniswapUrls.helpRequestUrl} link={uniswapUrls.helpArticleUrls.lpCollectFees}
title={t('pool.collectFees')} title={t('pool.collectFees')}
closeModal={onClose} closeModal={onClose}
closeDataTestId="ClaimFeeModal-close-icon" closeDataTestId="ClaimFeeModal-close-icon"
......
...@@ -10,7 +10,6 @@ import { useV3OrV4PositionDerivedInfo } from 'components/Liquidity/hooks' ...@@ -10,7 +10,6 @@ import { useV3OrV4PositionDerivedInfo } from 'components/Liquidity/hooks'
import { parseRestPosition } from 'components/Liquidity/utils' import { parseRestPosition } from 'components/Liquidity/utils'
import { LoadingFullscreen, LoadingRows } from 'components/Loader/styled' import { LoadingFullscreen, LoadingRows } from 'components/Loader/styled'
import { ZERO_ADDRESS } from 'constants/misc' import { ZERO_ADDRESS } from 'constants/misc'
import { usePositionOwner } from 'hooks/usePositionOwner'
import { usePositionTokenURI } from 'hooks/usePositionTokenURI' import { usePositionTokenURI } from 'hooks/usePositionTokenURI'
import NotFound from 'pages/NotFound' import NotFound from 'pages/NotFound'
import { LoadingRow } from 'pages/Pool/Positions/shared' import { LoadingRow } from 'pages/Pool/Positions/shared'
...@@ -19,7 +18,6 @@ import { ChevronRight } from 'react-feather' ...@@ -19,7 +18,6 @@ import { ChevronRight } from 'react-feather'
import { Navigate, useLocation, useNavigate, useParams } from 'react-router-dom' import { Navigate, useLocation, useNavigate, useParams } from 'react-router-dom'
import { setOpenModal } from 'state/application/reducer' import { setOpenModal } from 'state/application/reducer'
import { useAppDispatch } from 'state/hooks' import { useAppDispatch } from 'state/hooks'
import { MultichainContextProvider } from 'state/multichain/MultichainContext'
import { usePendingLPTransactionsChangeListener } from 'state/transactions/hooks' import { usePendingLPTransactionsChangeListener } from 'state/transactions/hooks'
import { ClickableTamaguiStyle } from 'theme/components' import { ClickableTamaguiStyle } from 'theme/components'
import { Button, Flex, Main, Switch, Text, styled } from 'ui/src' import { Button, Flex, Main, Switch, Text, styled } from 'ui/src'
...@@ -31,7 +29,6 @@ import Trace from 'uniswap/src/features/telemetry/Trace' ...@@ -31,7 +29,6 @@ import Trace from 'uniswap/src/features/telemetry/Trace'
import { InterfacePageNameLocal, ModalName } from 'uniswap/src/features/telemetry/constants' import { InterfacePageNameLocal, ModalName } from 'uniswap/src/features/telemetry/constants'
import { Trans, useTranslation } from 'uniswap/src/i18n' import { Trans, useTranslation } from 'uniswap/src/i18n'
import { currencyId, currencyIdToAddress } from 'uniswap/src/utils/currencyId' import { currencyId, currencyIdToAddress } from 'uniswap/src/utils/currencyId'
import { addressesAreEquivalent } from 'utils/addressesAreEquivalent'
import { useChainIdFromUrlParam } from 'utils/chainParams' import { useChainIdFromUrlParam } from 'utils/chainParams'
import { NumberType, useFormatter } from 'utils/formatNumbers' import { NumberType, useFormatter } from 'utils/formatNumbers'
import { useAccount } from 'wagmi' import { useAccount } from 'wagmi'
...@@ -97,17 +94,7 @@ function parseTokenId(tokenId: string | undefined): BigNumber | undefined { ...@@ -97,17 +94,7 @@ function parseTokenId(tokenId: string | undefined): BigNumber | undefined {
} }
} }
export default function PositionPageWrapper() { export default function PositionPage() {
const chainId = useChainIdFromUrlParam()
return (
<MultichainContextProvider initialChainId={chainId}>
<PositionPage />
</MultichainContextProvider>
)
}
function PositionPage() {
const { tokenId: tokenIdFromUrl } = useParams<{ tokenId: string }>() const { tokenId: tokenIdFromUrl } = useParams<{ tokenId: string }>()
const tokenId = parseTokenId(tokenIdFromUrl) const tokenId = parseTokenId(tokenIdFromUrl)
const chainId = useChainIdFromUrlParam() const chainId = useChainIdFromUrlParam()
...@@ -131,7 +118,7 @@ function PositionPage() { ...@@ -131,7 +118,7 @@ function PositionPage() {
const position = data?.position const position = data?.position
const positionInfo = useMemo(() => parseRestPosition(position), [position]) const positionInfo = useMemo(() => parseRestPosition(position), [position])
const metadata = usePositionTokenURI(tokenId, chainInfo?.id, positionInfo?.version) const metadata = usePositionTokenURI(tokenId, chainInfo?.id, positionInfo?.version)
const owner = usePositionOwner(tokenId, chainInfo?.id, positionInfo?.version)
usePendingLPTransactionsChangeListener(refetch) usePendingLPTransactionsChangeListener(refetch)
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
...@@ -198,9 +185,8 @@ function PositionPage() { ...@@ -198,9 +185,8 @@ function PositionPage() {
} }
const hasFees = feeValue0?.greaterThan(0) || feeValue1?.greaterThan(0) || false const hasFees = feeValue0?.greaterThan(0) || feeValue1?.greaterThan(0) || false
const isOwner = addressesAreEquivalent(owner, account?.address)
// TODO (WEB-5859): Use owner from GetPositions instead of on-chain calls // TODO (WEB-4920): hide action buttons if position owner is not connected wallet.
return ( return (
<Trace <Trace
...@@ -232,7 +218,7 @@ function PositionPage() { ...@@ -232,7 +218,7 @@ function PositionPage() {
alignItems="center" alignItems="center"
> >
<LiquidityPositionInfo positionInfo={positionInfo} /> <LiquidityPositionInfo positionInfo={positionInfo} />
{status !== PositionStatus.CLOSED && isOwner && ( {status !== PositionStatus.CLOSED && (
<Flex row gap="$gap12" alignItems="center" flexWrap="wrap"> <Flex row gap="$gap12" alignItems="center" flexWrap="wrap">
{positionInfo.version === ProtocolVersion.V3 && isV4DataEnabled && ( {positionInfo.version === ProtocolVersion.V3 && isV4DataEnabled && (
<HeaderButton <HeaderButton
...@@ -312,7 +298,7 @@ function PositionPage() { ...@@ -312,7 +298,7 @@ function PositionPage() {
<Text variant="subheading1"> <Text variant="subheading1">
<Trans i18nKey="pool.uncollectedFees" /> <Trans i18nKey="pool.uncollectedFees" />
</Text> </Text>
{hasFees && isOwner && ( {hasFees && (
<HeaderButton <HeaderButton
emphasis="primary" emphasis="primary"
onPress={() => { onPress={() => {
......
...@@ -6,7 +6,6 @@ import { useGetPoolTokenPercentage } from 'components/Liquidity/hooks' ...@@ -6,7 +6,6 @@ import { useGetPoolTokenPercentage } from 'components/Liquidity/hooks'
import { parseRestPosition } from 'components/Liquidity/utils' import { parseRestPosition } from 'components/Liquidity/utils'
import { DoubleCurrencyAndChainLogo } from 'components/Logo/DoubleLogo' import { DoubleCurrencyAndChainLogo } from 'components/Logo/DoubleLogo'
import { ZERO_ADDRESS } from 'constants/misc' import { ZERO_ADDRESS } from 'constants/misc'
import { usePositionOwnerV2 } from 'hooks/usePositionOwner'
import NotFound from 'pages/NotFound' import NotFound from 'pages/NotFound'
import { HeaderButton } from 'pages/Pool/Positions/PositionPage' import { HeaderButton } from 'pages/Pool/Positions/PositionPage'
import { TextLoader } from 'pages/Pool/Positions/shared' import { TextLoader } from 'pages/Pool/Positions/shared'
...@@ -15,7 +14,6 @@ import { ChevronRight } from 'react-feather' ...@@ -15,7 +14,6 @@ import { ChevronRight } from 'react-feather'
import { Navigate, useNavigate, useParams } from 'react-router-dom' import { Navigate, useNavigate, useParams } from 'react-router-dom'
import { setOpenModal } from 'state/application/reducer' import { setOpenModal } from 'state/application/reducer'
import { useAppDispatch } from 'state/hooks' import { useAppDispatch } from 'state/hooks'
import { MultichainContextProvider } from 'state/multichain/MultichainContext'
import { usePendingLPTransactionsChangeListener } from 'state/transactions/hooks' import { usePendingLPTransactionsChangeListener } from 'state/transactions/hooks'
import { Button, Circle, Flex, Main, Shine, Text, styled } from 'ui/src' import { Button, Circle, Flex, Main, Shine, Text, styled } from 'ui/src'
import { useGetPositionQuery } from 'uniswap/src/data/rest/getPosition' import { useGetPositionQuery } from 'uniswap/src/data/rest/getPosition'
...@@ -62,17 +60,7 @@ function RowLoader({ withIcon }: { withIcon?: boolean }) { ...@@ -62,17 +60,7 @@ function RowLoader({ withIcon }: { withIcon?: boolean }) {
) )
} }
export default function V2PositionPageWrapper() { export default function V2PositionPage() {
const chainId = useChainIdFromUrlParam()
return (
<MultichainContextProvider initialChainId={chainId}>
<V2PositionPage />
</MultichainContextProvider>
)
}
function V2PositionPage() {
const { pairAddress } = useParams<{ pairAddress: string }>() const { pairAddress } = useParams<{ pairAddress: string }>()
const chainId = useChainIdFromUrlParam() const chainId = useChainIdFromUrlParam()
const account = useAccount() const account = useAccount()
...@@ -103,8 +91,6 @@ function V2PositionPage() { ...@@ -103,8 +91,6 @@ function V2PositionPage() {
const token0USDValue = useUSDCValue(currency0Amount) const token0USDValue = useUSDCValue(currency0Amount)
const token1USDValue = useUSDCValue(currency1Amount) const token1USDValue = useUSDCValue(currency1Amount)
const poolTokenPercentage = useGetPoolTokenPercentage(positionInfo) const poolTokenPercentage = useGetPoolTokenPercentage(positionInfo)
const liquidityTokenAddress = positionInfo?.liquidityToken?.isToken ? positionInfo.liquidityToken.address : null
const isOwner = usePositionOwnerV2(account?.address, liquidityTokenAddress, positionInfo?.chainId)
if (!isLoading && !lpRedesignEnabled) { if (!isLoading && !lpRedesignEnabled) {
return <Navigate to="/pools" replace /> return <Navigate to="/pools" replace />
...@@ -145,43 +131,41 @@ function V2PositionPage() { ...@@ -145,43 +131,41 @@ function V2PositionPage() {
) : ( ) : (
<LiquidityPositionInfo positionInfo={positionInfo} /> <LiquidityPositionInfo positionInfo={positionInfo} />
)} )}
{isOwner && ( <Flex row gap="$gap12" alignItems="center" maxWidth="100%" flexWrap="wrap">
<Flex row gap="$gap12" alignItems="center" maxWidth="100%" flexWrap="wrap"> <HeaderButton
<HeaderButton disabled={positionLoading}
disabled={positionLoading} emphasis="secondary"
emphasis="secondary" onPress={() => {
onPress={() => { navigate('/migrate/v2')
navigate('/migrate/v2') }}
}} >
> <Text variant="buttonLabel2" color="$neutral1">
<Text variant="buttonLabel2" color="$neutral1"> <Trans i18nKey="common.migrate.v3" />
<Trans i18nKey="common.migrate.v3" /> </Text>
</Text> </HeaderButton>
</HeaderButton> <HeaderButton
<HeaderButton disabled={positionLoading}
disabled={positionLoading} emphasis="secondary"
emphasis="secondary" onPress={() => {
onPress={() => { dispatch(setOpenModal({ name: ModalName.AddLiquidity, initialState: positionInfo }))
dispatch(setOpenModal({ name: ModalName.AddLiquidity, initialState: positionInfo })) }}
}} >
> <Text variant="buttonLabel2" color="$neutral1">
<Text variant="buttonLabel2" color="$neutral1"> <Trans i18nKey="common.addLiquidity" />
<Trans i18nKey="common.addLiquidity" /> </Text>
</Text> </HeaderButton>
</HeaderButton> <HeaderButton
<HeaderButton disabled={positionLoading}
disabled={positionLoading} emphasis="primary"
emphasis="primary" onPress={() => {
onPress={() => { dispatch(setOpenModal({ name: ModalName.RemoveLiquidity, initialState: positionInfo }))
dispatch(setOpenModal({ name: ModalName.RemoveLiquidity, initialState: positionInfo })) }}
}} >
> <Text variant="buttonLabel2" color="$surface1">
<Text variant="buttonLabel2" color="$surface1"> <Trans i18nKey="pool.removeLiquidity" />
<Trans i18nKey="pool.removeLiquidity" /> </Text>
</Text> </HeaderButton>
</HeaderButton> </Flex>
</Flex>
)}
<Flex borderColor="$surface3" borderWidth={1} p="$spacing24" gap="$gap12" borderRadius="$rounded20"> <Flex borderColor="$surface3" borderWidth={1} p="$spacing24" gap="$gap12" borderRadius="$rounded20">
{positionLoading || !currency0Amount || !currency1Amount ? ( {positionLoading || !currency0Amount || !currency1Amount ? (
<Shine> <Shine>
......
...@@ -30,9 +30,12 @@ import { ...@@ -30,9 +30,12 @@ import {
generateCreatePositionTxRequest, generateCreatePositionTxRequest,
} from 'pages/Pool/Positions/create/utils' } from 'pages/Pool/Positions/create/utils'
import { useCallback, useEffect, useMemo, useState } from 'react' import { useCallback, useEffect, useMemo, useState } from 'react'
import { PositionField } from 'types/position'
import { nativeOnChain } from 'uniswap/src/constants/tokens'
import { useAccountMeta } from 'uniswap/src/contexts/UniswapContext' import { useAccountMeta } from 'uniswap/src/contexts/UniswapContext'
import { useCheckLpApprovalQuery } from 'uniswap/src/data/apiClients/tradingApi/useCheckLpApprovalQuery' import { useCheckLpApprovalQuery } from 'uniswap/src/data/apiClients/tradingApi/useCheckLpApprovalQuery'
import { useCreateLpPositionCalldataQuery } from 'uniswap/src/data/apiClients/tradingApi/useCreateLpPositionCalldataQuery' import { useCreateLpPositionCalldataQuery } from 'uniswap/src/data/apiClients/tradingApi/useCreateLpPositionCalldataQuery'
import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains'
import { useSwapSettingsContext } from 'uniswap/src/features/transactions/swap/settings/contexts/SwapSettingsContext' import { useSwapSettingsContext } from 'uniswap/src/features/transactions/swap/settings/contexts/SwapSettingsContext'
import { ONE_SECOND_MS } from 'utilities/src/time/time' import { ONE_SECOND_MS } from 'utilities/src/time/time'
...@@ -44,6 +47,10 @@ export function CreatePositionContextProvider({ ...@@ -44,6 +47,10 @@ export function CreatePositionContextProvider({
initialState?: Partial<PositionState> initialState?: Partial<PositionState>
}) { }) {
const [positionState, setPositionState] = useState<PositionState>({ ...DEFAULT_POSITION_STATE, ...initialState }) const [positionState, setPositionState] = useState<PositionState>({ ...DEFAULT_POSITION_STATE, ...initialState })
useEffect(() => {
// initial state may load in from the URL
setPositionState((positionState) => ({ ...positionState, ...initialState }))
}, [initialState])
const [step, setStep] = useState<PositionFlowStep>(PositionFlowStep.SELECT_TOKENS_AND_FEE_TIER) const [step, setStep] = useState<PositionFlowStep>(PositionFlowStep.SELECT_TOKENS_AND_FEE_TIER)
const derivedPositionInfo = useDerivedPositionInfo(positionState) const derivedPositionInfo = useDerivedPositionInfo(positionState)
const [feeTierSearchModalOpen, setFeeTierSearchModalOpen] = useState(false) const [feeTierSearchModalOpen, setFeeTierSearchModalOpen] = useState(false)
...@@ -51,14 +58,18 @@ export function CreatePositionContextProvider({ ...@@ -51,14 +58,18 @@ export function CreatePositionContextProvider({
open: false, open: false,
wishFeeData: DEFAULT_POSITION_STATE.fee, wishFeeData: DEFAULT_POSITION_STATE.fee,
}) })
const { defaultChainId } = useEnabledChains()
const defaultInitialToken = nativeOnChain(defaultChainId)
const reset = useCallback(() => { const reset = useCallback(() => {
setPositionState({ setPositionState({
...DEFAULT_POSITION_STATE, ...DEFAULT_POSITION_STATE,
...initialState, protocolVersion: positionState.protocolVersion,
currencyInputs: { [PositionField.TOKEN0]: defaultInitialToken },
}) })
setStep(PositionFlowStep.SELECT_TOKENS_AND_FEE_TIER) setStep(PositionFlowStep.SELECT_TOKENS_AND_FEE_TIER)
}, [initialState]) }, [defaultInitialToken, positionState.protocolVersion])
return ( return (
<CreatePositionContext.Provider <CreatePositionContext.Provider
...@@ -164,6 +175,7 @@ export function CreateTxContextProvider({ children }: { children: React.ReactNod ...@@ -164,6 +175,7 @@ export function CreateTxContextProvider({ children }: { children: React.ReactNod
priceRangeState, priceRangeState,
derivedPriceRangeInfo, derivedPriceRangeInfo,
derivedDepositInfo, derivedDepositInfo,
swapSettings,
}) })
}, [ }, [
account, account,
...@@ -171,6 +183,7 @@ export function CreateTxContextProvider({ children }: { children: React.ReactNod ...@@ -171,6 +183,7 @@ export function CreateTxContextProvider({ children }: { children: React.ReactNod
derivedDepositInfo, derivedDepositInfo,
derivedPositionInfo, derivedPositionInfo,
derivedPriceRangeInfo, derivedPriceRangeInfo,
swapSettings,
positionState, positionState,
priceRangeState, priceRangeState,
]) ])
...@@ -180,8 +193,7 @@ export function CreateTxContextProvider({ children }: { children: React.ReactNod ...@@ -180,8 +193,7 @@ export function CreateTxContextProvider({ children }: { children: React.ReactNod
refetch: createRefetch, refetch: createRefetch,
} = useCreateLpPositionCalldataQuery({ } = useCreateLpPositionCalldataQuery({
params: createCalldataQueryParams, params: createCalldataQueryParams,
deadlineInMinutes: swapSettings.customDeadline, staleTime: 5 * ONE_SECOND_MS,
refetchInterval: 5 * ONE_SECOND_MS,
}) })
const validatedValue = useMemo(() => { const validatedValue = useMemo(() => {
......
...@@ -48,7 +48,6 @@ import { SwapFormSettings } from 'uniswap/src/features/transactions/swap/form/Sw ...@@ -48,7 +48,6 @@ import { SwapFormSettings } from 'uniswap/src/features/transactions/swap/form/Sw
import { Deadline } from 'uniswap/src/features/transactions/swap/settings/configs/Deadline' import { Deadline } from 'uniswap/src/features/transactions/swap/settings/configs/Deadline'
import { SwapSettingsContextProvider } from 'uniswap/src/features/transactions/swap/settings/contexts/SwapSettingsContext' import { SwapSettingsContextProvider } from 'uniswap/src/features/transactions/swap/settings/contexts/SwapSettingsContext'
import { Trans, useTranslation } from 'uniswap/src/i18n' import { Trans, useTranslation } from 'uniswap/src/i18n'
import { INTERFACE_NAV_HEIGHT } from 'uniswap/src/theme/heights'
import { usePrevious } from 'utilities/src/react/hooks' import { usePrevious } from 'utilities/src/react/hooks'
function CreatingPoolInfo() { function CreatingPoolInfo() {
...@@ -179,7 +178,7 @@ const Sidebar = () => { ...@@ -179,7 +178,7 @@ const Sidebar = () => {
}, [creatingPoolOrPair, protocolVersion, setStep, step, t]) }, [creatingPoolOrPair, protocolVersion, setStep, step, t])
return ( return (
<Flex width={360} alignSelf="flex-start" $platform-web={{ position: 'sticky', top: INTERFACE_NAV_HEIGHT + 25 }}> <Flex width={360}>
<PoolProgressIndicator steps={PoolProgressSteps} /> <PoolProgressIndicator steps={PoolProgressSteps} />
</Flex> </Flex>
) )
...@@ -232,7 +231,7 @@ const Toolbar = ({ ...@@ -232,7 +231,7 @@ const Toolbar = ({
const { reset: resetMultichainState } = useMultichainContext() const { reset: resetMultichainState } = useMultichainContext()
const { isTestnetModeEnabled } = useEnabledChains() const { isTestnetModeEnabled } = useEnabledChains()
const prevIsTestnetModeEnabled = usePrevious(isTestnetModeEnabled) ?? false const prevIsTestnetModeEnabled = usePrevious(isTestnetModeEnabled)
const isFormUnchanged = useMemo(() => { const isFormUnchanged = useMemo(() => {
// Check if all form fields (except protocol version) are set to their default values // Check if all form fields (except protocol version) are set to their default values
...@@ -356,13 +355,10 @@ const Toolbar = ({ ...@@ -356,13 +355,10 @@ const Toolbar = ({
export default function CreatePosition() { export default function CreatePosition() {
const { value: lpRedesignEnabled, isLoading } = useFeatureFlagWithLoading(FeatureFlags.LPRedesign) const { value: lpRedesignEnabled, isLoading } = useFeatureFlagWithLoading(FeatureFlags.LPRedesign)
const isV4DataEnabled = useFeatureFlag(FeatureFlags.V4Data) const isV4DataEnabled = useFeatureFlag(FeatureFlags.V4Data)
const media = useMedia()
// URL format is `/positions/create/:protocolVersion`, with possible searchParams `?currencyA=...&currencyB=...&chain=...` // URL format is `/positions/create/:protocolVersion`, with possible searchParams `?currencyA=...&currencyB=...&chain=...`
const { protocolVersion } = useParams<{ protocolVersion: string }>() const { protocolVersion } = useParams<{ protocolVersion: string }>()
const paramsProtocolVersion = parseProtocolVersion(protocolVersion) const paramsProtocolVersion = parseProtocolVersion(protocolVersion)
const initialCurrencyInputs = useInitialCurrencyInputs()
const initialProtocolVersion = useMemo((): ProtocolVersion => { const initialProtocolVersion = useMemo((): ProtocolVersion => {
if (isV4DataEnabled) { if (isV4DataEnabled) {
return paramsProtocolVersion ?? ProtocolVersion.V4 return paramsProtocolVersion ?? ProtocolVersion.V4
...@@ -374,6 +370,10 @@ export default function CreatePosition() { ...@@ -374,6 +370,10 @@ export default function CreatePosition() {
return paramsProtocolVersion return paramsProtocolVersion
}, [isV4DataEnabled, paramsProtocolVersion]) }, [isV4DataEnabled, paramsProtocolVersion])
const initialCurrencyInputs = useInitialCurrencyInputs()
const media = useMedia()
if (!isLoading && !lpRedesignEnabled) { if (!isLoading && !lpRedesignEnabled) {
return <Navigate to="/pools" replace /> return <Navigate to="/pools" replace />
} }
......
...@@ -73,7 +73,6 @@ export const usePriceRangeContext = () => { ...@@ -73,7 +73,6 @@ export const usePriceRangeContext = () => {
export const DEFAULT_DEPOSIT_STATE: DepositState = { export const DEFAULT_DEPOSIT_STATE: DepositState = {
exactField: PositionField.TOKEN0, exactField: PositionField.TOKEN0,
exactAmounts: {},
} }
export const DepositContext = React.createContext<DepositContextType>({ export const DepositContext = React.createContext<DepositContextType>({
......
...@@ -34,11 +34,9 @@ export const DepositStep = ({ ...rest }: FlexProps) => { ...@@ -34,11 +34,9 @@ export const DepositStep = ({ ...rest }: FlexProps) => {
const handleUserInput = (field: PositionField, newValue: string) => { const handleUserInput = (field: PositionField, newValue: string) => {
setDepositState((prev) => ({ setDepositState((prev) => ({
...prev,
exactField: field, exactField: field,
exactAmounts: { exactAmount: newValue,
...prev.exactAmounts,
[field]: newValue,
},
})) }))
} }
...@@ -68,7 +66,7 @@ export const DepositStep = ({ ...rest }: FlexProps) => { ...@@ -68,7 +66,7 @@ export const DepositStep = ({ ...rest }: FlexProps) => {
<> <>
<Container {...rest}> <Container {...rest}>
<Flex gap={32}> <Flex gap={32}>
<Flex gap="$spacing4"> <Flex row alignItems="center">
<Text variant="subheading1"> <Text variant="subheading1">
<Trans i18nKey="common.depositTokens" /> <Trans i18nKey="common.depositTokens" />
</Text> </Text>
......
...@@ -259,9 +259,7 @@ export type UseDepositInfoProps = { ...@@ -259,9 +259,7 @@ export type UseDepositInfoProps = {
token0?: Currency token0?: Currency
token1?: Currency token1?: Currency
exactField: PositionField exactField: PositionField
exactAmounts: { exactAmount?: string
[field in PositionField]?: string
}
skipDependentAmount?: boolean skipDependentAmount?: boolean
deposit0Disabled?: boolean deposit0Disabled?: boolean
deposit1Disabled?: boolean deposit1Disabled?: boolean
...@@ -291,7 +289,7 @@ export function useDerivedDepositInfo(state: DepositState): DepositInfo { ...@@ -291,7 +289,7 @@ export function useDerivedDepositInfo(state: DepositState): DepositInfo {
const account = useAccount() const account = useAccount()
const { derivedPositionInfo } = useCreatePositionContext() const { derivedPositionInfo } = useCreatePositionContext()
const { derivedPriceRangeInfo } = usePriceRangeContext() const { derivedPriceRangeInfo } = usePriceRangeContext()
const { exactAmounts, exactField } = state const { exactAmount, exactField } = state
const { protocolVersion } = derivedPriceRangeInfo const { protocolVersion } = derivedPriceRangeInfo
const depositInfoProps: UseDepositInfoProps = useMemo(() => { const depositInfoProps: UseDepositInfoProps = useMemo(() => {
...@@ -303,7 +301,7 @@ export function useDerivedDepositInfo(state: DepositState): DepositInfo { ...@@ -303,7 +301,7 @@ export function useDerivedDepositInfo(state: DepositState): DepositInfo {
token0: derivedPositionInfo.currencies[0], token0: derivedPositionInfo.currencies[0],
token1: derivedPositionInfo.currencies[1], token1: derivedPositionInfo.currencies[1],
exactField, exactField,
exactAmounts, exactAmount,
} satisfies UseDepositInfoProps } satisfies UseDepositInfoProps
} }
...@@ -321,7 +319,7 @@ export function useDerivedDepositInfo(state: DepositState): DepositInfo { ...@@ -321,7 +319,7 @@ export function useDerivedDepositInfo(state: DepositState): DepositInfo {
token0: derivedPositionInfo.currencies[0], token0: derivedPositionInfo.currencies[0],
token1: derivedPositionInfo.currencies[1], token1: derivedPositionInfo.currencies[1],
exactField, exactField,
exactAmounts, exactAmount,
skipDependentAmount: outOfRange || invalidRange, skipDependentAmount: outOfRange || invalidRange,
deposit0Disabled, deposit0Disabled,
deposit1Disabled, deposit1Disabled,
...@@ -337,29 +335,25 @@ export function useDerivedDepositInfo(state: DepositState): DepositInfo { ...@@ -337,29 +335,25 @@ export function useDerivedDepositInfo(state: DepositState): DepositInfo {
token0: derivedPositionInfo.currencies[0], token0: derivedPositionInfo.currencies[0],
token1: derivedPositionInfo.currencies[1], token1: derivedPositionInfo.currencies[1],
exactField, exactField,
exactAmounts, exactAmount,
skipDependentAmount: outOfRange || invalidRange, skipDependentAmount: outOfRange || invalidRange,
deposit0Disabled, deposit0Disabled,
deposit1Disabled, deposit1Disabled,
} satisfies UseDepositInfoProps } satisfies UseDepositInfoProps
}, [account.address, derivedPositionInfo, derivedPriceRangeInfo, exactAmounts, exactField, protocolVersion]) }, [account.address, derivedPositionInfo, derivedPriceRangeInfo, exactAmount, exactField, protocolVersion])
return useDepositInfo(depositInfoProps) return useDepositInfo(depositInfoProps)
} }
export function useDepositInfo(state: UseDepositInfoProps): DepositInfo { export function useDepositInfo(state: UseDepositInfoProps): DepositInfo {
const account = useAccount() const account = useAccount()
const { protocolVersion, address, token0, token1, exactField, exactAmounts, deposit0Disabled, deposit1Disabled } = const { protocolVersion, address, token0, token1, exactField, exactAmount, deposit0Disabled, deposit1Disabled } =
state state
const [token0Balance, token1Balance] = useCurrencyBalances(address, [token0, token1]) const [token0Balance, token1Balance] = useCurrencyBalances(address, [token0, token1])
const [independentToken, dependentToken] = exactField === PositionField.TOKEN0 ? [token0, token1] : [token1, token0] const [independentToken, dependentToken] = exactField === PositionField.TOKEN0 ? [token0, token1] : [token1, token0]
const independentAmount = tryParseCurrencyAmount(exactAmounts[exactField], independentToken) const independentAmount = tryParseCurrencyAmount(exactAmount, independentToken)
const otherAmount = tryParseCurrencyAmount(
exactAmounts[exactField === PositionField.TOKEN0 ? PositionField.TOKEN1 : PositionField.TOKEN0],
dependentToken,
)
const dependentAmount: CurrencyAmount<Currency> | undefined = useMemo(() => { const dependentAmount: CurrencyAmount<Currency> | undefined = useMemo(() => {
const shouldSkip = state.skipDependentAmount || protocolVersion === ProtocolVersion.UNSPECIFIED const shouldSkip = state.skipDependentAmount || protocolVersion === ProtocolVersion.UNSPECIFIED
...@@ -370,7 +364,6 @@ export function useDepositInfo(state: UseDepositInfoProps): DepositInfo { ...@@ -370,7 +364,6 @@ export function useDepositInfo(state: UseDepositInfoProps): DepositInfo {
if (protocolVersion === ProtocolVersion.V2) { if (protocolVersion === ProtocolVersion.V2) {
return getDependentAmountFromV2Pair({ return getDependentAmountFromV2Pair({
independentAmount, independentAmount,
otherAmount,
pair: state.pair, pair: state.pair,
exactField, exactField,
token0, token0,
...@@ -399,7 +392,7 @@ export function useDepositInfo(state: UseDepositInfoProps): DepositInfo { ...@@ -399,7 +392,7 @@ export function useDepositInfo(state: UseDepositInfoProps): DepositInfo {
tickUpper, tickUpper,
}) })
return dependentToken && CurrencyAmount.fromRawAmount(dependentToken, dependentTokenAmount.quotient) return dependentToken && CurrencyAmount.fromRawAmount(dependentToken, dependentTokenAmount.quotient)
}, [state, protocolVersion, independentAmount, otherAmount, dependentToken, exactField, token0, token1]) }, [state, protocolVersion, independentAmount, dependentToken, exactField, token0, token1])
const independentTokenUSDValue = useUSDCValue(independentAmount) || undefined const independentTokenUSDValue = useUSDCValue(independentAmount) || undefined
const dependentTokenUSDValue = useUSDCValue(dependentAmount) || undefined const dependentTokenUSDValue = useUSDCValue(dependentAmount) || undefined
...@@ -474,7 +467,7 @@ export function useDepositInfo(state: UseDepositInfoProps): DepositInfo { ...@@ -474,7 +467,7 @@ export function useDepositInfo(state: UseDepositInfoProps): DepositInfo {
return useMemo( return useMemo(
() => ({ () => ({
currencyBalances: { [PositionField.TOKEN0]: token0Balance, [PositionField.TOKEN1]: token1Balance }, currencyBalances: { [PositionField.TOKEN0]: token0Balance, [PositionField.TOKEN1]: token1Balance },
formattedAmounts: { [exactField]: exactAmounts[exactField], [dependentField]: dependentAmount?.toExact() }, formattedAmounts: { [exactField]: exactAmount, [dependentField]: dependentAmount?.toExact() },
currencyAmounts: { [exactField]: independentAmount, [dependentField]: dependentAmount }, currencyAmounts: { [exactField]: independentAmount, [dependentField]: dependentAmount },
currencyAmountsUSDValue: { [exactField]: independentTokenUSDValue, [dependentField]: dependentTokenUSDValue }, currencyAmountsUSDValue: { [exactField]: independentTokenUSDValue, [dependentField]: dependentTokenUSDValue },
error, error,
...@@ -483,7 +476,7 @@ export function useDepositInfo(state: UseDepositInfoProps): DepositInfo { ...@@ -483,7 +476,7 @@ export function useDepositInfo(state: UseDepositInfoProps): DepositInfo {
token0Balance, token0Balance,
token1Balance, token1Balance,
exactField, exactField,
exactAmounts, exactAmount,
dependentField, dependentField,
dependentAmount, dependentAmount,
independentAmount, independentAmount,
...@@ -524,10 +517,8 @@ export function useInitialCurrencyInputs() { ...@@ -524,10 +517,8 @@ export function useInitialCurrencyInputs() {
const currencyA = useCurrency(currencyAddressA, supportedChainId) const currencyA = useCurrency(currencyAddressA, supportedChainId)
const currencyB = useCurrency(currencyAddressB, supportedChainId) const currencyB = useCurrency(currencyAddressB, supportedChainId)
return useMemo(() => { return {
return { [PositionField.TOKEN0]: currencyA ?? currencyB ?? defaultInitialToken,
[PositionField.TOKEN0]: currencyA ?? currencyB ?? defaultInitialToken, [PositionField.TOKEN1]: currencyA && currencyB ? currencyB : undefined,
[PositionField.TOKEN1]: currencyA && currencyB ? currencyB : undefined, }
}
}, [currencyA, currencyB, defaultInitialToken])
} }
...@@ -44,6 +44,8 @@ import { ...@@ -44,6 +44,8 @@ import {
} from 'uniswap/src/data/tradingApi/__generated__' } from 'uniswap/src/data/tradingApi/__generated__'
import { AccountMeta } from 'uniswap/src/features/accounts/types' import { AccountMeta } from 'uniswap/src/features/accounts/types'
import { CreatePositionTxAndGasInfo, LiquidityTransactionType } from 'uniswap/src/features/transactions/liquidity/types' import { CreatePositionTxAndGasInfo, LiquidityTransactionType } from 'uniswap/src/features/transactions/liquidity/types'
import { getTradeSettingsDeadline } from 'uniswap/src/features/transactions/swap/form/utils'
import { SwapSettingsState } from 'uniswap/src/features/transactions/swap/settings/contexts/SwapSettingsContext'
import { validatePermit, validateTransactionRequest } from 'uniswap/src/features/transactions/swap/utils/trade' import { validatePermit, validateTransactionRequest } from 'uniswap/src/features/transactions/swap/utils/trade'
import { areCurrenciesEqual } from 'uniswap/src/utils/currencyId' import { areCurrenciesEqual } from 'uniswap/src/utils/currencyId'
import { getTickToPrice, getV4TickToPrice } from 'utils/getTickToPrice' import { getTickToPrice, getV4TickToPrice } from 'utils/getTickToPrice'
...@@ -413,7 +415,6 @@ function createMockPair({ ...@@ -413,7 +415,6 @@ function createMockPair({
export function getDependentAmountFromV2Pair({ export function getDependentAmountFromV2Pair({
independentAmount, independentAmount,
otherAmount,
pair, pair,
exactField, exactField,
token0, token0,
...@@ -421,7 +422,6 @@ export function getDependentAmountFromV2Pair({ ...@@ -421,7 +422,6 @@ export function getDependentAmountFromV2Pair({
dependentToken, dependentToken,
}: { }: {
independentAmount?: CurrencyAmount<Currency> independentAmount?: CurrencyAmount<Currency>
otherAmount?: CurrencyAmount<Currency>
pair?: Pair pair?: Pair
exactField: PositionField exactField: PositionField
token0?: Currency token0?: Currency
...@@ -433,22 +433,16 @@ export function getDependentAmountFromV2Pair({ ...@@ -433,22 +433,16 @@ export function getDependentAmountFromV2Pair({
return undefined return undefined
} }
try { const dependentTokenAmount =
const dependentTokenAmount = exactField === PositionField.TOKEN0
exactField === PositionField.TOKEN0 ? pair.priceOf(token0Wrapped).quote(independentAmount.wrapped)
? pair.priceOf(token0Wrapped).quote(independentAmount.wrapped) : pair.priceOf(token1Wrapped).quote(independentAmount.wrapped)
: pair.priceOf(token1Wrapped).quote(independentAmount.wrapped)
return dependentToken return dependentToken
? dependentToken?.isNative ? dependentToken?.isNative
? CurrencyAmount.fromRawAmount(dependentToken, dependentTokenAmount.quotient) ? CurrencyAmount.fromRawAmount(dependentToken, dependentTokenAmount.quotient)
: dependentTokenAmount : dependentTokenAmount
: undefined : undefined
} catch (e) {
// in some cases there can be an initialized pool but there is no liquidity in which case
// the user can enter whatever they want for the dependent amount and that pool will be created
return otherAmount
}
} }
export function getDependentAmountFromV3Position({ export function getDependentAmountFromV3Position({
...@@ -894,6 +888,7 @@ export function generateCreateCalldataQueryParams({ ...@@ -894,6 +888,7 @@ export function generateCreateCalldataQueryParams({
priceRangeState, priceRangeState,
derivedPriceRangeInfo, derivedPriceRangeInfo,
derivedDepositInfo, derivedDepositInfo,
swapSettings,
}: { }: {
account?: AccountMeta account?: AccountMeta
approvalCalldata?: CheckApprovalLPResponse approvalCalldata?: CheckApprovalLPResponse
...@@ -902,10 +897,12 @@ export function generateCreateCalldataQueryParams({ ...@@ -902,10 +897,12 @@ export function generateCreateCalldataQueryParams({
priceRangeState: PriceRangeState priceRangeState: PriceRangeState
derivedPriceRangeInfo: PriceRangeInfo derivedPriceRangeInfo: PriceRangeInfo
derivedDepositInfo: DepositInfo derivedDepositInfo: DepositInfo
swapSettings: SwapSettingsState
}): CreateLPPositionRequest | undefined { }): CreateLPPositionRequest | undefined {
const apiProtocolItems = getProtocolItems(positionState.protocolVersion) const apiProtocolItems = getProtocolItems(positionState.protocolVersion)
const currencies = derivedPositionInfo.currencies const currencies = derivedPositionInfo.currencies
const { currencyAmounts } = derivedDepositInfo const { currencyAmounts } = derivedDepositInfo
const { customDeadline } = swapSettings
if ( if (
!account?.address || !account?.address ||
...@@ -919,6 +916,8 @@ export function generateCreateCalldataQueryParams({ ...@@ -919,6 +916,8 @@ export function generateCreateCalldataQueryParams({
const { token0Approval, token1Approval, positionTokenApproval, permitData } = approvalCalldata ?? {} const { token0Approval, token1Approval, positionTokenApproval, permitData } = approvalCalldata ?? {}
const deadline = getTradeSettingsDeadline(customDeadline)
if (derivedPositionInfo.protocolVersion === ProtocolVersion.V2) { if (derivedPositionInfo.protocolVersion === ProtocolVersion.V2) {
if (derivedPositionInfo.protocolVersion !== derivedPriceRangeInfo.protocolVersion) { if (derivedPositionInfo.protocolVersion !== derivedPriceRangeInfo.protocolVersion) {
return undefined return undefined
...@@ -947,6 +946,7 @@ export function generateCreateCalldataQueryParams({ ...@@ -947,6 +946,7 @@ export function generateCreateCalldataQueryParams({
chainId: currencyAmounts.TOKEN0.currency.chainId, chainId: currencyAmounts.TOKEN0.currency.chainId,
amount0: currencyAmounts[token0Index]?.quotient.toString(), amount0: currencyAmounts[token0Index]?.quotient.toString(),
amount1: currencyAmounts[token1Index]?.quotient.toString(), amount1: currencyAmounts[token1Index]?.quotient.toString(),
deadline,
position: { position: {
pool: { pool: {
token0: getCurrencyAddressForTradingApi(currencyAmounts[token0Index]?.currency), token0: getCurrencyAddressForTradingApi(currencyAmounts[token0Index]?.currency),
...@@ -1005,6 +1005,7 @@ export function generateCreateCalldataQueryParams({ ...@@ -1005,6 +1005,7 @@ export function generateCreateCalldataQueryParams({
currentTick, currentTick,
sqrtRatioX96, sqrtRatioX96,
initialPrice, initialPrice,
deadline,
position: { position: {
tickLower, tickLower,
tickUpper, tickUpper,
......
...@@ -364,13 +364,7 @@ export default function Pool() { ...@@ -364,13 +364,7 @@ export default function Pool() {
text={t('liquidity.provideOnProtocols')} text={t('liquidity.provideOnProtocols')}
link={uniswapUrls.helpArticleUrls.providingLiquidityInfo} link={uniswapUrls.helpArticleUrls.providingLiquidityInfo}
/> />
{isV4DataEnabled && ( <LearnMoreTile img={V4_HOOK} text={t('liquidity.hooks')} link={uniswapUrls.helpArticleUrls.v4HooksInfo} />
<LearnMoreTile
img={V4_HOOK}
text={t('liquidity.hooks')}
link={uniswapUrls.helpArticleUrls.v4HooksInfo}
/>
)}
</Flex> </Flex>
<ExternalArrowLink href={uniswapUrls.helpArticleUrls.positionsLearnMore}> <ExternalArrowLink href={uniswapUrls.helpArticleUrls.positionsLearnMore}>
{t('common.button.learn')} {t('common.button.learn')}
......
import { forwardRef, useEffect } from 'react' import { forwardRef } from 'react'
import { Input, InputProps, Input as TextInputBase, isWeb, useSporeColors } from 'ui/src' import { Input, InputProps, Input as TextInputBase, useSporeColors } from 'ui/src'
export type TextInputProps = InputProps export type TextInputProps = InputProps
...@@ -10,18 +10,6 @@ export const TextInput = forwardRef<TextInputBase, TextInputProps>(function _Tex ...@@ -10,18 +10,6 @@ export const TextInput = forwardRef<TextInputBase, TextInputProps>(function _Tex
ref, ref,
) { ) {
const colors = useSporeColors() const colors = useSporeColors()
useEffect(() => {
// Ensure virtualkeyboardpolicy is set to "auto" on the DOM element
// otherwise the virtual keyboard will not show on android mobile
// TODO (WEB-5798): remove tamagui input hack
if (ref && 'current' in ref && ref.current && isWeb) {
const inputElement = ref.current as unknown as HTMLElement
inputElement.setAttribute('virtualkeyboardpolicy', 'auto')
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []) // only run on mount since we only need to set this once
return ( return (
<Input <Input
ref={ref} ref={ref}
......
...@@ -4,7 +4,10 @@ import type { ImageSourcePropType } from 'react-native' ...@@ -4,7 +4,10 @@ import type { ImageSourcePropType } from 'react-native'
import { CELO_LOGO, ETH_LOGO } from 'ui/src/assets' import { CELO_LOGO, ETH_LOGO } from 'ui/src/assets'
import { import {
ARB, ARB,
BTC_BSC,
BUSD_BSC, BUSD_BSC,
CEUR_CELO,
CUSD_CELO,
DAI, DAI,
DAI_ARBITRUM_ONE, DAI_ARBITRUM_ONE,
DAI_AVALANCHE, DAI_AVALANCHE,
...@@ -35,6 +38,7 @@ import { ...@@ -35,6 +38,7 @@ import {
USDT_POLYGON, USDT_POLYGON,
WBTC, WBTC,
WBTC_ARBITRUM_ONE, WBTC_ARBITRUM_ONE,
WBTC_CELO,
WBTC_OPTIMISM, WBTC_OPTIMISM,
WBTC_POLYGON, WBTC_POLYGON,
WETH_AVALANCHE, WETH_AVALANCHE,
...@@ -55,9 +59,7 @@ type ChainCurrencyList = { ...@@ -55,9 +59,7 @@ type ChainCurrencyList = {
} }
/** /**
* @deprecated * Shows up in the currency select for swap and add liquidity
* Instead, see the list used in the token selector's quick-select common options section at useAllCommonBaseCurrencies.ts.
* This list is currently used as fallback list when Token GQL query fails for above list + for hardcoded tokens on testnet chains.
*/ */
export const COMMON_BASES: ChainCurrencyList = { export const COMMON_BASES: ChainCurrencyList = {
[UniverseChainId.Mainnet]: [ [UniverseChainId.Mainnet]: [
...@@ -116,11 +118,24 @@ export const COMMON_BASES: ChainCurrencyList = { ...@@ -116,11 +118,24 @@ export const COMMON_BASES: ChainCurrencyList = {
WBTC_POLYGON, WBTC_POLYGON,
].map(buildPartialCurrencyInfo), ].map(buildPartialCurrencyInfo),
[UniverseChainId.Celo]: [nativeOnChain(UniverseChainId.Celo), USDC_CELO].map(buildPartialCurrencyInfo), [UniverseChainId.Celo]: [
nativeOnChain(UniverseChainId.Celo),
CEUR_CELO,
CUSD_CELO,
PORTAL_ETH_CELO,
USDC_CELO,
WBTC_CELO,
].map(buildPartialCurrencyInfo),
[UniverseChainId.Bnb]: [nativeOnChain(UniverseChainId.Bnb), DAI_BSC, USDC_BSC, USDT_BSC, ETH_BSC, BUSD_BSC].map( [UniverseChainId.Bnb]: [
buildPartialCurrencyInfo, nativeOnChain(UniverseChainId.Bnb),
), DAI_BSC,
USDC_BSC,
USDT_BSC,
ETH_BSC,
BTC_BSC,
BUSD_BSC,
].map(buildPartialCurrencyInfo),
[UniverseChainId.Avalanche]: [ [UniverseChainId.Avalanche]: [
nativeOnChain(UniverseChainId.Avalanche), nativeOnChain(UniverseChainId.Avalanche),
......
...@@ -91,8 +91,6 @@ export const USDC_BASE = new Token( ...@@ -91,8 +91,6 @@ export const USDC_BASE = new Token(
'USD Coin', 'USD Coin',
) )
export const BTC_BSC = new Token(UniverseChainId.Bnb, '0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c', 18, 'BTCB', 'BTCB')
export const USDC_BNB = new Token(UniverseChainId.Bnb, '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d', 18, 'USDC', 'USDC') export const USDC_BNB = new Token(UniverseChainId.Bnb, '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d', 18, 'USDC', 'USDC')
export const USDT_BNB = new Token( export const USDT_BNB = new Token(
...@@ -103,6 +101,8 @@ export const USDT_BNB = new Token( ...@@ -103,6 +101,8 @@ export const USDT_BNB = new Token(
'TetherUSD', 'TetherUSD',
) )
export const BTC_BSC = new Token(UniverseChainId.Bnb, '0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c', 18, 'BTCB', 'BTCB')
export const USDC_BSC = new Token(UniverseChainId.Bnb, '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d', 18, 'USDC', 'USDC') export const USDC_BSC = new Token(UniverseChainId.Bnb, '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d', 18, 'USDC', 'USDC')
export const USDT_BSC = new Token(UniverseChainId.Bnb, '0x55d398326f99059fF775485246999027B3197955', 18, 'USDT', 'USDT') export const USDT_BSC = new Token(UniverseChainId.Bnb, '0x55d398326f99059fF775485246999027B3197955', 18, 'USDT', 'USDT')
...@@ -245,6 +245,14 @@ export const CELO_CELO = new Token( ...@@ -245,6 +245,14 @@ export const CELO_CELO = new Token(
'Celo', 'Celo',
) )
export const CEUR_CELO = new Token(
UniverseChainId.Celo,
'0xD8763CBa276a3738E6DE85b4b3bF5FDed6D6cA73',
18,
'cEUR',
'Celo Euro Stablecoin',
)
export const PORTAL_ETH_CELO = new Token( export const PORTAL_ETH_CELO = new Token(
UniverseChainId.Celo, UniverseChainId.Celo,
'0x66803FB87aBd4aaC3cbB3fAd7C3aa01f6F3FB207', '0x66803FB87aBd4aaC3cbB3fAd7C3aa01f6F3FB207',
...@@ -253,6 +261,14 @@ export const PORTAL_ETH_CELO = new Token( ...@@ -253,6 +261,14 @@ export const PORTAL_ETH_CELO = new Token(
'Portal Ether', 'Portal Ether',
) )
export const WBTC_CELO = new Token(
UniverseChainId.Celo,
'0xd71Ffd0940c920786eC4DbB5A12306669b5b81EF',
18,
'WBTC',
'Wrapped BTC',
)
export const USDC_CELO = new Token( export const USDC_CELO = new Token(
UniverseChainId.Celo, UniverseChainId.Celo,
'0xceba9300f2b948710d2653dd7b07f33a8b32118c', '0xceba9300f2b948710d2653dd7b07f33a8b32118c',
......
...@@ -39,14 +39,15 @@ export const uniswapUrls = { ...@@ -39,14 +39,15 @@ export const uniswapUrls = {
limitsFailure: `${helpUrl}/articles/24300813697933-Why-did-my-limit-order-fail-or-not-execute`, limitsFailure: `${helpUrl}/articles/24300813697933-Why-did-my-limit-order-fail-or-not-execute`,
limitsInfo: `${helpUrl}/sections/24372644881293`, limitsInfo: `${helpUrl}/sections/24372644881293`,
limitsNetworkSupport: `${helpUrl}/articles/24470251716237-What-networks-do-limits-support`, limitsNetworkSupport: `${helpUrl}/articles/24470251716237-What-networks-do-limits-support`,
lpCollectFees: `${helpUrl}/articles/20901267003789-How-to-collect-fees-from-a-liquidity-pool-on-Uniswap-v3`,
fiatOnRampHelp: `${helpUrl}/articles/11306574799117`, fiatOnRampHelp: `${helpUrl}/articles/11306574799117`,
transferCryptoHelp: `${helpUrl}/articles/27103878635661-How-to-transfer-crypto-from-a-Robinhood-or-Coinbase-account-to-the-Uniswap-Wallet`, transferCryptoHelp: `${helpUrl}/articles/27103878635661-How-to-transfer-crypto-from-a-Robinhood-or-Coinbase-account-to-the-Uniswap-Wallet`,
moonpayRegionalAvailability: `${helpUrl}/articles/11306664890381-Why-isn-t-MoonPay-available-in-my-region-`, moonpayRegionalAvailability: `${helpUrl}/articles/11306664890381-Why-isn-t-MoonPay-available-in-my-region-`,
networkFeeInfo: `${helpUrl}/articles/8370337377805-What-is-a-network-fee-`, networkFeeInfo: `${helpUrl}/articles/8370337377805-What-is-a-network-fee-`,
poolOutOfSync: `${helpUrl}/articles/25845512413069`, poolOutOfSync: `${helpUrl}/articles/25845512413069`,
positionsLearnMore: `${helpUrl}/sections/8122851346573`, positionsLearnMore: `${helpUrl}/sections/30998264709645`,
priceImpact: `${helpUrl}/articles/8671539602317-What-is-Price-Impact`, priceImpact: `${helpUrl}/articles/8671539602317-What-is-Price-Impact`,
providingLiquidityInfo: `${helpUrl}/sections/20982919867021`, providingLiquidityInfo: `${helpUrl}/articles/30998269400333`,
recoveryPhraseHowToImport: `${helpUrl}/articles/11380692567949-How-to-import-a-recovery-phrase-into-the-Uniswap-Wallet`, recoveryPhraseHowToImport: `${helpUrl}/articles/11380692567949-How-to-import-a-recovery-phrase-into-the-Uniswap-Wallet`,
recoveryPhraseHowToFind: `${helpUrl}/articles/11306360177677-How-to-find-my-recovery-phrase-in-the-Uniswap-Wallet`, recoveryPhraseHowToFind: `${helpUrl}/articles/11306360177677-How-to-find-my-recovery-phrase-in-the-Uniswap-Wallet`,
recoveryPhraseForgotten: `${helpUrl}/articles/11306367118349`, recoveryPhraseForgotten: `${helpUrl}/articles/11306367118349`,
...@@ -61,7 +62,6 @@ export const uniswapUrls = { ...@@ -61,7 +62,6 @@ export const uniswapUrls = {
uniswapXFailure: `${helpUrl}/articles/17515489874189-Why-can-my-swap-not-be-filled-`, uniswapXFailure: `${helpUrl}/articles/17515489874189-Why-can-my-swap-not-be-filled-`,
unitagClaimPeriod: `${helpUrl}/articles/24009960408589`, unitagClaimPeriod: `${helpUrl}/articles/24009960408589`,
unsupportedTokenPolicy: `${helpUrl}/articles/18783694078989-Unsupported-Token-Policy`, unsupportedTokenPolicy: `${helpUrl}/articles/18783694078989-Unsupported-Token-Policy`,
addingV4Hooks: `${helpUrl}/articles/32402040565133`,
v4HooksInfo: `${helpUrl}/articles/30998263256717`, v4HooksInfo: `${helpUrl}/articles/30998263256717`,
v4RoutingInfo: `${helpUrl}/articles/32214043316109`, v4RoutingInfo: `${helpUrl}/articles/32214043316109`,
walletHelp: `${helpUrl}/categories/11301970439565-Uniswap-Wallet`, walletHelp: `${helpUrl}/categories/11301970439565-Uniswap-Wallet`,
......
import { UseQueryResult, skipToken, useQuery } from '@tanstack/react-query' import { UseQueryResult, skipToken, useQuery } from '@tanstack/react-query'
import { uniswapUrls } from 'uniswap/src/constants/urls' import { uniswapUrls } from 'uniswap/src/constants/urls'
import { TRADING_API_CACHE_KEY, createLpPosition } from 'uniswap/src/data/apiClients/tradingApi/TradingApiClient' import { TRADING_API_CACHE_KEY, createLpPosition } from 'uniswap/src/data/apiClients/tradingApi/TradingApiClient'
import { getTradeSettingsDeadline } from 'uniswap/src/data/apiClients/tradingApi/utils/getTradeSettingsDeadline'
import { UseQueryApiHelperHookArgs } from 'uniswap/src/data/apiClients/types' import { UseQueryApiHelperHookArgs } from 'uniswap/src/data/apiClients/types'
import { CreateLPPositionRequest, CreateLPPositionResponse } from 'uniswap/src/data/tradingApi/__generated__' import { CreateLPPositionRequest, CreateLPPositionResponse } from 'uniswap/src/data/tradingApi/__generated__'
export function useCreateLpPositionCalldataQuery({ export function useCreateLpPositionCalldataQuery({
params, params,
deadlineInMinutes,
...rest ...rest
}: UseQueryApiHelperHookArgs<CreateLPPositionRequest, CreateLPPositionResponse> & { }: UseQueryApiHelperHookArgs<
deadlineInMinutes: number | undefined CreateLPPositionRequest,
}): UseQueryResult<CreateLPPositionResponse> { CreateLPPositionResponse
>): UseQueryResult<CreateLPPositionResponse> {
const queryKey = [TRADING_API_CACHE_KEY, uniswapUrls.tradingApiPaths.createLp, params] const queryKey = [TRADING_API_CACHE_KEY, uniswapUrls.tradingApiPaths.createLp, params]
const deadline = getTradeSettingsDeadline(deadlineInMinutes)
const paramsWithDeadline = { ...params, deadline }
return useQuery<CreateLPPositionResponse>({ return useQuery<CreateLPPositionResponse>({
queryKey, queryKey,
queryFn: params queryFn: params ? async (): ReturnType<typeof createLpPosition> => await createLpPosition(params) : skipToken,
? async (): ReturnType<typeof createLpPosition> => await createLpPosition(paramsWithDeadline)
: skipToken,
...rest, ...rest,
}) })
} }
import { UseQueryResult, skipToken, useQuery } from '@tanstack/react-query' import { UseQueryResult, skipToken, useQuery } from '@tanstack/react-query'
import { uniswapUrls } from 'uniswap/src/constants/urls' import { uniswapUrls } from 'uniswap/src/constants/urls'
import { TRADING_API_CACHE_KEY, decreaseLpPosition } from 'uniswap/src/data/apiClients/tradingApi/TradingApiClient' import { TRADING_API_CACHE_KEY, decreaseLpPosition } from 'uniswap/src/data/apiClients/tradingApi/TradingApiClient'
import { getTradeSettingsDeadline } from 'uniswap/src/data/apiClients/tradingApi/utils/getTradeSettingsDeadline'
import { UseQueryApiHelperHookArgs } from 'uniswap/src/data/apiClients/types' import { UseQueryApiHelperHookArgs } from 'uniswap/src/data/apiClients/types'
import { DecreaseLPPositionRequest, DecreaseLPPositionResponse } from 'uniswap/src/data/tradingApi/__generated__' import { DecreaseLPPositionRequest, DecreaseLPPositionResponse } from 'uniswap/src/data/tradingApi/__generated__'
export function useDecreaseLpPositionCalldataQuery({ export function useDecreaseLpPositionCalldataQuery({
params, params,
deadlineInMinutes,
...rest ...rest
}: UseQueryApiHelperHookArgs<DecreaseLPPositionRequest, DecreaseLPPositionResponse> & { }: UseQueryApiHelperHookArgs<
deadlineInMinutes: number | undefined DecreaseLPPositionRequest,
}): UseQueryResult<DecreaseLPPositionResponse> { DecreaseLPPositionResponse
>): UseQueryResult<DecreaseLPPositionResponse> {
const queryKey = [TRADING_API_CACHE_KEY, uniswapUrls.tradingApiPaths.decreaseLp, params] const queryKey = [TRADING_API_CACHE_KEY, uniswapUrls.tradingApiPaths.decreaseLp, params]
const deadline = getTradeSettingsDeadline(deadlineInMinutes)
const paramsWithDeadline = { ...params, deadline }
return useQuery<DecreaseLPPositionResponse>({ return useQuery<DecreaseLPPositionResponse>({
queryKey, queryKey,
queryFn: params queryFn: params ? async (): ReturnType<typeof decreaseLpPosition> => await decreaseLpPosition(params) : skipToken,
? async (): ReturnType<typeof decreaseLpPosition> => await decreaseLpPosition(paramsWithDeadline)
: skipToken,
...rest, ...rest,
}) })
} }
import { UseQueryResult, skipToken, useQuery } from '@tanstack/react-query' import { UseQueryResult, skipToken, useQuery } from '@tanstack/react-query'
import { uniswapUrls } from 'uniswap/src/constants/urls' import { uniswapUrls } from 'uniswap/src/constants/urls'
import { TRADING_API_CACHE_KEY, increaseLpPosition } from 'uniswap/src/data/apiClients/tradingApi/TradingApiClient' import { TRADING_API_CACHE_KEY, increaseLpPosition } from 'uniswap/src/data/apiClients/tradingApi/TradingApiClient'
import { getTradeSettingsDeadline } from 'uniswap/src/data/apiClients/tradingApi/utils/getTradeSettingsDeadline'
import { UseQueryApiHelperHookArgs } from 'uniswap/src/data/apiClients/types' import { UseQueryApiHelperHookArgs } from 'uniswap/src/data/apiClients/types'
import { IncreaseLPPositionRequest, IncreaseLPPositionResponse } from 'uniswap/src/data/tradingApi/__generated__' import { IncreaseLPPositionRequest, IncreaseLPPositionResponse } from 'uniswap/src/data/tradingApi/__generated__'
export function useIncreaseLpPositionCalldataQuery({ export function useIncreaseLpPositionCalldataQuery({
params, params,
deadlineInMinutes,
...rest ...rest
}: UseQueryApiHelperHookArgs<IncreaseLPPositionRequest, IncreaseLPPositionResponse> & { }: UseQueryApiHelperHookArgs<
deadlineInMinutes: number | undefined IncreaseLPPositionRequest,
}): UseQueryResult<IncreaseLPPositionResponse> { IncreaseLPPositionResponse
>): UseQueryResult<IncreaseLPPositionResponse> {
const queryKey = [TRADING_API_CACHE_KEY, uniswapUrls.tradingApiPaths.increaseLp, params] const queryKey = [TRADING_API_CACHE_KEY, uniswapUrls.tradingApiPaths.increaseLp, params]
const deadline = getTradeSettingsDeadline(deadlineInMinutes)
const paramsWithDeadline = { ...params, deadline }
return useQuery<IncreaseLPPositionResponse>({ return useQuery<IncreaseLPPositionResponse>({
queryKey, queryKey,
queryFn: params queryFn: params ? async (): ReturnType<typeof increaseLpPosition> => await increaseLpPosition(params) : skipToken,
? async (): ReturnType<typeof increaseLpPosition> => await increaseLpPosition(paramsWithDeadline)
: skipToken,
...rest, ...rest,
}) })
} }
export const getTradeSettingsDeadline = (customDeadline?: number): number | undefined => {
// if custom deadline is set (in minutes), convert to unix timestamp format from now
const deadlineSeconds = (customDeadline ?? 0) * 60
const deadline = customDeadline ? Math.floor(Date.now() / 1000) + deadlineSeconds : undefined
return deadline
}
...@@ -319,7 +319,7 @@ export const UNIVERSE_CHAIN_INFO: Record<UniverseChainId, UniverseChainInfo> = { ...@@ -319,7 +319,7 @@ export const UNIVERSE_CHAIN_INFO: Record<UniverseChainId, UniverseChainInfo> = {
[RPCType.Fallback]: { http: ['https://rpc.ankr.com/optimism'] }, [RPCType.Fallback]: { http: ['https://rpc.ankr.com/optimism'] },
[RPCType.Interface]: { http: [`https://optimism-mainnet.infura.io/v3/${config.infuraKey}`] }, [RPCType.Interface]: { http: [`https://optimism-mainnet.infura.io/v3/${config.infuraKey}`] },
}, },
spotPriceStablecoinAmount: CurrencyAmount.fromRawAmount(USDC_OPTIMISM, 10_000e6), spotPriceStablecoinAmount: CurrencyAmount.fromRawAmount(DAI_OPTIMISM, 10_000e18),
stablecoins: [USDC_OPTIMISM, DAI_OPTIMISM], stablecoins: [USDC_OPTIMISM, DAI_OPTIMISM],
statusPage: 'https://optimism.io/status', statusPage: 'https://optimism.io/status',
supportsInterfaceClientSideRouting: true, supportsInterfaceClientSideRouting: true,
......
...@@ -192,6 +192,14 @@ type BridgeSwapTransactionResultProperties = BaseSwapTransactionResultProperties ...@@ -192,6 +192,14 @@ type BridgeSwapTransactionResultProperties = BaseSwapTransactionResultProperties
type FailedUniswapXOrderResultProperties = Omit<UniswapXTransactionResultProperties, 'hash'> type FailedUniswapXOrderResultProperties = Omit<UniswapXTransactionResultProperties, 'hash'>
type FailedClassicSwapResultProperties = Omit<ClassicSwapTransactionResultProperties, 'hash'> & {
hash: string | undefined
}
type FailedBridgeSwapResultProperties = Omit<BridgeSwapTransactionResultProperties, 'hash'> & {
hash: string | undefined
}
type TransferProperties = { type TransferProperties = {
chainId: UniverseChainId chainId: UniverseChainId
tokenAddress: Address tokenAddress: Address
...@@ -679,9 +687,9 @@ export type UniverseEventProperties = { ...@@ -679,9 +687,9 @@ export type UniverseEventProperties = {
| UniswapXTransactionResultProperties | UniswapXTransactionResultProperties
| BridgeSwapTransactionResultProperties | BridgeSwapTransactionResultProperties
[SwapEventName.SWAP_TRANSACTION_FAILED]: [SwapEventName.SWAP_TRANSACTION_FAILED]:
| ClassicSwapTransactionResultProperties | FailedClassicSwapResultProperties
| FailedUniswapXOrderResultProperties | FailedUniswapXOrderResultProperties
| BridgeSwapTransactionResultProperties | FailedBridgeSwapResultProperties
[SwapEventName.SWAP_DETAILS_EXPANDED]: ITraceContext | undefined [SwapEventName.SWAP_DETAILS_EXPANDED]: ITraceContext | undefined
[SwapEventName.SWAP_AUTOROUTER_VISUALIZATION_EXPANDED]: ITraceContext [SwapEventName.SWAP_AUTOROUTER_VISUALIZATION_EXPANDED]: ITraceContext
[SwapEventName.SWAP_QUOTE_RECEIVED]: { [SwapEventName.SWAP_QUOTE_RECEIVED]: {
......
...@@ -21,7 +21,10 @@ import { currencyIdToAddress } from 'uniswap/src/utils/currencyId' ...@@ -21,7 +21,10 @@ import { currencyIdToAddress } from 'uniswap/src/utils/currencyId'
type TokenWarningCardProps = { type TokenWarningCardProps = {
currencyInfo: Maybe<CurrencyInfo> currencyInfo: Maybe<CurrencyInfo>
tokenProtectionWarningOverride?: TokenProtectionWarning tokenProtectionWarningOverride?: TokenProtectionWarning
feePercentOverride?: number feeOnTransferOverride?: {
buyFeePercent?: number
sellFeePercent?: number
}
onPress?: () => void onPress?: () => void
headingTestId?: string headingTestId?: string
descriptionTestId?: string descriptionTestId?: string
...@@ -33,11 +36,15 @@ type TokenWarningCardProps = { ...@@ -33,11 +36,15 @@ type TokenWarningCardProps = {
function useTokenWarningOverrides( function useTokenWarningOverrides(
currencyInfo: Maybe<CurrencyInfo>, currencyInfo: Maybe<CurrencyInfo>,
tokenProtectionWarningOverride?: TokenProtectionWarning, tokenProtectionWarningOverride?: TokenProtectionWarning,
feePercentOverride?: number, feeOnTransferOverride?: {
buyFeePercent?: number
sellFeePercent?: number
},
): { severity: WarningSeverity; heading: string | null; description: string | null } { ): { severity: WarningSeverity; heading: string | null; description: string | null } {
const { t } = useTranslation() const { t } = useTranslation()
const { formatPercent } = useLocalizationContext() const { formatPercent } = useLocalizationContext()
const { heading: headingDefault, description: descriptionDefault } = useTokenWarningCardText(currencyInfo) const { heading: headingDefault, description: descriptionDefault } = useTokenWarningCardText(currencyInfo)
const { buyFeePercent, sellFeePercent } = getFeeOnTransfer(currencyInfo?.currency)
const severity = tokenProtectionWarningOverride const severity = tokenProtectionWarningOverride
? getSeverityFromTokenProtectionWarning(tokenProtectionWarningOverride) ? getSeverityFromTokenProtectionWarning(tokenProtectionWarningOverride)
...@@ -52,7 +59,8 @@ function useTokenWarningOverrides( ...@@ -52,7 +59,8 @@ function useTokenWarningOverrides(
t, t,
tokenProtectionWarning: tokenProtectionWarningOverride ?? TokenProtectionWarning.None, tokenProtectionWarning: tokenProtectionWarningOverride ?? TokenProtectionWarning.None,
tokenSymbol: currencyInfo?.currency.symbol, tokenSymbol: currencyInfo?.currency.symbol,
feePercent: feePercentOverride ?? getFeeOnTransfer(currencyInfo?.currency), buyFeePercent: feeOnTransferOverride?.buyFeePercent ?? buyFeePercent,
sellFeePercent: feeOnTransferOverride?.sellFeePercent ?? sellFeePercent,
formatPercent, formatPercent,
}) })
...@@ -65,7 +73,7 @@ function useTokenWarningOverrides( ...@@ -65,7 +73,7 @@ function useTokenWarningOverrides(
export function TokenWarningCard({ export function TokenWarningCard({
currencyInfo, currencyInfo,
tokenProtectionWarningOverride, tokenProtectionWarningOverride,
feePercentOverride, feeOnTransferOverride,
headingTestId, headingTestId,
descriptionTestId, descriptionTestId,
hideCtaIcon, hideCtaIcon,
...@@ -77,13 +85,14 @@ export function TokenWarningCard({ ...@@ -77,13 +85,14 @@ export function TokenWarningCard({
const { severity, heading, description } = useTokenWarningOverrides( const { severity, heading, description } = useTokenWarningOverrides(
currencyInfo, currencyInfo,
tokenProtectionWarningOverride, tokenProtectionWarningOverride,
feePercentOverride, feeOnTransferOverride,
) )
if (!currencyInfo || !severity || !description) { if (!currencyInfo || !severity || !description) {
return null return null
} }
const { buyFeePercent, sellFeePercent } = getFeeOnTransfer(currencyInfo?.currency)
const analyticsProperties = { const analyticsProperties = {
tokenSymbol: currencyInfo.currency.symbol, tokenSymbol: currencyInfo.currency.symbol,
chainId: currencyInfo.currency.chainId, chainId: currencyInfo.currency.chainId,
...@@ -91,7 +100,8 @@ export function TokenWarningCard({ ...@@ -91,7 +100,8 @@ export function TokenWarningCard({
warningSeverity: WarningSeverity[severity], warningSeverity: WarningSeverity[severity],
tokenProtectionWarning: tokenProtectionWarning:
TokenProtectionWarning[tokenProtectionWarningOverride ?? getTokenProtectionWarning(currencyInfo)], TokenProtectionWarning[tokenProtectionWarningOverride ?? getTokenProtectionWarning(currencyInfo)],
feeOnTransfer: feePercentOverride ?? getFeeOnTransfer(currencyInfo.currency), buyFeePercent: feeOnTransferOverride?.buyFeePercent ?? buyFeePercent,
sellFeePercent: feeOnTransferOverride?.sellFeePercent ?? sellFeePercent,
safetyInfo: currencyInfo.safetyInfo, safetyInfo: currencyInfo.safetyInfo,
} }
......
import { Percent } from '@uniswap/sdk-core'
import { TFunction } from 'i18next' import { TFunction } from 'i18next'
import { useState } from 'react' import { useState } from 'react'
import { Trans } from 'react-i18next' import { Trans } from 'react-i18next'
...@@ -41,7 +40,7 @@ interface TokenWarningProps { ...@@ -41,7 +40,7 @@ interface TokenWarningProps {
isInfoOnlyWarning?: boolean // if this is an informational-only warning. Hides the Reject button isInfoOnlyWarning?: boolean // if this is an informational-only warning. Hides the Reject button
shouldBeCombinedPlural?: boolean // some 2-token warnings will be combined into one plural modal (see `getShouldHaveCombinedPluralTreatment`) shouldBeCombinedPlural?: boolean // some 2-token warnings will be combined into one plural modal (see `getShouldHaveCombinedPluralTreatment`)
hasSecondWarning?: boolean // true if this is a 2-token warning with two separate warning screens hasSecondWarning?: boolean // true if this is a 2-token warning with two separate warning screens
feeOnTransferOverride?: { fee: Percent; feeType: 'buy' | 'sell' } // if defined, forces TokenWarningModal to display FOT content over any other warning content & overrides GQL fee info with TradingApi quote's fee info, which is more correct for dynamic FoT fees feeOnTransferOverride?: { buyFeePercent?: number; sellFeePercent?: number } // if defined, forces TokenWarningModal to display FOT content over any other warning content & overrides GQL fee info with TradingApi quote's fee info, which is more correct for dynamic FoT fees
} }
interface TokenWarningModalContentProps extends TokenWarningProps { interface TokenWarningModalContentProps extends TokenWarningProps {
...@@ -74,13 +73,12 @@ function TokenWarningModalContent({ ...@@ -74,13 +73,12 @@ function TokenWarningModalContent({
const { t } = useTranslation() const { t } = useTranslation()
const { formatPercent } = useLocalizationContext() const { formatPercent } = useLocalizationContext()
const tokenProtectionWarning = feeOnTransferOverride const tokenProtectionWarning =
? getFeeWarning(feeOnTransferOverride.fee) feeOnTransferOverride?.buyFeePercent || feeOnTransferOverride?.sellFeePercent
: getTokenProtectionWarning(currencyInfo0) ? getFeeWarning(Math.max(feeOnTransferOverride.buyFeePercent ?? 0, feeOnTransferOverride.sellFeePercent ?? 0))
: getTokenProtectionWarning(currencyInfo0)
const severity = getSeverityFromTokenProtectionWarning(tokenProtectionWarning) const severity = getSeverityFromTokenProtectionWarning(tokenProtectionWarning)
const feePercent = feeOnTransferOverride const { buyFeePercent, sellFeePercent } = getFeeOnTransfer(currencyInfo0.currency)
? parseFloat(feeOnTransferOverride.fee.toFixed())
: getFeeOnTransfer(currencyInfo0.currency)
const isFeeRelatedWarning = getIsFeeRelatedWarning(tokenProtectionWarning) const isFeeRelatedWarning = getIsFeeRelatedWarning(tokenProtectionWarning)
const tokenSymbol = currencyInfo0.currency.symbol const tokenSymbol = currencyInfo0.currency.symbol
const titleText = getModalHeaderText({ const titleText = getModalHeaderText({
...@@ -94,7 +92,8 @@ function TokenWarningModalContent({ ...@@ -94,7 +92,8 @@ function TokenWarningModalContent({
t, t,
tokenProtectionWarning, tokenProtectionWarning,
tokenSymbol, tokenSymbol,
feePercent, buyFeePercent: feeOnTransferOverride?.buyFeePercent ?? buyFeePercent,
sellFeePercent: feeOnTransferOverride?.sellFeePercent ?? sellFeePercent,
shouldHavePluralTreatment: shouldBeCombinedPlural, shouldHavePluralTreatment: shouldBeCombinedPlural,
formatPercent, formatPercent,
}) })
...@@ -136,7 +135,8 @@ function TokenWarningModalContent({ ...@@ -136,7 +135,8 @@ function TokenWarningModalContent({
tokenAddress1: currencyInfo1 && currencyIdToAddress(currencyInfo1.currencyId), tokenAddress1: currencyInfo1 && currencyIdToAddress(currencyInfo1.currencyId),
warningSeverity: WarningSeverity[severity], warningSeverity: WarningSeverity[severity],
tokenProtectionWarning: TokenProtectionWarning[tokenProtectionWarning], tokenProtectionWarning: TokenProtectionWarning[tokenProtectionWarning],
feeOnTransfer: feePercent, buyFeePercent: feeOnTransferOverride?.buyFeePercent ?? buyFeePercent,
sellFeePercent: feeOnTransferOverride?.sellFeePercent ?? sellFeePercent,
safetyInfo: currencyInfo0.safetyInfo, safetyInfo: currencyInfo0.safetyInfo,
...(showCheckbox && { dismissTokenWarningCheckbox: dontShowAgain }), ...(showCheckbox && { dismissTokenWarningCheckbox: dontShowAgain }),
} }
......
import { Currency, NativeCurrency, Percent, Token } from '@uniswap/sdk-core' import { Currency, NativeCurrency, Token } from '@uniswap/sdk-core'
import { WarningSeverity } from 'uniswap/src/components/modals/WarningModal/types' import { WarningSeverity } from 'uniswap/src/components/modals/WarningModal/types'
import { ProtectionResult } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { ProtectionResult } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { AttackType, CurrencyInfo, SafetyInfo, TokenList } from 'uniswap/src/features/dataApi/types' import { AttackType, CurrencyInfo, SafetyInfo, TokenList } from 'uniswap/src/features/dataApi/types'
...@@ -377,25 +377,25 @@ describe('safetyUtils', () => { ...@@ -377,25 +377,25 @@ describe('safetyUtils', () => {
describe('getFeeWarning', () => { describe('getFeeWarning', () => {
it.each([ it.each([
[new Percent(0, 100), TokenProtectionWarning.None, '0% -> None'], [0, TokenProtectionWarning.None, '0% -> None'],
// Low fees (0-5%) // Low fees (0-5%)
[new Percent(3, 1000), TokenProtectionWarning.FotLow, '0.3% -> FotLow'], [0.3, TokenProtectionWarning.FotLow, '0.3% -> FotLow'],
[new Percent(42, 1000), TokenProtectionWarning.FotLow, '4.2% -> FotLow'], [4.2, TokenProtectionWarning.FotLow, '4.2% -> FotLow'],
[new Percent(99, 10000), TokenProtectionWarning.FotLow, '0.99% -> FotLow'], [0.99, TokenProtectionWarning.FotLow, '0.99% -> FotLow'],
// High fees (5-80%) // High fees (5-80%)
[new Percent(5, 100), TokenProtectionWarning.FotHigh, '5% -> FotHigh'], [5, TokenProtectionWarning.FotHigh, '5% -> FotHigh'],
[new Percent(50, 100), TokenProtectionWarning.FotHigh, '50% -> FotHigh'], [50, TokenProtectionWarning.FotHigh, '50% -> FotHigh'],
[new Percent(799, 1000), TokenProtectionWarning.FotHigh, '79.9% -> FotHigh'], [79.9, TokenProtectionWarning.FotHigh, '79.9% -> FotHigh'],
[new Percent(5123, 10000), TokenProtectionWarning.FotHigh, '51.23% -> FotHigh'], [51.23, TokenProtectionWarning.FotHigh, '51.23% -> FotHigh'],
[new Percent(6789, 10000), TokenProtectionWarning.FotHigh, '67.89% -> FotHigh'], [67.89, TokenProtectionWarning.FotHigh, '67.89% -> FotHigh'],
// Very high fees (80-100%) // Very high fees (80-100%)
[new Percent(80, 100), TokenProtectionWarning.FotVeryHigh, '80% -> FotVeryHigh'], [80, TokenProtectionWarning.FotVeryHigh, '80% -> FotVeryHigh'],
[new Percent(90, 100), TokenProtectionWarning.FotVeryHigh, '90% -> FotVeryHigh'], [90, TokenProtectionWarning.FotVeryHigh, '90% -> FotVeryHigh'],
[new Percent(8456, 10000), TokenProtectionWarning.FotVeryHigh, '84.56% -> FotVeryHigh'], [84.56, TokenProtectionWarning.FotVeryHigh, '84.56% -> FotVeryHigh'],
[new Percent(999, 1000), TokenProtectionWarning.FotVeryHigh, '99.9% -> FotVeryHigh'], [99.9, TokenProtectionWarning.FotVeryHigh, '99.9% -> FotVeryHigh'],
// Honeypot (100%) // Honeypot (100%)
[new Percent(100, 100), TokenProtectionWarning.MaliciousHoneypot, '100% -> MaliciousHoneypot'], [100, TokenProtectionWarning.MaliciousHoneypot, '100% -> MaliciousHoneypot'],
[new Percent(10000, 10000), TokenProtectionWarning.MaliciousHoneypot, '100% -> MaliciousHoneypot'], [100, TokenProtectionWarning.MaliciousHoneypot, '100% -> MaliciousHoneypot'],
])('%s', (fee, expectedWarning, _) => { ])('%s', (fee, expectedWarning, _) => {
expect(getFeeWarning(fee)).toBe(expectedWarning) expect(getFeeWarning(fee)).toBe(expectedWarning)
}) })
......
...@@ -15,6 +15,7 @@ import { INSUFFICIENT_NATIVE_TOKEN_TEXT_VARIANT } from 'uniswap/src/features/tra ...@@ -15,6 +15,7 @@ import { INSUFFICIENT_NATIVE_TOKEN_TEXT_VARIANT } from 'uniswap/src/features/tra
import { useUSDCValue } from 'uniswap/src/features/transactions/swap/hooks/useUSDCPrice' import { useUSDCValue } from 'uniswap/src/features/transactions/swap/hooks/useUSDCPrice'
import { useNetworkColors } from 'uniswap/src/utils/colors' import { useNetworkColors } from 'uniswap/src/utils/colors'
import { NumberType } from 'utilities/src/format/types' import { NumberType } from 'utilities/src/format/types'
import { logger } from 'utilities/src/logger/logger'
export function useInsufficientNativeTokenWarning({ export function useInsufficientNativeTokenWarning({
flow, flow,
...@@ -59,6 +60,21 @@ export function useInsufficientNativeTokenWarning({ ...@@ -59,6 +60,21 @@ export function useInsufficientNativeTokenWarning({
return null return null
} }
if (!gasAmount) {
logger.warn(
'useInsufficientNativeTokenWarning',
'useInsufficientNativeTokenWarning',
'No `gasAmount` found when trying to render `InsufficientNativeTokenWarning`',
{
warning,
gasFee,
nativeCurrency,
nativeCurrencyInfo,
},
)
return null
}
const supportedChainId = toSupportedChainId(nativeCurrency?.chainId) const supportedChainId = toSupportedChainId(nativeCurrency?.chainId)
if (!supportedChainId) { if (!supportedChainId) {
...@@ -70,7 +86,7 @@ export function useInsufficientNativeTokenWarning({ ...@@ -70,7 +86,7 @@ export function useInsufficientNativeTokenWarning({
const modalOrTooltipMainMessage = ( const modalOrTooltipMainMessage = (
<Trans <Trans
components={{ components={{
// TODO(EXT-1269): move this to `value` once the bug in i18next is fixed. // TODO(WALL-3901): move this to `value` once the bug in i18next is fixed.
// We need to pass this as a `component` instead of a `value` because there seems to be a bug in i18next // We need to pass this as a `component` instead of a `value` because there seems to be a bug in i18next
// which causes the value `<$0.01` to be incorrectly escaped. // which causes the value `<$0.01` to be incorrectly escaped.
fiatTokenAmount: ( fiatTokenAmount: (
...@@ -82,8 +98,8 @@ export function useInsufficientNativeTokenWarning({ ...@@ -82,8 +98,8 @@ export function useInsufficientNativeTokenWarning({
i18nKey="transaction.warning.insufficientGas.modal.message" i18nKey="transaction.warning.insufficientGas.modal.message"
values={{ values={{
networkName, networkName,
tokenSymbol: nativeCurrency?.symbol, tokenSymbol: nativeCurrency.symbol,
tokenAmount: gasAmount?.toSignificant(2), tokenAmount: gasAmount.toSignificant(2),
}} }}
/> />
) )
......
...@@ -41,8 +41,8 @@ export function SwapReviewTokenWarningCard({ ...@@ -41,8 +41,8 @@ export function SwapReviewTokenWarningCard({
const feeOnTransferOverride = const feeOnTransferOverride =
showFeeSeverityWarning && tokenFeeInfo && feeType showFeeSeverityWarning && tokenFeeInfo && feeType
? { ? {
fee: tokenFeeInfo.fee, buyFeePercent: feeType === 'buy' ? feePercent : undefined,
feeType, sellFeePercent: feeType === 'sell' ? feePercent : undefined,
} }
: undefined : undefined
...@@ -59,7 +59,7 @@ export function SwapReviewTokenWarningCard({ ...@@ -59,7 +59,7 @@ export function SwapReviewTokenWarningCard({
hideCtaIcon hideCtaIcon
currencyInfo={currencyInfoToDisplay} currencyInfo={currencyInfoToDisplay}
tokenProtectionWarningOverride={tokenProtectionWarningToDisplay} tokenProtectionWarningOverride={tokenProtectionWarningToDisplay}
feePercentOverride={showFeeSeverityWarning ? feePercent : undefined} feeOnTransferOverride={feeOnTransferOverride}
checked={checked} checked={checked}
setChecked={setChecked} setChecked={setChecked}
onPress={onPress} onPress={onPress}
......
...@@ -22,7 +22,7 @@ export function getFeeSeverity(fee: Percent): { ...@@ -22,7 +22,7 @@ export function getFeeSeverity(fee: Percent): {
} { } {
// WarningSeverity for styling. Same logic as getTokenWarningSeverity but without non-fee-related cases. // WarningSeverity for styling. Same logic as getTokenWarningSeverity but without non-fee-related cases.
// If fee >= 5% then HIGH, else 0% < fee < 5% then MEDIUM, else NONE // If fee >= 5% then HIGH, else 0% < fee < 5% then MEDIUM, else NONE
const tokenProtectionWarning = getFeeWarning(fee) const tokenProtectionWarning = getFeeWarning(parseFloat(fee.toFixed()))
const severity = getSeverityFromTokenProtectionWarning(tokenProtectionWarning) const severity = getSeverityFromTokenProtectionWarning(tokenProtectionWarning)
return { severity, tokenProtectionWarning } return { severity, tokenProtectionWarning }
} }
......
...@@ -15,3 +15,11 @@ export const getShouldResetExactAmountToken = ( ...@@ -15,3 +15,11 @@ export const getShouldResetExactAmountToken = (
return shouldResetInputAmount || shouldResetOutputAmount return shouldResetInputAmount || shouldResetOutputAmount
} }
export const getTradeSettingsDeadline = (customDeadline?: number): number | undefined => {
// if custom deadline is set (in minutes), convert to unix timestamp format from now
const deadlineSeconds = (customDeadline ?? 0) * 60
const deadline = customDeadline ? Math.floor(Date.now() / 1000) + deadlineSeconds : undefined
return deadline
}
...@@ -3,7 +3,6 @@ import { providers } from 'ethers/lib/ethers' ...@@ -3,7 +3,6 @@ import { providers } from 'ethers/lib/ethers'
import { useEffect, useMemo, useRef } from 'react' import { useEffect, useMemo, useRef } from 'react'
import { WithV4Flag } from 'uniswap/src/data/apiClients/tradingApi/TradingApiClient' import { WithV4Flag } from 'uniswap/src/data/apiClients/tradingApi/TradingApiClient'
import { useTradingApiSwapQuery } from 'uniswap/src/data/apiClients/tradingApi/useTradingApiSwapQuery' import { useTradingApiSwapQuery } from 'uniswap/src/data/apiClients/tradingApi/useTradingApiSwapQuery'
import { getTradeSettingsDeadline } from 'uniswap/src/data/apiClients/tradingApi/utils/getTradeSettingsDeadline'
import { import {
CreateSwapRequest, CreateSwapRequest,
NullablePermit, NullablePermit,
...@@ -19,6 +18,7 @@ import { useDynamicConfigValue, useFeatureFlag } from 'uniswap/src/features/gati ...@@ -19,6 +18,7 @@ import { useDynamicConfigValue, useFeatureFlag } from 'uniswap/src/features/gati
import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { getBaseTradeAnalyticsPropertiesFromSwapInfo } from 'uniswap/src/features/transactions/swap/analytics' import { getBaseTradeAnalyticsPropertiesFromSwapInfo } from 'uniswap/src/features/transactions/swap/analytics'
import { getTradeSettingsDeadline } from 'uniswap/src/features/transactions/swap/form/utils'
import { usePermit2SignatureWithData } from 'uniswap/src/features/transactions/swap/hooks/usePermit2Signature' import { usePermit2SignatureWithData } from 'uniswap/src/features/transactions/swap/hooks/usePermit2Signature'
import { useWrapTransactionRequest } from 'uniswap/src/features/transactions/swap/hooks/useWrapTransactionRequest' import { useWrapTransactionRequest } from 'uniswap/src/features/transactions/swap/hooks/useWrapTransactionRequest'
import { useSwapSettingsContext } from 'uniswap/src/features/transactions/swap/settings/contexts/SwapSettingsContext' import { useSwapSettingsContext } from 'uniswap/src/features/transactions/swap/settings/contexts/SwapSettingsContext'
......
...@@ -8,7 +8,6 @@ import { ...@@ -8,7 +8,6 @@ import {
USDC_ASTROCHAIN_SEPOLIA, USDC_ASTROCHAIN_SEPOLIA,
USDC_AVALANCHE, USDC_AVALANCHE,
USDC_BASE, USDC_BASE,
USDC_BNB,
USDC_CELO, USDC_CELO,
USDC_OPTIMISM, USDC_OPTIMISM,
USDC_POLYGON, USDC_POLYGON,
...@@ -16,6 +15,7 @@ import { ...@@ -16,6 +15,7 @@ import {
USDC_WORLD_CHAIN, USDC_WORLD_CHAIN,
USDC_ZKSYNC, USDC_ZKSYNC,
USDC_ZORA, USDC_ZORA,
USDT_BNB,
} from 'uniswap/src/constants/tokens' } from 'uniswap/src/constants/tokens'
import { UniverseChainId } from 'uniswap/src/features/chains/types' import { UniverseChainId } from 'uniswap/src/features/chains/types'
import { useTrade } from 'uniswap/src/features/transactions/swap/hooks/useTrade' import { useTrade } from 'uniswap/src/features/transactions/swap/hooks/useTrade'
...@@ -29,7 +29,7 @@ export const STABLECOIN_AMOUNT_OUT: { [chainId: number]: CurrencyAmount<Token> } ...@@ -29,7 +29,7 @@ export const STABLECOIN_AMOUNT_OUT: { [chainId: number]: CurrencyAmount<Token> }
[UniverseChainId.Sepolia]: CurrencyAmount.fromRawAmount(USDC_SEPOLIA, 100_000e6), [UniverseChainId.Sepolia]: CurrencyAmount.fromRawAmount(USDC_SEPOLIA, 100_000e6),
[UniverseChainId.ArbitrumOne]: CurrencyAmount.fromRawAmount(USDC_ARBITRUM, 10_000e6), [UniverseChainId.ArbitrumOne]: CurrencyAmount.fromRawAmount(USDC_ARBITRUM, 10_000e6),
[UniverseChainId.Base]: CurrencyAmount.fromRawAmount(USDC_BASE, 10_000e6), [UniverseChainId.Base]: CurrencyAmount.fromRawAmount(USDC_BASE, 10_000e6),
[UniverseChainId.Bnb]: CurrencyAmount.fromRawAmount(USDC_BNB, 10_000e18), [UniverseChainId.Bnb]: CurrencyAmount.fromRawAmount(USDT_BNB, 10_000e18),
[UniverseChainId.Polygon]: CurrencyAmount.fromRawAmount(USDC_POLYGON, 10_000e6), [UniverseChainId.Polygon]: CurrencyAmount.fromRawAmount(USDC_POLYGON, 10_000e6),
[UniverseChainId.Optimism]: CurrencyAmount.fromRawAmount(USDC_OPTIMISM, 10_000e6), [UniverseChainId.Optimism]: CurrencyAmount.fromRawAmount(USDC_OPTIMISM, 10_000e6),
[UniverseChainId.Blast]: CurrencyAmount.fromRawAmount(USDB_BLAST, 10_000e18), [UniverseChainId.Blast]: CurrencyAmount.fromRawAmount(USDB_BLAST, 10_000e18),
......
...@@ -32,7 +32,13 @@ export function FeeOnTransferWarning({ ...@@ -32,7 +32,13 @@ export function FeeOnTransferWarning({
const { tokenProtectionWarning } = getFeeSeverity(feeInfo.fee) const { tokenProtectionWarning } = getFeeSeverity(feeInfo.fee)
const title = getModalHeaderText({ t, tokenProtectionWarning, tokenSymbol0: tokenSymbol }) ?? '' const title = getModalHeaderText({ t, tokenProtectionWarning, tokenSymbol0: tokenSymbol }) ?? ''
const subtitle = const subtitle =
getModalSubtitleTokenWarningText({ t, tokenProtectionWarning, tokenSymbol, formattedFeePercent }) ?? '' getModalSubtitleTokenWarningText({
t,
tokenProtectionWarning,
tokenSymbol,
formattedBuyFeePercent: feeType === 'buy' ? formattedFeePercent : undefined,
formattedSellFeePercent: feeType === 'sell' ? formattedFeePercent : undefined,
}) ?? ''
if (isInterface) { if (isInterface) {
return ( return (
...@@ -77,8 +83,8 @@ export function FeeOnTransferWarning({ ...@@ -77,8 +83,8 @@ export function FeeOnTransferWarning({
isVisible={showModal} isVisible={showModal}
currencyInfo0={feeInfo.currencyInfo} currencyInfo0={feeInfo.currencyInfo}
feeOnTransferOverride={{ feeOnTransferOverride={{
fee: feeInfo.fee, buyFeePercent: feeType === 'buy' ? feePercent : undefined,
feeType, sellFeePercent: feeType === 'sell' ? feePercent : undefined,
}} }}
closeModalOnly={onClose} closeModalOnly={onClose}
onAcknowledge={onClose} onAcknowledge={onClose}
......
...@@ -197,6 +197,15 @@ export function computeRoutes( ...@@ -197,6 +197,15 @@ export function computeRoutes(
} }
}) })
} catch (e) { } catch (e) {
logger.error(e, {
tags: { file: 'tradingApi.ts', function: 'computeRoutes' },
extra: {
input: parsedCurrencyIn.address,
output: parsedCurrencyOut.address,
inputChainId: parsedCurrencyIn.chainId,
outputChainId: parsedCurrencyOut.chainId,
},
})
return undefined return undefined
} }
} }
...@@ -206,6 +215,11 @@ function parseTokenApi(token: TradingApiTokenInRoute): Token { ...@@ -206,6 +215,11 @@ function parseTokenApi(token: TradingApiTokenInRoute): Token {
if (!chainId || !address || !decimals || !symbol) { if (!chainId || !address || !decimals || !symbol) {
throw new Error('Expected token to have chainId, address, decimals, and symbol') throw new Error('Expected token to have chainId, address, decimals, and symbol')
} }
if (address === NATIVE_ADDRESS_FOR_TRADING_API) {
throw new Error('Cannot parse native currency as an erc20 Token')
}
return new Token( return new Token(
chainId, chainId,
address, address,
...@@ -228,8 +242,16 @@ function parseV4PoolApi({ ...@@ -228,8 +242,16 @@ function parseV4PoolApi({
tokenIn, tokenIn,
tokenOut, tokenOut,
}: TradingApiV4PoolInRoute): V4Pool { }: TradingApiV4PoolInRoute): V4Pool {
const currencyIn = parseTokenApi(tokenIn) if (!tokenIn.address || !tokenOut.address || !tokenIn.chainId || !tokenOut.chainId) {
const currencyOut = parseTokenApi(tokenOut) throw new Error('Expected V4 route to have defined addresses and chainIds')
}
const inputIsNative = tokenIn.address === NATIVE_ADDRESS_FOR_TRADING_API
const outputIsNative = tokenOut.address === NATIVE_ADDRESS_FOR_TRADING_API
// Unlike lower protocol versions, v4 routes can involve unwrapped native tokens.
const currencyIn = inputIsNative ? NativeCurrency.onChain(tokenIn.chainId) : parseTokenApi(tokenIn)
const currencyOut = outputIsNative ? NativeCurrency.onChain(tokenOut.chainId) : parseTokenApi(tokenOut)
return new V4Pool( return new V4Pool(
currencyIn, currencyIn,
......
...@@ -149,6 +149,7 @@ export type FinalizedTransactionDetails = TransactionDetails & ...@@ -149,6 +149,7 @@ export type FinalizedTransactionDetails = TransactionDetails &
export type TransactionOptions = { export type TransactionOptions = {
request: providers.TransactionRequest request: providers.TransactionRequest
submittedTimestampMs?: number
timeoutTimestampMs?: number timeoutTimestampMs?: number
submitViaPrivateRpc?: boolean submitViaPrivateRpc?: boolean
} }
......
...@@ -837,8 +837,8 @@ ...@@ -837,8 +837,8 @@
"fee.tier.create": "Create fee tier", "fee.tier.create": "Create fee tier",
"fee.tier.create.button": "Create new fee tier", "fee.tier.create.button": "Create new fee tier",
"fee.tier.create.description": "Creating a new tier will initialize in a new pool and result in higher than usual network costs.", "fee.tier.create.description": "Creating a new tier will initialize in a new pool and result in higher than usual network costs.",
"fee.tier.description": "The amount earned providing liquidity. Choose an amount that suits your risk tolerance and strategy.", "fee.tier.description": "The amount earned facilitating trades. Choose an amount that suits your risk tolerance and strategy.",
"fee.tier.description.v2": "The amount earned providing liquidity. All v2 pools have fixed 0.3% fees. For more options, provide liquidity on v4.", "fee.tier.description.v2": "The amount earned facilitating trades. All v2 pools have fixed 0.3% fees. For more options, provide liquidity on v4.",
"fee.tier.dynamic": "Dynamic fee tier", "fee.tier.dynamic": "Dynamic fee tier",
"fee.tier.dynamic.create": "Creating dynamic fee tier", "fee.tier.dynamic.create": "Creating dynamic fee tier",
"fee.tier.dynamic.create.info": "You are about to create a pool with a dynamic fee tier. Before proceeding, please ensure that the selected hook supports dynamic fees.", "fee.tier.dynamic.create.info": "You are about to create a pool with a dynamic fee tier. Before proceeding, please ensure that the selected hook supports dynamic fees.",
...@@ -1996,11 +1996,11 @@ ...@@ -1996,11 +1996,11 @@
"token.safety.warning.fotLow.title": "Fee detected", "token.safety.warning.fotLow.title": "Fee detected",
"token.safety.warning.fotVeryHigh.title": "Very high fee detected", "token.safety.warning.fotVeryHigh.title": "Very high fee detected",
"token.safety.warning.honeypot.message": "{{tokenSymbol}} has been flagged as unsellable. Swapping this token may result in a loss of your funds.", "token.safety.warning.honeypot.message": "{{tokenSymbol}} has been flagged as unsellable. Swapping this token may result in a loss of your funds.",
"token.safety.warning.honeypot.title": "Honeypot detected", "token.safety.warning.honeypot.title": "100% sell fee detected",
"token.safety.warning.impersonator": "Impersonates another token", "token.safety.warning.impersonator": "Impersonates another token",
"token.safety.warning.impersonator.title": "Impersonator token detected", "token.safety.warning.impersonator.title": "Impersonator token detected",
"token.safety.warning.malicious.general.message": "{{tokenSymbol}} has been flagged as malicious.", "token.safety.warning.malicious.general.message": "{{tokenSymbol}} has been flagged as malicious by Blockaid.",
"token.safety.warning.malicious.impersonator.message": "{{tokenSymbol}} has been flagged as an attempt to copy a different token. It may not be the token you are looking to swap.", "token.safety.warning.malicious.impersonator.message": "{{tokenSymbol}} has been flagged by Blockaid for attempting to copy a different token. It may not be the token you are looking to swap.",
"token.safety.warning.malicious.impersonator.message.short": "{{tokenSymbol}} may not be the token you are looking to swap.", "token.safety.warning.malicious.impersonator.message.short": "{{tokenSymbol}} may not be the token you are looking to swap.",
"token.safety.warning.malicious.title": "Malicious token detected", "token.safety.warning.malicious.title": "Malicious token detected",
"token.safety.warning.mayResultInLoss": "Swapping it may result in a loss of funds.", "token.safety.warning.mayResultInLoss": "Swapping it may result in a loss of funds.",
...@@ -2010,13 +2010,17 @@ ...@@ -2010,13 +2010,17 @@
"token.safety.warning.medium.heading.default_other_also": "These tokens also aren’t traded on leading U.S. centralized exchanges.", "token.safety.warning.medium.heading.default_other_also": "These tokens also aren’t traded on leading U.S. centralized exchanges.",
"token.safety.warning.medium.heading.named": "{{tokenSymbol}} isn’t traded on leading U.S. centralized exchanges.", "token.safety.warning.medium.heading.named": "{{tokenSymbol}} isn’t traded on leading U.S. centralized exchanges.",
"token.safety.warning.notListedOnExchanges": "Not listed on leading U.S. exchanges", "token.safety.warning.notListedOnExchanges": "Not listed on leading U.S. exchanges",
"token.safety.warning.spam.message": "{{tokenSymbol}} has been flagged as a potential spam token.", "token.safety.warning.sellFee100.message": "{{ tokenSymbol }} has been flagged as unsellable.",
"token.safety.warning.sellFee100.title": "100% sell fee detected",
"token.safety.warning.spam.message": "{{tokenSymbol}} has been flagged as spam by Blockaid.",
"token.safety.warning.spam.title": "Spam token detected", "token.safety.warning.spam.title": "Spam token detected",
"token.safety.warning.spamsUsers": "Spams users", "token.safety.warning.spamsUsers": "Spams users",
"token.safety.warning.strong.heading.default_one": "This token isn’t traded on leading U.S. centralized exchanges or frequently swapped on Uniswap.", "token.safety.warning.strong.heading.default_one": "This token isn’t traded on leading U.S. centralized exchanges or frequently swapped on Uniswap.",
"token.safety.warning.strong.heading.default_other": "These tokens aren’t traded on leading U.S. centralized exchanges or frequently swapped on Uniswap.", "token.safety.warning.strong.heading.default_other": "These tokens aren’t traded on leading U.S. centralized exchanges or frequently swapped on Uniswap.",
"token.safety.warning.strong.heading.named": "{{tokenSymbol}} isn’t traded on leading U.S. centralized exchanges or frequently swapped on Uniswap.", "token.safety.warning.strong.heading.named": "{{tokenSymbol}} isn’t traded on leading U.S. centralized exchanges or frequently swapped on Uniswap.",
"token.safety.warning.tokenChargesFee.percent.message": "{{tokenSymbol}} charges a {{feePercent}} fee when bought or sold.", "token.safety.warning.tokenChargesFee.both.message": "{{tokenSymbol}} charges a {{buyFeePercent}} fee when bought and {{sellFeePercent}} when sold.",
"token.safety.warning.tokenChargesFee.buy.message": "{{tokenSymbol}} charges a {{feePercent}} fee when bought.",
"token.safety.warning.tokenChargesFee.sell.message": "{{tokenSymbol}} charges a {{feePercent}} fee when sold.",
"token.safetyLevel.blocked.header": "Not available", "token.safetyLevel.blocked.header": "Not available",
"token.safetyLevel.blocked.message": "You can’t trade this token using the Uniswap Wallet.", "token.safetyLevel.blocked.message": "You can’t trade this token using the Uniswap Wallet.",
"token.safetyLevel.medium.header": "Caution", "token.safetyLevel.medium.header": "Caution",
......
...@@ -177,7 +177,6 @@ ...@@ -177,7 +177,6 @@
"common.approvePending": "Godkendelse afventer...", "common.approvePending": "Godkendelse afventer...",
"common.approveSpend": "Godkend {{symbol}} udgifter", "common.approveSpend": "Godkend {{symbol}} udgifter",
"common.approving": "Godkender", "common.approving": "Godkender",
"common.areYouSure": "Er du sikker?",
"common.automatic": "Auto", "common.automatic": "Auto",
"common.availableIn": "Uniswap tilgængelig i: <locale />", "common.availableIn": "Uniswap tilgængelig i: <locale />",
"common.availableOnIOSAndroid": "Tilgængelig på iOS og Android", "common.availableOnIOSAndroid": "Tilgængelig på iOS og Android",
...@@ -650,7 +649,6 @@ ...@@ -650,7 +649,6 @@
"common.unwrap.failed": "Udpakningen mislykkedes", "common.unwrap.failed": "Udpakningen mislykkedes",
"common.unwrapped": "Udpakket", "common.unwrapped": "Udpakket",
"common.unwrapping": "Udpakning", "common.unwrapping": "Udpakning",
"common.version": "Version",
"common.view.profile": "Se profil", "common.view.profile": "Se profil",
"common.viewOnBlockExplorer": "Se på Block Explorer", "common.viewOnBlockExplorer": "Se på Block Explorer",
"common.viewOnExplorer": "Se på Explorer", "common.viewOnExplorer": "Se på Explorer",
...@@ -837,8 +835,8 @@ ...@@ -837,8 +835,8 @@
"fee.tier.create": "Opret gebyrniveau", "fee.tier.create": "Opret gebyrniveau",
"fee.tier.create.button": "Opret et nyt gebyrniveau", "fee.tier.create.button": "Opret et nyt gebyrniveau",
"fee.tier.create.description": "Oprettelse af et nyt niveau vil initialiseres i en ny pulje og resultere i højere netværksomkostninger end normalt.", "fee.tier.create.description": "Oprettelse af et nyt niveau vil initialiseres i en ny pulje og resultere i højere netværksomkostninger end normalt.",
"fee.tier.description": "Beløbet tjent for at lette handler. Vælg et beløb, der passer til din risikotolerance og strategi.", "fee.tier.description": "Det tjente beløb giver likviditet. Vælg et beløb, der passer til din risikotolerance og strategi.",
"fee.tier.description.v2": "Beløbet tjent for at lette handler. Alle v2-puljer har faste gebyrer på 0,3 %. For flere muligheder, giv likviditet på v4.", "fee.tier.description.v2": "Det tjente beløb giver likviditet. Alle v2-puljer har faste gebyrer på 0,3 %. For flere muligheder, giv likviditet på v4.",
"fee.tier.dynamic": "Dynamisk gebyrniveau", "fee.tier.dynamic": "Dynamisk gebyrniveau",
"fee.tier.dynamic.create": "Oprettelse af dynamisk gebyrniveau", "fee.tier.dynamic.create": "Oprettelse af dynamisk gebyrniveau",
"fee.tier.dynamic.create.info": "Du er ved at oprette en pulje med et dynamisk gebyrniveau. Før du fortsætter, skal du sikre dig, at den valgte krog understøtter dynamiske gebyrer.", "fee.tier.dynamic.create.info": "Du er ved at oprette en pulje med et dynamisk gebyrniveau. Før du fortsætter, skal du sikre dig, at den valgte krog understøtter dynamiske gebyrer.",
...@@ -1492,7 +1490,6 @@ ...@@ -1492,7 +1490,6 @@
"position.provide.liquidityDescription": "Tilvejebringelse af likviditet i hele rækken sikrer kontinuerlig markedsdeltagelse på tværs af alle mulige priser, hvilket tilbyder enkelhed, men med potentiale for højere permanente tab.", "position.provide.liquidityDescription": "Tilvejebringelse af likviditet i hele rækken sikrer kontinuerlig markedsdeltagelse på tværs af alle mulige priser, hvilket tilbyder enkelhed, men med potentiale for højere permanente tab.",
"position.provide.liquidityDescription.custom": "Brugerdefineret sortiment giver dig mulighed for at koncentrere din likviditet inden for specifikke prisgrænser, hvilket øger kapitaleffektiviteten og gebyrindtjeningen, men kræver mere aktiv styring.", "position.provide.liquidityDescription.custom": "Brugerdefineret sortiment giver dig mulighed for at koncentrere din likviditet inden for specifikke prisgrænser, hvilket øger kapitaleffektiviteten og gebyrindtjeningen, men kræver mere aktiv styring.",
"position.removeHook": "Fjern krogen", "position.removeHook": "Fjern krogen",
"position.resetDescription": "Dine tokens, pris og områdevalg nulstilles.",
"position.setRange": "Sæt prisinterval", "position.setRange": "Sæt prisinterval",
"position.setRange.inputsBelow": "Brug nedenstående input til at indstille dit område.", "position.setRange.inputsBelow": "Brug nedenstående input til at indstille dit område.",
"position.step.deposit": "Indtast indbetalingsbeløb", "position.step.deposit": "Indtast indbetalingsbeløb",
...@@ -1540,7 +1537,6 @@ ...@@ -1540,7 +1537,6 @@
"revoke.failed.message": "Dette giver Uniswap-protokollen adgang til dit token til handel.", "revoke.failed.message": "Dette giver Uniswap-protokollen adgang til dit token til handel.",
"routing.aggregateLiquidity": "Når det er tilgængeligt, samler likviditetskilder til bedre priser og gasfri swaps.", "routing.aggregateLiquidity": "Når det er tilgængeligt, samler likviditetskilder til bedre priser og gasfri swaps.",
"routing.cheapest": "Uniswap-klienten vælger den billigste handelsoption med hensyn til pris og netværksomkostninger.", "routing.cheapest": "Uniswap-klienten vælger den billigste handelsoption med hensyn til pris og netværksomkostninger.",
"routing.cheapest.v4": "Uniswap-klienten vælger den optimale handelsmulighed med hensyn til pris og netværksomkostninger.",
"scantastic.code.expired": "Udløbet", "scantastic.code.expired": "Udløbet",
"scantastic.code.subtitle": "Indtast denne kode i Uniswap-udvidelsen. Din gendannelsessætning bliver sikkert krypteret og overført.", "scantastic.code.subtitle": "Indtast denne kode i Uniswap-udvidelsen. Din gendannelsessætning bliver sikkert krypteret og overført.",
"scantastic.code.timeRemaining.shorthand.hours": "Ny kode i {{hours}}h {{minutes}}m {{seconds}}s", "scantastic.code.timeRemaining.shorthand.hours": "Ny kode i {{hours}}h {{minutes}}m {{seconds}}s",
...@@ -1761,7 +1757,6 @@ ...@@ -1761,7 +1757,6 @@
"swap.approveInWallet": "Godkend i din tegnebog", "swap.approveInWallet": "Godkend i din tegnebog",
"swap.balance.amount": "Saldo: {{amount}}", "swap.balance.amount": "Saldo: {{amount}}",
"swap.bestRoute.cost": "Bedste pris rute koster ~{{gasPrice}} i gas. ", "swap.bestRoute.cost": "Bedste pris rute koster ~{{gasPrice}} i gas. ",
"swap.bestRoute.cost.v4": "Optimale ruteomkostninger ~{{gasPrice}} i gas. ",
"swap.bridging.estimatedTime": "Est. tid", "swap.bridging.estimatedTime": "Est. tid",
"swap.bridging.title": "Udveksling på tværs af netværk", "swap.bridging.title": "Udveksling på tværs af netværk",
"swap.bridging.warning.description": "Du skifter fra {{fromNetwork}} til {{toNetwork}}. Dette er også kendt som \"bridging\", som flytter dine tokens fra et netværk til et andet.", "swap.bridging.warning.description": "Du skifter fra {{fromNetwork}} til {{toNetwork}}. Dette er også kendt som \"bridging\", som flytter dine tokens fra et netværk til et andet.",
...@@ -1848,7 +1843,6 @@ ...@@ -1848,7 +1843,6 @@
"swap.settings.protection.subtitle.unavailable": "Ikke tilgængelig på {{chainName}}", "swap.settings.protection.subtitle.unavailable": "Ikke tilgængelig på {{chainName}}",
"swap.settings.protection.title": "Swap beskyttelse", "swap.settings.protection.title": "Swap beskyttelse",
"swap.settings.routingPreference.option.default.description": "Uniswap-klienten vælger den billigste handelsoption med hensyn til pris og netværksomkostninger.", "swap.settings.routingPreference.option.default.description": "Uniswap-klienten vælger den billigste handelsoption med hensyn til pris og netværksomkostninger.",
"swap.settings.routingPreference.option.default.description.v4": "Uniswap-klienten vælger den optimale handelsmulighed med hensyn til pris og netværksomkostninger.",
"swap.settings.routingPreference.option.v2.title": "v2 puljer", "swap.settings.routingPreference.option.v2.title": "v2 puljer",
"swap.settings.routingPreference.option.v3.title": "v3 puljer", "swap.settings.routingPreference.option.v3.title": "v3 puljer",
"swap.settings.routingPreference.option.v4.title": "v4 puljer", "swap.settings.routingPreference.option.v4.title": "v4 puljer",
...@@ -1877,7 +1871,6 @@ ...@@ -1877,7 +1871,6 @@
"swap.taxTooltip.tokenSelected": "{{tokenSymbol}} gebyrer tillader ikke nøjagtige nøjagtige output. Brug i stedet feltet \"Sælg\".", "swap.taxTooltip.tokenSelected": "{{tokenSymbol}} gebyrer tillader ikke nøjagtige nøjagtige output. Brug i stedet feltet \"Sælg\".",
"swap.tokenOwnFees": "Nogle tokens tager et gebyr, når de købes eller sælges, som fastsættes af tokenudstederen. Uniswap modtager ingen af disse gebyrer.", "swap.tokenOwnFees": "Nogle tokens tager et gebyr, når de købes eller sælges, som fastsættes af tokenudstederen. Uniswap modtager ingen af disse gebyrer.",
"swap.total": "Total", "swap.total": "Total",
"swap.tradeRoutes": "Handelsruter",
"swap.transaction.deadline": "Transaktionsfrist", "swap.transaction.deadline": "Transaktionsfrist",
"swap.transaction.revertAfter": "Din transaktion vil vende tilbage, hvis den er afventende i mere end denne periode.", "swap.transaction.revertAfter": "Din transaktion vil vende tilbage, hvis den er afventende i mere end denne periode.",
"swap.unsupportedAssets.readMore": "Læs mere om ikke-understøttede aktiver", "swap.unsupportedAssets.readMore": "Læs mere om ikke-understøttede aktiver",
...@@ -1916,6 +1909,7 @@ ...@@ -1916,6 +1909,7 @@
"swap.warning.rateLimit.title": "Satsgrænsen er overskredet", "swap.warning.rateLimit.title": "Satsgrænsen er overskredet",
"swap.warning.router.message": "Du har muligvis mistet forbindelsen, eller netværket kan være nede. Hvis problemet fortsætter, prøv venligst igen senere.", "swap.warning.router.message": "Du har muligvis mistet forbindelsen, eller netværket kan være nede. Hvis problemet fortsætter, prøv venligst igen senere.",
"swap.warning.router.title": "Denne handel kan ikke gennemføres lige nu", "swap.warning.router.title": "Denne handel kan ikke gennemføres lige nu",
"swap.warning.tokenBlocked.button": "{{tokenSymbol}} er blokeret",
"swap.warning.uniswapFee.message.default": "Gebyrer pålægges for at sikre den bedste oplevelse med Uniswap. Der er intet gebyr forbundet med denne swap.", "swap.warning.uniswapFee.message.default": "Gebyrer pålægges for at sikre den bedste oplevelse med Uniswap. Der er intet gebyr forbundet med denne swap.",
"swap.warning.uniswapFee.message.included": "Gebyrer pålægges for at sikre den bedste oplevelse med Uniswap og er allerede indregnet i dette tilbud.", "swap.warning.uniswapFee.message.included": "Gebyrer pålægges for at sikre den bedste oplevelse med Uniswap og er allerede indregnet i dette tilbud.",
"swap.warning.uniswapFee.title": "Byttegebyr", "swap.warning.uniswapFee.title": "Byttegebyr",
...@@ -1996,11 +1990,11 @@ ...@@ -1996,11 +1990,11 @@
"token.safety.warning.fotLow.title": "Gebyr opdaget", "token.safety.warning.fotLow.title": "Gebyr opdaget",
"token.safety.warning.fotVeryHigh.title": "Meget højt gebyr registreret", "token.safety.warning.fotVeryHigh.title": "Meget højt gebyr registreret",
"token.safety.warning.honeypot.message": "{{tokenSymbol}} er blevet markeret som usælgelig. At bytte dette token kan resultere i et tab af dine midler.", "token.safety.warning.honeypot.message": "{{tokenSymbol}} er blevet markeret som usælgelig. At bytte dette token kan resultere i et tab af dine midler.",
"token.safety.warning.honeypot.title": "Honeypot opdaget", "token.safety.warning.honeypot.title": "100 % salgsgebyr registreret",
"token.safety.warning.impersonator": "Efterligner et andet token", "token.safety.warning.impersonator": "Efterligner et andet token",
"token.safety.warning.impersonator.title": "Imitator-token fundet", "token.safety.warning.impersonator.title": "Imitator-token fundet",
"token.safety.warning.malicious.general.message": "{{tokenSymbol}} er blevet markeret som ondsindet.", "token.safety.warning.malicious.general.message": "{{tokenSymbol}} er blevet markeret som ondsindet af Blockaid.",
"token.safety.warning.malicious.impersonator.message": "{{tokenSymbol}} er blevet markeret som et forsøg på at kopiere et andet token. Det er muligvis ikke det token, du ønsker at bytte.", "token.safety.warning.malicious.impersonator.message": "{{tokenSymbol}} er blevet markeret af Blockaid for at forsøge at kopiere et andet token. Det er muligvis ikke det token, du ønsker at bytte.",
"token.safety.warning.malicious.impersonator.message.short": "{{tokenSymbol}} er muligvis ikke det token, du ønsker at bytte.", "token.safety.warning.malicious.impersonator.message.short": "{{tokenSymbol}} er muligvis ikke det token, du ønsker at bytte.",
"token.safety.warning.malicious.title": "Ondsindet token fundet", "token.safety.warning.malicious.title": "Ondsindet token fundet",
"token.safety.warning.mayResultInLoss": "At bytte det kan resultere i et tab af midler.", "token.safety.warning.mayResultInLoss": "At bytte det kan resultere i et tab af midler.",
...@@ -2009,14 +2003,19 @@ ...@@ -2009,14 +2003,19 @@
"token.safety.warning.medium.heading.default_one_also": "Dette token handles heller ikke på førende amerikanske centraliserede børser.", "token.safety.warning.medium.heading.default_one_also": "Dette token handles heller ikke på førende amerikanske centraliserede børser.",
"token.safety.warning.medium.heading.default_other_also": "Disse tokens handles heller ikke på førende amerikanske centraliserede børser.", "token.safety.warning.medium.heading.default_other_also": "Disse tokens handles heller ikke på førende amerikanske centraliserede børser.",
"token.safety.warning.medium.heading.named": "{{tokenSymbol}} handles ikke på førende amerikanske centraliserede børser.", "token.safety.warning.medium.heading.named": "{{tokenSymbol}} handles ikke på førende amerikanske centraliserede børser.",
"token.safety.warning.notAvailableToTrade": "Ikke tilgængelig for handel",
"token.safety.warning.notListedOnExchanges": "Ikke noteret på førende amerikanske børser", "token.safety.warning.notListedOnExchanges": "Ikke noteret på førende amerikanske børser",
"token.safety.warning.spam.message": "{{tokenSymbol}} er blevet markeret som et potentielt spamtoken.", "token.safety.warning.sellFee100.message": "{{ tokenSymbol }} er blevet markeret som usælgelig.",
"token.safety.warning.sellFee100.title": "100 % salgsgebyr registreret",
"token.safety.warning.spam.message": "{{tokenSymbol}} er blevet markeret som spam af Blockaid.",
"token.safety.warning.spam.title": "Spam-token fundet", "token.safety.warning.spam.title": "Spam-token fundet",
"token.safety.warning.spamsUsers": "Spams brugere", "token.safety.warning.spamsUsers": "Spams brugere",
"token.safety.warning.strong.heading.default_one": "Dette token handles ikke på førende amerikanske centraliserede børser eller byttes ofte på Uniswap.", "token.safety.warning.strong.heading.default_one": "Dette token handles ikke på førende amerikanske centraliserede børser eller byttes ofte på Uniswap.",
"token.safety.warning.strong.heading.default_other": "Disse tokens handles ikke på førende amerikanske centraliserede børser eller byttes ofte på Uniswap.", "token.safety.warning.strong.heading.default_other": "Disse tokens handles ikke på førende amerikanske centraliserede børser eller byttes ofte på Uniswap.",
"token.safety.warning.strong.heading.named": "{{tokenSymbol}} handles ikke på førende amerikanske centraliserede børser eller byttes ofte på Uniswap.", "token.safety.warning.strong.heading.named": "{{tokenSymbol}} handles ikke på førende amerikanske centraliserede børser eller byttes ofte på Uniswap.",
"token.safety.warning.tokenChargesFee.percent.message": "{{tokenSymbol}} opkræver et {{feePercent}} gebyr ved køb eller salg.", "token.safety.warning.tokenChargesFee.both.message": "{{tokenSymbol}} opkræver et {{buyFeePercent}} gebyr ved køb og {{sellFeePercent}} ved salg.",
"token.safety.warning.tokenChargesFee.buy.message": "{{tokenSymbol}} opkræver et {{feePercent}} gebyr ved køb.",
"token.safety.warning.tokenChargesFee.sell.message": "{{tokenSymbol}} opkræver et {{feePercent}} gebyr ved salg.",
"token.safetyLevel.blocked.header": "Ikke tilgængelig", "token.safetyLevel.blocked.header": "Ikke tilgængelig",
"token.safetyLevel.blocked.message": "Du kan ikke bytte dette token ved at bruge Uniswap Wallet.", "token.safetyLevel.blocked.message": "Du kan ikke bytte dette token ved at bruge Uniswap Wallet.",
"token.safetyLevel.medium.header": "Advarsel", "token.safetyLevel.medium.header": "Advarsel",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment