ci(release): publish latest release

parent 0f3c158e
* @uniswap/web-admins
IPFS hash of the deployment:
- CIDv0: `QmemEFGpbyCk7BDVXisJ6TzpqXXoEVpACm1kZxyz5FFGNo`
- CIDv1: `bafybeihua3m55nwz3am7z7j7yeoi35vrnmohvrkyi5gd4obsax72anky3q`
We are back with some new updates! Here’s the latest:
The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org).
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
Token Warnings: See more information about the tokens you’re attempting to swap, enriched with data from Blockaid.
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
\ No newline at end of file
extension/1.11.0
\ No newline at end of file
......@@ -126,4 +126,16 @@
<changefreq>weekly</changefreq>
<priority>0.5</priority>
</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>
<!DOCTYPE html>
<html translate="no" style="overflow-x: hidden;">
<html translate="no">
<head>
<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 { ConfirmModalState } from 'components/ConfirmSwapModal'
import Modal from 'components/Modal'
import { AutoColumn } from 'components/deprecated/Column'
import styled from 'lib/styled-components'
import { PropsWithChildren } from 'react'
import { HeightAnimator } from 'ui/src'
import { PropsWithChildren, useRef } from 'react'
import { animated, easings, useSpring } from 'react-spring'
import { TRANSITION_DURATIONS } from 'theme/styles'
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)`
background-color: ${({ theme }) => theme.surface1};
width: 100%;
......@@ -15,27 +31,36 @@ const Content = styled(AutoColumn)`
export function SwapModal({
children,
confirmModalState,
onDismiss,
}: PropsWithChildren<{
confirmModalState: ConfirmModalState
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 (
<Trace modal={InterfaceModalName.CONFIRM_SWAP}>
<Modal isOpen $scrollOverlay onDismiss={onDismiss} maxHeight="90vh" slideIn>
<HeightAnimator
open={true}
width="100%"
minWidth="min-content"
overflow="hidden"
borderRadius="$rounded20"
backgroundColor="$surface1"
$md={{
borderBottomLeftRadius: 0,
borderBottomRightRadius: 0,
}}
>
<Content>{children}</Content>
</HeightAnimator>
<AnimatedContainer style={prevConfirmModalState.current !== confirmModalState ? springProps : undefined}>
<div ref={ref}>
<Content>{children}</Content>
</div>
</AnimatedContainer>
</Modal>
</Trace>
)
......
......@@ -194,7 +194,7 @@ export function ConfirmSwapModal({
return (
// Wrapping in a new theme provider resets any color extraction overriding on the current page. Swap modal should use default/non-overridden theme.
<ThemeProvider>
<SwapModal onDismiss={onModalDismiss}>
<SwapModal confirmModalState={confirmModalState} onDismiss={onModalDismiss}>
{/* Head section displays title, help button, close icon */}
<Container $height="24px" $padding="6px 12px 4px 12px">
<SwapHead
......
......@@ -18,7 +18,7 @@ describe('Expand', () => {
Body
</Expand>,
)
expect(screen.queryByText('Body')).not.toBeNull()
expect(screen.queryByText('Body')).toBeVisible()
})
it('calls `onToggle` when button is pressed', () => {
......
import AnimatedDropdown from 'components/AnimatedDropdown'
import Column from 'components/deprecated/Column'
import Row, { RowBetween } from 'components/deprecated/Row'
import styled from 'lib/styled-components'
import { PropsWithChildren, ReactElement } from 'react'
import { ChevronDown } from 'react-feather'
import { HeightAnimator } from 'ui/src'
import { iconSizes } from 'ui/src/theme'
const ButtonContainer = styled(Row)`
......@@ -53,9 +53,9 @@ export default function Expand({
<ExpandIcon $isOpen={isOpen} size={iconSizes[iconSize]} />
</ButtonContainer>
</RowBetween>
<HeightAnimator open={isOpen}>
<AnimatedDropdown open={isOpen}>
<Content gap="md">{children}</Content>
</HeightAnimator>
</AnimatedDropdown>
</Wrapper>
)
}
......@@ -14,6 +14,7 @@ import {
IncreasePositionTxAndGasInfo,
LiquidityTransactionType,
} 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 { validatePermit, validateTransactionRequest } from 'uniswap/src/features/transactions/swap/utils/trade'
import { ONE_SECOND_MS } from 'utilities/src/time/time'
......@@ -104,6 +105,8 @@ export function IncreaseLiquidityTxContextProvider({ children }: PropsWithChildr
return undefined
}
const deadline = getTradeSettingsDeadline(customDeadline)
return {
simulateTransaction: !approvalsNeeded,
protocol: apiProtocolItems,
......@@ -130,9 +133,10 @@ export function IncreaseLiquidityTxContextProvider({ children }: PropsWithChildr
hooks: positionInfo.v4hook,
},
},
deadline,
slippageTolerance: customSlippageTolerance,
}
}, [account, positionInfo, pool, currencyAmounts, approvalsNeeded, customSlippageTolerance])
}, [account, positionInfo, pool, currencyAmounts, approvalsNeeded, customDeadline, customSlippageTolerance])
const {
data: increaseCalldata,
......@@ -141,8 +145,7 @@ export function IncreaseLiquidityTxContextProvider({ children }: PropsWithChildr
refetch: calldataRefetch,
} = useIncreaseLpPositionCalldataQuery({
params: increaseCalldataQueryParams,
deadlineInMinutes: customDeadline,
refetchInterval: 5 * ONE_SECOND_MS,
staleTime: 5 * ONE_SECOND_MS,
})
const { increase, gasFee: actualGasFee } = increaseCalldata || {}
......
......@@ -30,9 +30,7 @@ export function useDerivedIncreaseLiquidityInfo(state: IncreaseLiquidityState):
token0,
token1,
exactField,
exactAmounts: {
[exactField]: exactAmount,
},
exactAmount,
deposit0Disabled: false,
deposit1Disabled: false,
}
......@@ -55,9 +53,7 @@ export function useDerivedIncreaseLiquidityInfo(state: IncreaseLiquidityState):
token0,
token1,
exactField,
exactAmounts: {
[exactField]: exactAmount,
},
exactAmount,
deposit0Disabled,
deposit1Disabled,
}
......@@ -73,9 +69,7 @@ export function useDerivedIncreaseLiquidityInfo(state: IncreaseLiquidityState):
token0: currency0,
token1: currency1,
exactField,
exactAmounts: {
[exactField]: exactAmount,
},
exactAmount,
deposit0Disabled,
deposit1Disabled,
}
......@@ -84,7 +78,6 @@ export function useDerivedIncreaseLiquidityInfo(state: IncreaseLiquidityState):
return {
protocolVersion: ProtocolVersion.UNSPECIFIED,
exactField,
exactAmounts: {},
}
}, [account.address, exactAmount, exactField, positionInfo, currency0, currency1, token0, token1])
......
......@@ -162,7 +162,7 @@ export function HookModal({
<Text variant="body2" color="$neutral2" textAlign="center" my="$padding8">
{hasDangerous ? t('position.hook.warningInfo') : t('position.addingHook.disclaimer')}
</Text>
<LearnMoreLink centered url={uniswapUrls.helpArticleUrls.addingV4Hooks} textVariant="buttonLabel3" />
<LearnMoreLink centered url={uniswapUrls.helpArticleUrls.v4HooksInfo} textVariant="buttonLabel3" />
</Flex>
<Flex borderRadius="$rounded16" backgroundColor="$surface2" py="$gap12" px="$gap16">
......
......@@ -11,9 +11,7 @@ import { UniverseChainId } from 'uniswap/src/features/chains/types'
export interface DepositState {
exactField: PositionField
exactAmounts: {
[field in PositionField]?: string
}
exactAmount?: string
}
export type DepositContextType = {
......
......@@ -15,6 +15,7 @@ import {
ProtocolItems,
} from 'uniswap/src/data/tradingApi/__generated__'
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 { ONE_SECOND_MS } from 'utilities/src/time/time'
......@@ -67,6 +68,8 @@ export function useRemoveLiquidityTxAndGasInfo({ account }: { account?: string }
return undefined
}
const deadline = getTradeSettingsDeadline(customDeadline)
return {
simulateTransaction: !approvalsNeeded,
protocol: apiProtocolItems,
......@@ -104,6 +107,7 @@ export function useRemoveLiquidityTxAndGasInfo({ account }: { account?: string }
hooks: positionInfo.v4hook,
},
},
deadline,
slippageTolerance: customSlippageTolerance,
}
}, [
......@@ -115,6 +119,7 @@ export function useRemoveLiquidityTxAndGasInfo({ account }: { account?: string }
approvalsNeeded,
feeValue0,
feeValue1,
customDeadline,
customSlippageTolerance,
])
......@@ -125,8 +130,7 @@ export function useRemoveLiquidityTxAndGasInfo({ account }: { account?: string }
refetch: calldataRefetch,
} = useDecreaseLpPositionCalldataQuery({
params: decreaseCalldataQueryParams,
deadlineInMinutes: customDeadline,
refetchInterval: 5 * ONE_SECOND_MS,
staleTime: 5 * ONE_SECOND_MS,
})
const { value: estimatedGasFee } = useTransactionGasFee(decreaseCalldata?.decrease, !!decreaseCalldata?.gasFee)
......
......@@ -31,7 +31,7 @@ describe('MaxSlippageSettings', () => {
it('is expanded by default when custom slippage is set', () => {
store.dispatch(updateUserSlippageTolerance({ userSlippageTolerance: 10 }))
renderSlippageSettings()
expect(getSlippageInput()).not.toBeNull()
expect(getSlippageInput()).toBeVisible()
})
it('does not render auto slippage as a value, but a placeholder', () => {
renderSlippageSettings()
......
......@@ -23,7 +23,7 @@ describe('TransactionDeadlineSettings', () => {
it('is expanded by default when custom deadline is set', () => {
store.dispatch(updateUserDeadline({ userDeadline: DEFAULT_DEADLINE_FROM_NOW * 2 }))
renderTransactionDeadlineSettings()
expect(getDeadlineInput()).not.toBeNull()
expect(getDeadlineInput()).toBeVisible()
})
it('does not render default deadline as a value, but a placeholder', () => {
renderTransactionDeadlineSettings()
......
import { Percent } from '@uniswap/sdk-core'
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 MenuButton from 'components/Settings/MenuButton'
import MultipleRoutingOptions from 'components/Settings/MultipleRoutingOptions'
import RouterPreferenceSettings from 'components/Settings/RouterPreferenceSettings'
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 useDisableScrolling from 'hooks/useDisableScrolling'
import { useIsUniswapXSupportedChain } from 'hooks/useIsUniswapXSupportedChain'
......@@ -21,7 +22,6 @@ import { InterfaceTrade } from 'state/routing/types'
import { isUniswapXTrade } from 'state/routing/utils'
import { Divider, ThemedText } from 'theme/components'
import { Z_INDEX } from 'theme/zIndex'
import { HeightAnimator } from 'ui/src'
import { useIsSupportedChainId } from 'uniswap/src/features/chains/hooks/useSupportedChainId'
import { isL2ChainId } from 'uniswap/src/features/chains/utils'
import { FeatureFlags } from 'uniswap/src/features/gating/flags'
......@@ -149,13 +149,13 @@ export default function SettingsTab({
<RouterPreferenceSettings />
</AutoColumn>
)}
<HeightAnimator open={!isUniswapXTrade(trade)}>
<AnimatedDropdown open={!isUniswapXTrade(trade)}>
<ExpandColumn $padTop={showRoutingSettings}>
{showRoutingSettings && <Divider />}
<MaxSlippageSettings autoSlippage={autoSlippage} />
{showDeadlineSettings && <TransactionDeadlineSettings />}
</ExpandColumn>
</HeightAnimator>
</AnimatedDropdown>
{multipleRoutingOptionsEnabled && (
<>
{!isUniswapXTrade(trade) && <StyledDivider />}
......
......@@ -2,6 +2,7 @@ import clsx, { ClassValue } from 'clsx'
import { Atoms, atoms } from 'nft/css/atoms'
import { sprinkles } from 'nft/css/sprinkles.css'
import * as React from 'react'
import { animated } from 'react-spring'
type HTMLProperties<T = HTMLElement> = Omit<
React.AllHTMLAttributes<T>,
......@@ -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]
Box.displayName = 'Box'
......@@ -2,6 +2,7 @@ import { InterfaceElementName, SwapEventName } from '@uniswap/analytics-events'
import { Percent } from '@uniswap/sdk-core'
import { ReactComponent as ExpandoIconClosed } from 'assets/svg/expando-icon-closed.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 Column from 'components/deprecated/Column'
import Row, { AutoRow, RowBetween, RowFixed } from 'components/deprecated/Row'
......@@ -20,7 +21,7 @@ import { InterfaceTrade, LimitOrderTrade, RouterPreference } from 'state/routing
import { isClassicTrade, isLimitTrade } from 'state/routing/utils'
import { useRouterPreference, useUserSlippageTolerance } from 'state/user/hooks'
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 { Trans, t } from 'uniswap/src/i18n'
import { useTrace } from 'utilities/src/telemetry/trace/TraceContext'
......@@ -306,14 +307,23 @@ function ExpandableLineItems(props: {
const lineItemProps = { trade, allowedSlippage, syncing: false, open, priceImpact }
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">
<AnimatedLineItem {...lineItemProps} type={SwapLineItemType.PRICE_IMPACT} delay={ms('50ms')} />
<AnimatedLineItem {...lineItemProps} type={SwapLineItemType.MAX_SLIPPAGE} delay={ms('100ms')} />
<AnimatedLineItem {...lineItemProps} type={SwapLineItemType.MINIMUM_OUTPUT} delay={ms('120ms')} />
<AnimatedLineItem {...lineItemProps} type={SwapLineItemType.MAXIMUM_INPUT} delay={ms('120ms')} />
</Column>
</HeightAnimator>
</AnimatedDropdown>
)
}
......
import { InterfaceElementName, SwapEventName } from '@uniswap/analytics-events'
import { Percent } from '@uniswap/sdk-core'
import AnimatedDropdown from 'components/AnimatedDropdown'
import { LoadingOpacityContainer } from 'components/Loader/styled'
import Column from 'components/deprecated/Column'
import { RowBetween, RowFixed } from 'components/deprecated/Row'
......@@ -13,7 +14,6 @@ import { ChevronDown } from 'react-feather'
import { InterfaceTrade } from 'state/routing/types'
import { isSubmittableTrade } from 'state/routing/utils'
import { ThemedText } from 'theme/components'
import { HeightAnimator } from 'ui/src'
import Trace from 'uniswap/src/features/telemetry/Trace'
import { Trans } from 'uniswap/src/i18n'
import { useTrace } from 'utilities/src/telemetry/trace/TraceContext'
......@@ -106,7 +106,7 @@ function AdvancedSwapDetails(props: SwapDetailsProps & { open: boolean }) {
const lineItemProps = { trade, allowedSlippage, format, syncing, priceImpact }
return (
<HeightAnimator open={open}>
<AnimatedDropdown open={open}>
<SwapDetailsWrapper gap="sm" data-testid="advanced-swap-details">
<SwapLineItem {...lineItemProps} type={SwapLineItemType.PRICE_IMPACT} />
<SwapLineItem {...lineItemProps} type={SwapLineItemType.MAX_SLIPPAGE} />
......@@ -117,6 +117,6 @@ function AdvancedSwapDetails(props: SwapDetailsProps & { open: boolean }) {
<SwapLineItem {...lineItemProps} type={SwapLineItemType.NETWORK_COST} />
<SwapLineItem {...lineItemProps} type={SwapLineItemType.ROUTING_INFO} />
</SwapDetailsWrapper>
</HeightAnimator>
</AnimatedDropdown>
)
}
......@@ -432,12 +432,9 @@ exports[`SwapDetails.tsx matches base snapshot, test trade exact input 1`] = `
</div>
</div>
<div
class="css-view-175oi2r"
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;"
style="height: 0px; margin-top: -8px; overflow: hidden; width: 100%; min-width: min-content; will-change: height;"
>
<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"
>
......@@ -1036,12 +1033,9 @@ exports[`SwapDetails.tsx renders a preview trade while disabling submission 1`]
</div>
</div>
<div
class="css-view-175oi2r"
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;"
style="height: 0px; margin-top: -8px; overflow: hidden; width: 100%; min-width: min-content; will-change: height;"
>
<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"
>
......
......@@ -341,12 +341,9 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
</div>
</div>
<div
class="css-view-175oi2r"
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%;"
style="height: 0px; overflow: hidden; width: 100%; min-width: min-content; will-change: height;"
>
<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"
data-testid="advanced-swap-details"
......
......@@ -21,11 +21,11 @@ describe('Routing', () => {
})
it('contains all coins for celo', () => {
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', () => {
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?:
}
/**
* @deprecated useCurrencyInfo from packages/uniswap instead
* Returns a CurrencyInfo from the tokenAddress+chainId pair.
*/
export function useCurrencyInfo(currency?: Currency, chainId?: UniverseChainId, skip?: boolean): Maybe<CurrencyInfo>
......
......@@ -243,25 +243,7 @@ export function usePoolActiveLiquidity({
}
}
let sdkPrice
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 sdkPrice = tickToPrice(token0, token1, activeTick)
const activeTickProcessed: TickProcessed = {
liquidityActive: JSBI.BigInt(pool?.liquidity ?? 0),
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'
import { NonfungiblePositionManager } from 'uniswap/src/abis/types/v3/NonfungiblePositionManager'
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,'
......@@ -32,7 +32,7 @@ type UsePositionTokenURIResult =
loading: true
}
export function useNFTPositionManagerContract(
function useNFTPositionManagerContract(
version: ProtocolVersion,
chainId?: UniverseChainId,
): NonfungiblePositionManager | Erc721 | null {
......
......@@ -3,7 +3,7 @@ import { parseEther } from '@ethersproject/units'
import { InterfaceElementName, NFTEventName } from '@uniswap/analytics-events'
import clsx from 'clsx'
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 { useIsMobile } from 'hooks/screenSize/useIsMobile'
import { useScreenSize } from 'hooks/screenSize/useScreenSize'
......@@ -489,7 +489,7 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
return (
<>
<Box
<AnimatedBox
backgroundColor="surface1"
position="sticky"
top="72"
......@@ -594,7 +594,7 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
)}
</Row>
</InfiniteScrollWrapper>
</Box>
</AnimatedBox>
<InfiniteScrollWrapper>
{loading ? (
<CollectionNftsLoading height={renderedHeight} />
......
import { ScrollBarStyles } from 'components/Common/styles'
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 styled from 'lib/styled-components'
import { Column, Row } from 'nft/components/Flex'
......@@ -13,10 +13,12 @@ import { themeVars } from 'nft/css/sprinkles.css'
import { useFiltersExpanded, useWalletCollections } from 'nft/hooks'
import { WalletCollection } from 'nft/types'
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 { FixedSizeList, ListOnItemsRenderedProps } from 'react-window'
import InfiniteLoader from 'react-window-infinite-loader'
import { ThemedText } from 'theme/components'
import { TRANSITION_DURATIONS } from 'theme/styles'
import { LabeledCheckbox } from 'ui/src'
import noop from 'utilities/src/react/noop'
......@@ -79,13 +81,22 @@ export const FilterSidebar = ({
const [isFiltersExpanded, setFiltersExpanded] = useFiltersExpanded()
const isMobile = useIsMobile()
const { sidebarX } = useSpring({
sidebarX: isFiltersExpanded ? 0 : -360,
config: {
duration: TRANSITION_DURATIONS.medium,
easing: easings.easeOutSine,
},
})
const hideSearch = useMemo(
() => (walletCollections && walletCollections?.length >= WALLET_COLLECTIONS_PAGINATION_LIMIT) || isFetchingNextPage,
[walletCollections, isFetchingNextPage],
)
return (
<Box
// @ts-ignore
<AnimatedBox
position={{ sm: 'fixed', md: 'sticky' }}
top={{ sm: '0', md: '72' }}
left={{ sm: '0', md: 'unset' }}
......@@ -93,7 +104,8 @@ export const FilterSidebar = ({
height={{ sm: 'full', md: 'auto' }}
zIndex={{ sm: 'modal', md: 'auto' }}
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
paddingTop={{ sm: '24', md: '0' }}
......@@ -122,7 +134,7 @@ export const FilterSidebar = ({
hideSearch={hideSearch}
/>
</Box>
</Box>
</AnimatedBox>
)
}
......
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 { useIsMobile } from 'hooks/screenSize/useIsMobile'
import { useAccount } from 'hooks/useAccount'
......@@ -22,6 +22,7 @@ import { getOSCollectionsInfiniteQueryOptions } from 'nft/queries/openSea/OSColl
import { WalletCollection } from 'nft/types'
import { Dispatch, SetStateAction, Suspense, useCallback, useEffect, useMemo, useState } from 'react'
import InfiniteScroll from 'react-infinite-scroll-component'
import { easings, useSpring } from 'react-spring'
const ProfilePageColumn = styled(Column)`
${ScreenBreakpointsPaddings}
......@@ -181,6 +182,14 @@ const ProfilePageNfts = ({
first: DEFAULT_WALLET_ASSET_QUERY_AMOUNT,
})
const { gridX } = useSpring({
gridX: isFiltersExpanded ? FILTER_SIDEBAR_WIDTH : -PADDING,
config: {
duration: 250,
easing: easings.easeOutSine,
},
})
if (loading) {
return <ProfileBodyLoadingSkeleton />
}
......@@ -192,11 +201,13 @@ const ProfilePageNfts = ({
<EmptyWalletModule />
</EmptyStateContainer>
) : (
<Box
<AnimatedBox
flexShrink="0"
position={isMobile && isBagExpanded ? 'fixed' : 'static'}
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"
>
......@@ -238,7 +249,7 @@ const ProfilePageNfts = ({
))
: null}
</InfiniteScroll>
</Box>
</AnimatedBox>
)}
</Column>
)
......
......@@ -7,6 +7,7 @@ import { BREAKPOINTS } from 'theme'
const AppContainer = styled.div`
min-height: 100vh;
max-width: 100vw;
overflow-x: hidden;
// grid container settings
display: grid;
......
......@@ -100,7 +100,7 @@ export function ClaimFeeModal() {
<Modal name={ModalName.ClaimFee} onClose={onClose} isDismissible>
<Flex gap="$gap16">
<GetHelpHeader
link={uniswapUrls.helpRequestUrl}
link={uniswapUrls.helpArticleUrls.lpCollectFees}
title={t('pool.collectFees')}
closeModal={onClose}
closeDataTestId="ClaimFeeModal-close-icon"
......
......@@ -10,7 +10,6 @@ import { useV3OrV4PositionDerivedInfo } from 'components/Liquidity/hooks'
import { parseRestPosition } from 'components/Liquidity/utils'
import { LoadingFullscreen, LoadingRows } from 'components/Loader/styled'
import { ZERO_ADDRESS } from 'constants/misc'
import { usePositionOwner } from 'hooks/usePositionOwner'
import { usePositionTokenURI } from 'hooks/usePositionTokenURI'
import NotFound from 'pages/NotFound'
import { LoadingRow } from 'pages/Pool/Positions/shared'
......@@ -19,7 +18,6 @@ import { ChevronRight } from 'react-feather'
import { Navigate, useLocation, useNavigate, useParams } from 'react-router-dom'
import { setOpenModal } from 'state/application/reducer'
import { useAppDispatch } from 'state/hooks'
import { MultichainContextProvider } from 'state/multichain/MultichainContext'
import { usePendingLPTransactionsChangeListener } from 'state/transactions/hooks'
import { ClickableTamaguiStyle } from 'theme/components'
import { Button, Flex, Main, Switch, Text, styled } from 'ui/src'
......@@ -31,7 +29,6 @@ import Trace from 'uniswap/src/features/telemetry/Trace'
import { InterfacePageNameLocal, ModalName } from 'uniswap/src/features/telemetry/constants'
import { Trans, useTranslation } from 'uniswap/src/i18n'
import { currencyId, currencyIdToAddress } from 'uniswap/src/utils/currencyId'
import { addressesAreEquivalent } from 'utils/addressesAreEquivalent'
import { useChainIdFromUrlParam } from 'utils/chainParams'
import { NumberType, useFormatter } from 'utils/formatNumbers'
import { useAccount } from 'wagmi'
......@@ -97,17 +94,7 @@ function parseTokenId(tokenId: string | undefined): BigNumber | undefined {
}
}
export default function PositionPageWrapper() {
const chainId = useChainIdFromUrlParam()
return (
<MultichainContextProvider initialChainId={chainId}>
<PositionPage />
</MultichainContextProvider>
)
}
function PositionPage() {
export default function PositionPage() {
const { tokenId: tokenIdFromUrl } = useParams<{ tokenId: string }>()
const tokenId = parseTokenId(tokenIdFromUrl)
const chainId = useChainIdFromUrlParam()
......@@ -131,7 +118,7 @@ function PositionPage() {
const position = data?.position
const positionInfo = useMemo(() => parseRestPosition(position), [position])
const metadata = usePositionTokenURI(tokenId, chainInfo?.id, positionInfo?.version)
const owner = usePositionOwner(tokenId, chainInfo?.id, positionInfo?.version)
usePendingLPTransactionsChangeListener(refetch)
const dispatch = useAppDispatch()
......@@ -198,9 +185,8 @@ function PositionPage() {
}
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 (
<Trace
......@@ -232,7 +218,7 @@ function PositionPage() {
alignItems="center"
>
<LiquidityPositionInfo positionInfo={positionInfo} />
{status !== PositionStatus.CLOSED && isOwner && (
{status !== PositionStatus.CLOSED && (
<Flex row gap="$gap12" alignItems="center" flexWrap="wrap">
{positionInfo.version === ProtocolVersion.V3 && isV4DataEnabled && (
<HeaderButton
......@@ -312,7 +298,7 @@ function PositionPage() {
<Text variant="subheading1">
<Trans i18nKey="pool.uncollectedFees" />
</Text>
{hasFees && isOwner && (
{hasFees && (
<HeaderButton
emphasis="primary"
onPress={() => {
......
......@@ -6,7 +6,6 @@ import { useGetPoolTokenPercentage } from 'components/Liquidity/hooks'
import { parseRestPosition } from 'components/Liquidity/utils'
import { DoubleCurrencyAndChainLogo } from 'components/Logo/DoubleLogo'
import { ZERO_ADDRESS } from 'constants/misc'
import { usePositionOwnerV2 } from 'hooks/usePositionOwner'
import NotFound from 'pages/NotFound'
import { HeaderButton } from 'pages/Pool/Positions/PositionPage'
import { TextLoader } from 'pages/Pool/Positions/shared'
......@@ -15,7 +14,6 @@ import { ChevronRight } from 'react-feather'
import { Navigate, useNavigate, useParams } from 'react-router-dom'
import { setOpenModal } from 'state/application/reducer'
import { useAppDispatch } from 'state/hooks'
import { MultichainContextProvider } from 'state/multichain/MultichainContext'
import { usePendingLPTransactionsChangeListener } from 'state/transactions/hooks'
import { Button, Circle, Flex, Main, Shine, Text, styled } from 'ui/src'
import { useGetPositionQuery } from 'uniswap/src/data/rest/getPosition'
......@@ -62,17 +60,7 @@ function RowLoader({ withIcon }: { withIcon?: boolean }) {
)
}
export default function V2PositionPageWrapper() {
const chainId = useChainIdFromUrlParam()
return (
<MultichainContextProvider initialChainId={chainId}>
<V2PositionPage />
</MultichainContextProvider>
)
}
function V2PositionPage() {
export default function V2PositionPage() {
const { pairAddress } = useParams<{ pairAddress: string }>()
const chainId = useChainIdFromUrlParam()
const account = useAccount()
......@@ -103,8 +91,6 @@ function V2PositionPage() {
const token0USDValue = useUSDCValue(currency0Amount)
const token1USDValue = useUSDCValue(currency1Amount)
const poolTokenPercentage = useGetPoolTokenPercentage(positionInfo)
const liquidityTokenAddress = positionInfo?.liquidityToken?.isToken ? positionInfo.liquidityToken.address : null
const isOwner = usePositionOwnerV2(account?.address, liquidityTokenAddress, positionInfo?.chainId)
if (!isLoading && !lpRedesignEnabled) {
return <Navigate to="/pools" replace />
......@@ -145,43 +131,41 @@ function V2PositionPage() {
) : (
<LiquidityPositionInfo positionInfo={positionInfo} />
)}
{isOwner && (
<Flex row gap="$gap12" alignItems="center" maxWidth="100%" flexWrap="wrap">
<HeaderButton
disabled={positionLoading}
emphasis="secondary"
onPress={() => {
navigate('/migrate/v2')
}}
>
<Text variant="buttonLabel2" color="$neutral1">
<Trans i18nKey="common.migrate.v3" />
</Text>
</HeaderButton>
<HeaderButton
disabled={positionLoading}
emphasis="secondary"
onPress={() => {
dispatch(setOpenModal({ name: ModalName.AddLiquidity, initialState: positionInfo }))
}}
>
<Text variant="buttonLabel2" color="$neutral1">
<Trans i18nKey="common.addLiquidity" />
</Text>
</HeaderButton>
<HeaderButton
disabled={positionLoading}
emphasis="primary"
onPress={() => {
dispatch(setOpenModal({ name: ModalName.RemoveLiquidity, initialState: positionInfo }))
}}
>
<Text variant="buttonLabel2" color="$surface1">
<Trans i18nKey="pool.removeLiquidity" />
</Text>
</HeaderButton>
</Flex>
)}
<Flex row gap="$gap12" alignItems="center" maxWidth="100%" flexWrap="wrap">
<HeaderButton
disabled={positionLoading}
emphasis="secondary"
onPress={() => {
navigate('/migrate/v2')
}}
>
<Text variant="buttonLabel2" color="$neutral1">
<Trans i18nKey="common.migrate.v3" />
</Text>
</HeaderButton>
<HeaderButton
disabled={positionLoading}
emphasis="secondary"
onPress={() => {
dispatch(setOpenModal({ name: ModalName.AddLiquidity, initialState: positionInfo }))
}}
>
<Text variant="buttonLabel2" color="$neutral1">
<Trans i18nKey="common.addLiquidity" />
</Text>
</HeaderButton>
<HeaderButton
disabled={positionLoading}
emphasis="primary"
onPress={() => {
dispatch(setOpenModal({ name: ModalName.RemoveLiquidity, initialState: positionInfo }))
}}
>
<Text variant="buttonLabel2" color="$surface1">
<Trans i18nKey="pool.removeLiquidity" />
</Text>
</HeaderButton>
</Flex>
<Flex borderColor="$surface3" borderWidth={1} p="$spacing24" gap="$gap12" borderRadius="$rounded20">
{positionLoading || !currency0Amount || !currency1Amount ? (
<Shine>
......
......@@ -30,9 +30,12 @@ import {
generateCreatePositionTxRequest,
} from 'pages/Pool/Positions/create/utils'
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 { useCheckLpApprovalQuery } from 'uniswap/src/data/apiClients/tradingApi/useCheckLpApprovalQuery'
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 { ONE_SECOND_MS } from 'utilities/src/time/time'
......@@ -44,6 +47,10 @@ export function CreatePositionContextProvider({
initialState?: Partial<PositionState>
}) {
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 derivedPositionInfo = useDerivedPositionInfo(positionState)
const [feeTierSearchModalOpen, setFeeTierSearchModalOpen] = useState(false)
......@@ -51,14 +58,18 @@ export function CreatePositionContextProvider({
open: false,
wishFeeData: DEFAULT_POSITION_STATE.fee,
})
const { defaultChainId } = useEnabledChains()
const defaultInitialToken = nativeOnChain(defaultChainId)
const reset = useCallback(() => {
setPositionState({
...DEFAULT_POSITION_STATE,
...initialState,
protocolVersion: positionState.protocolVersion,
currencyInputs: { [PositionField.TOKEN0]: defaultInitialToken },
})
setStep(PositionFlowStep.SELECT_TOKENS_AND_FEE_TIER)
}, [initialState])
}, [defaultInitialToken, positionState.protocolVersion])
return (
<CreatePositionContext.Provider
......@@ -164,6 +175,7 @@ export function CreateTxContextProvider({ children }: { children: React.ReactNod
priceRangeState,
derivedPriceRangeInfo,
derivedDepositInfo,
swapSettings,
})
}, [
account,
......@@ -171,6 +183,7 @@ export function CreateTxContextProvider({ children }: { children: React.ReactNod
derivedDepositInfo,
derivedPositionInfo,
derivedPriceRangeInfo,
swapSettings,
positionState,
priceRangeState,
])
......@@ -180,8 +193,7 @@ export function CreateTxContextProvider({ children }: { children: React.ReactNod
refetch: createRefetch,
} = useCreateLpPositionCalldataQuery({
params: createCalldataQueryParams,
deadlineInMinutes: swapSettings.customDeadline,
refetchInterval: 5 * ONE_SECOND_MS,
staleTime: 5 * ONE_SECOND_MS,
})
const validatedValue = useMemo(() => {
......
......@@ -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 { SwapSettingsContextProvider } from 'uniswap/src/features/transactions/swap/settings/contexts/SwapSettingsContext'
import { Trans, useTranslation } from 'uniswap/src/i18n'
import { INTERFACE_NAV_HEIGHT } from 'uniswap/src/theme/heights'
import { usePrevious } from 'utilities/src/react/hooks'
function CreatingPoolInfo() {
......@@ -179,7 +178,7 @@ const Sidebar = () => {
}, [creatingPoolOrPair, protocolVersion, setStep, step, t])
return (
<Flex width={360} alignSelf="flex-start" $platform-web={{ position: 'sticky', top: INTERFACE_NAV_HEIGHT + 25 }}>
<Flex width={360}>
<PoolProgressIndicator steps={PoolProgressSteps} />
</Flex>
)
......@@ -232,7 +231,7 @@ const Toolbar = ({
const { reset: resetMultichainState } = useMultichainContext()
const { isTestnetModeEnabled } = useEnabledChains()
const prevIsTestnetModeEnabled = usePrevious(isTestnetModeEnabled) ?? false
const prevIsTestnetModeEnabled = usePrevious(isTestnetModeEnabled)
const isFormUnchanged = useMemo(() => {
// Check if all form fields (except protocol version) are set to their default values
......@@ -356,13 +355,10 @@ const Toolbar = ({
export default function CreatePosition() {
const { value: lpRedesignEnabled, isLoading } = useFeatureFlagWithLoading(FeatureFlags.LPRedesign)
const isV4DataEnabled = useFeatureFlag(FeatureFlags.V4Data)
const media = useMedia()
// URL format is `/positions/create/:protocolVersion`, with possible searchParams `?currencyA=...&currencyB=...&chain=...`
const { protocolVersion } = useParams<{ protocolVersion: string }>()
const paramsProtocolVersion = parseProtocolVersion(protocolVersion)
const initialCurrencyInputs = useInitialCurrencyInputs()
const initialProtocolVersion = useMemo((): ProtocolVersion => {
if (isV4DataEnabled) {
return paramsProtocolVersion ?? ProtocolVersion.V4
......@@ -374,6 +370,10 @@ export default function CreatePosition() {
return paramsProtocolVersion
}, [isV4DataEnabled, paramsProtocolVersion])
const initialCurrencyInputs = useInitialCurrencyInputs()
const media = useMedia()
if (!isLoading && !lpRedesignEnabled) {
return <Navigate to="/pools" replace />
}
......
......@@ -73,7 +73,6 @@ export const usePriceRangeContext = () => {
export const DEFAULT_DEPOSIT_STATE: DepositState = {
exactField: PositionField.TOKEN0,
exactAmounts: {},
}
export const DepositContext = React.createContext<DepositContextType>({
......
......@@ -34,11 +34,9 @@ export const DepositStep = ({ ...rest }: FlexProps) => {
const handleUserInput = (field: PositionField, newValue: string) => {
setDepositState((prev) => ({
...prev,
exactField: field,
exactAmounts: {
...prev.exactAmounts,
[field]: newValue,
},
exactAmount: newValue,
}))
}
......@@ -68,7 +66,7 @@ export const DepositStep = ({ ...rest }: FlexProps) => {
<>
<Container {...rest}>
<Flex gap={32}>
<Flex gap="$spacing4">
<Flex row alignItems="center">
<Text variant="subheading1">
<Trans i18nKey="common.depositTokens" />
</Text>
......
......@@ -259,9 +259,7 @@ export type UseDepositInfoProps = {
token0?: Currency
token1?: Currency
exactField: PositionField
exactAmounts: {
[field in PositionField]?: string
}
exactAmount?: string
skipDependentAmount?: boolean
deposit0Disabled?: boolean
deposit1Disabled?: boolean
......@@ -291,7 +289,7 @@ export function useDerivedDepositInfo(state: DepositState): DepositInfo {
const account = useAccount()
const { derivedPositionInfo } = useCreatePositionContext()
const { derivedPriceRangeInfo } = usePriceRangeContext()
const { exactAmounts, exactField } = state
const { exactAmount, exactField } = state
const { protocolVersion } = derivedPriceRangeInfo
const depositInfoProps: UseDepositInfoProps = useMemo(() => {
......@@ -303,7 +301,7 @@ export function useDerivedDepositInfo(state: DepositState): DepositInfo {
token0: derivedPositionInfo.currencies[0],
token1: derivedPositionInfo.currencies[1],
exactField,
exactAmounts,
exactAmount,
} satisfies UseDepositInfoProps
}
......@@ -321,7 +319,7 @@ export function useDerivedDepositInfo(state: DepositState): DepositInfo {
token0: derivedPositionInfo.currencies[0],
token1: derivedPositionInfo.currencies[1],
exactField,
exactAmounts,
exactAmount,
skipDependentAmount: outOfRange || invalidRange,
deposit0Disabled,
deposit1Disabled,
......@@ -337,29 +335,25 @@ export function useDerivedDepositInfo(state: DepositState): DepositInfo {
token0: derivedPositionInfo.currencies[0],
token1: derivedPositionInfo.currencies[1],
exactField,
exactAmounts,
exactAmount,
skipDependentAmount: outOfRange || invalidRange,
deposit0Disabled,
deposit1Disabled,
} satisfies UseDepositInfoProps
}, [account.address, derivedPositionInfo, derivedPriceRangeInfo, exactAmounts, exactField, protocolVersion])
}, [account.address, derivedPositionInfo, derivedPriceRangeInfo, exactAmount, exactField, protocolVersion])
return useDepositInfo(depositInfoProps)
}
export function useDepositInfo(state: UseDepositInfoProps): DepositInfo {
const account = useAccount()
const { protocolVersion, address, token0, token1, exactField, exactAmounts, deposit0Disabled, deposit1Disabled } =
const { protocolVersion, address, token0, token1, exactField, exactAmount, deposit0Disabled, deposit1Disabled } =
state
const [token0Balance, token1Balance] = useCurrencyBalances(address, [token0, token1])
const [independentToken, dependentToken] = exactField === PositionField.TOKEN0 ? [token0, token1] : [token1, token0]
const independentAmount = tryParseCurrencyAmount(exactAmounts[exactField], independentToken)
const otherAmount = tryParseCurrencyAmount(
exactAmounts[exactField === PositionField.TOKEN0 ? PositionField.TOKEN1 : PositionField.TOKEN0],
dependentToken,
)
const independentAmount = tryParseCurrencyAmount(exactAmount, independentToken)
const dependentAmount: CurrencyAmount<Currency> | undefined = useMemo(() => {
const shouldSkip = state.skipDependentAmount || protocolVersion === ProtocolVersion.UNSPECIFIED
......@@ -370,7 +364,6 @@ export function useDepositInfo(state: UseDepositInfoProps): DepositInfo {
if (protocolVersion === ProtocolVersion.V2) {
return getDependentAmountFromV2Pair({
independentAmount,
otherAmount,
pair: state.pair,
exactField,
token0,
......@@ -399,7 +392,7 @@ export function useDepositInfo(state: UseDepositInfoProps): DepositInfo {
tickUpper,
})
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 dependentTokenUSDValue = useUSDCValue(dependentAmount) || undefined
......@@ -474,7 +467,7 @@ export function useDepositInfo(state: UseDepositInfoProps): DepositInfo {
return useMemo(
() => ({
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 },
currencyAmountsUSDValue: { [exactField]: independentTokenUSDValue, [dependentField]: dependentTokenUSDValue },
error,
......@@ -483,7 +476,7 @@ export function useDepositInfo(state: UseDepositInfoProps): DepositInfo {
token0Balance,
token1Balance,
exactField,
exactAmounts,
exactAmount,
dependentField,
dependentAmount,
independentAmount,
......@@ -524,10 +517,8 @@ export function useInitialCurrencyInputs() {
const currencyA = useCurrency(currencyAddressA, supportedChainId)
const currencyB = useCurrency(currencyAddressB, supportedChainId)
return useMemo(() => {
return {
[PositionField.TOKEN0]: currencyA ?? currencyB ?? defaultInitialToken,
[PositionField.TOKEN1]: currencyA && currencyB ? currencyB : undefined,
}
}, [currencyA, currencyB, defaultInitialToken])
return {
[PositionField.TOKEN0]: currencyA ?? currencyB ?? defaultInitialToken,
[PositionField.TOKEN1]: currencyA && currencyB ? currencyB : undefined,
}
}
......@@ -44,6 +44,8 @@ import {
} from 'uniswap/src/data/tradingApi/__generated__'
import { AccountMeta } from 'uniswap/src/features/accounts/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 { areCurrenciesEqual } from 'uniswap/src/utils/currencyId'
import { getTickToPrice, getV4TickToPrice } from 'utils/getTickToPrice'
......@@ -413,7 +415,6 @@ function createMockPair({
export function getDependentAmountFromV2Pair({
independentAmount,
otherAmount,
pair,
exactField,
token0,
......@@ -421,7 +422,6 @@ export function getDependentAmountFromV2Pair({
dependentToken,
}: {
independentAmount?: CurrencyAmount<Currency>
otherAmount?: CurrencyAmount<Currency>
pair?: Pair
exactField: PositionField
token0?: Currency
......@@ -433,22 +433,16 @@ export function getDependentAmountFromV2Pair({
return undefined
}
try {
const dependentTokenAmount =
exactField === PositionField.TOKEN0
? pair.priceOf(token0Wrapped).quote(independentAmount.wrapped)
: pair.priceOf(token1Wrapped).quote(independentAmount.wrapped)
const dependentTokenAmount =
exactField === PositionField.TOKEN0
? pair.priceOf(token0Wrapped).quote(independentAmount.wrapped)
: pair.priceOf(token1Wrapped).quote(independentAmount.wrapped)
return dependentToken
? dependentToken?.isNative
? CurrencyAmount.fromRawAmount(dependentToken, dependentTokenAmount.quotient)
: dependentTokenAmount
: 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
}
return dependentToken
? dependentToken?.isNative
? CurrencyAmount.fromRawAmount(dependentToken, dependentTokenAmount.quotient)
: dependentTokenAmount
: undefined
}
export function getDependentAmountFromV3Position({
......@@ -894,6 +888,7 @@ export function generateCreateCalldataQueryParams({
priceRangeState,
derivedPriceRangeInfo,
derivedDepositInfo,
swapSettings,
}: {
account?: AccountMeta
approvalCalldata?: CheckApprovalLPResponse
......@@ -902,10 +897,12 @@ export function generateCreateCalldataQueryParams({
priceRangeState: PriceRangeState
derivedPriceRangeInfo: PriceRangeInfo
derivedDepositInfo: DepositInfo
swapSettings: SwapSettingsState
}): CreateLPPositionRequest | undefined {
const apiProtocolItems = getProtocolItems(positionState.protocolVersion)
const currencies = derivedPositionInfo.currencies
const { currencyAmounts } = derivedDepositInfo
const { customDeadline } = swapSettings
if (
!account?.address ||
......@@ -919,6 +916,8 @@ export function generateCreateCalldataQueryParams({
const { token0Approval, token1Approval, positionTokenApproval, permitData } = approvalCalldata ?? {}
const deadline = getTradeSettingsDeadline(customDeadline)
if (derivedPositionInfo.protocolVersion === ProtocolVersion.V2) {
if (derivedPositionInfo.protocolVersion !== derivedPriceRangeInfo.protocolVersion) {
return undefined
......@@ -947,6 +946,7 @@ export function generateCreateCalldataQueryParams({
chainId: currencyAmounts.TOKEN0.currency.chainId,
amount0: currencyAmounts[token0Index]?.quotient.toString(),
amount1: currencyAmounts[token1Index]?.quotient.toString(),
deadline,
position: {
pool: {
token0: getCurrencyAddressForTradingApi(currencyAmounts[token0Index]?.currency),
......@@ -1005,6 +1005,7 @@ export function generateCreateCalldataQueryParams({
currentTick,
sqrtRatioX96,
initialPrice,
deadline,
position: {
tickLower,
tickUpper,
......
......@@ -364,13 +364,7 @@ export default function Pool() {
text={t('liquidity.provideOnProtocols')}
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>
<ExternalArrowLink href={uniswapUrls.helpArticleUrls.positionsLearnMore}>
{t('common.button.learn')}
......
import { forwardRef, useEffect } from 'react'
import { Input, InputProps, Input as TextInputBase, isWeb, useSporeColors } from 'ui/src'
import { forwardRef } from 'react'
import { Input, InputProps, Input as TextInputBase, useSporeColors } from 'ui/src'
export type TextInputProps = InputProps
......@@ -10,18 +10,6 @@ export const TextInput = forwardRef<TextInputBase, TextInputProps>(function _Tex
ref,
) {
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 (
<Input
ref={ref}
......
......@@ -4,7 +4,10 @@ import type { ImageSourcePropType } from 'react-native'
import { CELO_LOGO, ETH_LOGO } from 'ui/src/assets'
import {
ARB,
BTC_BSC,
BUSD_BSC,
CEUR_CELO,
CUSD_CELO,
DAI,
DAI_ARBITRUM_ONE,
DAI_AVALANCHE,
......@@ -35,6 +38,7 @@ import {
USDT_POLYGON,
WBTC,
WBTC_ARBITRUM_ONE,
WBTC_CELO,
WBTC_OPTIMISM,
WBTC_POLYGON,
WETH_AVALANCHE,
......@@ -55,9 +59,7 @@ type ChainCurrencyList = {
}
/**
* @deprecated
* 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.
* Shows up in the currency select for swap and add liquidity
*/
export const COMMON_BASES: ChainCurrencyList = {
[UniverseChainId.Mainnet]: [
......@@ -116,11 +118,24 @@ export const COMMON_BASES: ChainCurrencyList = {
WBTC_POLYGON,
].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(
buildPartialCurrencyInfo,
),
[UniverseChainId.Bnb]: [
nativeOnChain(UniverseChainId.Bnb),
DAI_BSC,
USDC_BSC,
USDT_BSC,
ETH_BSC,
BTC_BSC,
BUSD_BSC,
].map(buildPartialCurrencyInfo),
[UniverseChainId.Avalanche]: [
nativeOnChain(UniverseChainId.Avalanche),
......
......@@ -91,8 +91,6 @@ export const USDC_BASE = new Token(
'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 USDT_BNB = new Token(
......@@ -103,6 +101,8 @@ export const USDT_BNB = new Token(
'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 USDT_BSC = new Token(UniverseChainId.Bnb, '0x55d398326f99059fF775485246999027B3197955', 18, 'USDT', 'USDT')
......@@ -245,6 +245,14 @@ export const CELO_CELO = new Token(
'Celo',
)
export const CEUR_CELO = new Token(
UniverseChainId.Celo,
'0xD8763CBa276a3738E6DE85b4b3bF5FDed6D6cA73',
18,
'cEUR',
'Celo Euro Stablecoin',
)
export const PORTAL_ETH_CELO = new Token(
UniverseChainId.Celo,
'0x66803FB87aBd4aaC3cbB3fAd7C3aa01f6F3FB207',
......@@ -253,6 +261,14 @@ export const PORTAL_ETH_CELO = new Token(
'Portal Ether',
)
export const WBTC_CELO = new Token(
UniverseChainId.Celo,
'0xd71Ffd0940c920786eC4DbB5A12306669b5b81EF',
18,
'WBTC',
'Wrapped BTC',
)
export const USDC_CELO = new Token(
UniverseChainId.Celo,
'0xceba9300f2b948710d2653dd7b07f33a8b32118c',
......
......@@ -39,14 +39,15 @@ export const uniswapUrls = {
limitsFailure: `${helpUrl}/articles/24300813697933-Why-did-my-limit-order-fail-or-not-execute`,
limitsInfo: `${helpUrl}/sections/24372644881293`,
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`,
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-`,
networkFeeInfo: `${helpUrl}/articles/8370337377805-What-is-a-network-fee-`,
poolOutOfSync: `${helpUrl}/articles/25845512413069`,
positionsLearnMore: `${helpUrl}/sections/8122851346573`,
positionsLearnMore: `${helpUrl}/sections/30998264709645`,
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`,
recoveryPhraseHowToFind: `${helpUrl}/articles/11306360177677-How-to-find-my-recovery-phrase-in-the-Uniswap-Wallet`,
recoveryPhraseForgotten: `${helpUrl}/articles/11306367118349`,
......@@ -61,7 +62,6 @@ export const uniswapUrls = {
uniswapXFailure: `${helpUrl}/articles/17515489874189-Why-can-my-swap-not-be-filled-`,
unitagClaimPeriod: `${helpUrl}/articles/24009960408589`,
unsupportedTokenPolicy: `${helpUrl}/articles/18783694078989-Unsupported-Token-Policy`,
addingV4Hooks: `${helpUrl}/articles/32402040565133`,
v4HooksInfo: `${helpUrl}/articles/30998263256717`,
v4RoutingInfo: `${helpUrl}/articles/32214043316109`,
walletHelp: `${helpUrl}/categories/11301970439565-Uniswap-Wallet`,
......
import { UseQueryResult, skipToken, useQuery } from '@tanstack/react-query'
import { uniswapUrls } from 'uniswap/src/constants/urls'
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 { CreateLPPositionRequest, CreateLPPositionResponse } from 'uniswap/src/data/tradingApi/__generated__'
export function useCreateLpPositionCalldataQuery({
params,
deadlineInMinutes,
...rest
}: UseQueryApiHelperHookArgs<CreateLPPositionRequest, CreateLPPositionResponse> & {
deadlineInMinutes: number | undefined
}): UseQueryResult<CreateLPPositionResponse> {
}: UseQueryApiHelperHookArgs<
CreateLPPositionRequest,
CreateLPPositionResponse
>): UseQueryResult<CreateLPPositionResponse> {
const queryKey = [TRADING_API_CACHE_KEY, uniswapUrls.tradingApiPaths.createLp, params]
const deadline = getTradeSettingsDeadline(deadlineInMinutes)
const paramsWithDeadline = { ...params, deadline }
return useQuery<CreateLPPositionResponse>({
queryKey,
queryFn: params
? async (): ReturnType<typeof createLpPosition> => await createLpPosition(paramsWithDeadline)
: skipToken,
queryFn: params ? async (): ReturnType<typeof createLpPosition> => await createLpPosition(params) : skipToken,
...rest,
})
}
import { UseQueryResult, skipToken, useQuery } from '@tanstack/react-query'
import { uniswapUrls } from 'uniswap/src/constants/urls'
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 { DecreaseLPPositionRequest, DecreaseLPPositionResponse } from 'uniswap/src/data/tradingApi/__generated__'
export function useDecreaseLpPositionCalldataQuery({
params,
deadlineInMinutes,
...rest
}: UseQueryApiHelperHookArgs<DecreaseLPPositionRequest, DecreaseLPPositionResponse> & {
deadlineInMinutes: number | undefined
}): UseQueryResult<DecreaseLPPositionResponse> {
}: UseQueryApiHelperHookArgs<
DecreaseLPPositionRequest,
DecreaseLPPositionResponse
>): UseQueryResult<DecreaseLPPositionResponse> {
const queryKey = [TRADING_API_CACHE_KEY, uniswapUrls.tradingApiPaths.decreaseLp, params]
const deadline = getTradeSettingsDeadline(deadlineInMinutes)
const paramsWithDeadline = { ...params, deadline }
return useQuery<DecreaseLPPositionResponse>({
queryKey,
queryFn: params
? async (): ReturnType<typeof decreaseLpPosition> => await decreaseLpPosition(paramsWithDeadline)
: skipToken,
queryFn: params ? async (): ReturnType<typeof decreaseLpPosition> => await decreaseLpPosition(params) : skipToken,
...rest,
})
}
import { UseQueryResult, skipToken, useQuery } from '@tanstack/react-query'
import { uniswapUrls } from 'uniswap/src/constants/urls'
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 { IncreaseLPPositionRequest, IncreaseLPPositionResponse } from 'uniswap/src/data/tradingApi/__generated__'
export function useIncreaseLpPositionCalldataQuery({
params,
deadlineInMinutes,
...rest
}: UseQueryApiHelperHookArgs<IncreaseLPPositionRequest, IncreaseLPPositionResponse> & {
deadlineInMinutes: number | undefined
}): UseQueryResult<IncreaseLPPositionResponse> {
}: UseQueryApiHelperHookArgs<
IncreaseLPPositionRequest,
IncreaseLPPositionResponse
>): UseQueryResult<IncreaseLPPositionResponse> {
const queryKey = [TRADING_API_CACHE_KEY, uniswapUrls.tradingApiPaths.increaseLp, params]
const deadline = getTradeSettingsDeadline(deadlineInMinutes)
const paramsWithDeadline = { ...params, deadline }
return useQuery<IncreaseLPPositionResponse>({
queryKey,
queryFn: params
? async (): ReturnType<typeof increaseLpPosition> => await increaseLpPosition(paramsWithDeadline)
: skipToken,
queryFn: params ? async (): ReturnType<typeof increaseLpPosition> => await increaseLpPosition(params) : skipToken,
...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> = {
[RPCType.Fallback]: { http: ['https://rpc.ankr.com/optimism'] },
[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],
statusPage: 'https://optimism.io/status',
supportsInterfaceClientSideRouting: true,
......
......@@ -192,6 +192,14 @@ type BridgeSwapTransactionResultProperties = BaseSwapTransactionResultProperties
type FailedUniswapXOrderResultProperties = Omit<UniswapXTransactionResultProperties, 'hash'>
type FailedClassicSwapResultProperties = Omit<ClassicSwapTransactionResultProperties, 'hash'> & {
hash: string | undefined
}
type FailedBridgeSwapResultProperties = Omit<BridgeSwapTransactionResultProperties, 'hash'> & {
hash: string | undefined
}
type TransferProperties = {
chainId: UniverseChainId
tokenAddress: Address
......@@ -679,9 +687,9 @@ export type UniverseEventProperties = {
| UniswapXTransactionResultProperties
| BridgeSwapTransactionResultProperties
[SwapEventName.SWAP_TRANSACTION_FAILED]:
| ClassicSwapTransactionResultProperties
| FailedClassicSwapResultProperties
| FailedUniswapXOrderResultProperties
| BridgeSwapTransactionResultProperties
| FailedBridgeSwapResultProperties
[SwapEventName.SWAP_DETAILS_EXPANDED]: ITraceContext | undefined
[SwapEventName.SWAP_AUTOROUTER_VISUALIZATION_EXPANDED]: ITraceContext
[SwapEventName.SWAP_QUOTE_RECEIVED]: {
......
......@@ -21,7 +21,10 @@ import { currencyIdToAddress } from 'uniswap/src/utils/currencyId'
type TokenWarningCardProps = {
currencyInfo: Maybe<CurrencyInfo>
tokenProtectionWarningOverride?: TokenProtectionWarning
feePercentOverride?: number
feeOnTransferOverride?: {
buyFeePercent?: number
sellFeePercent?: number
}
onPress?: () => void
headingTestId?: string
descriptionTestId?: string
......@@ -33,11 +36,15 @@ type TokenWarningCardProps = {
function useTokenWarningOverrides(
currencyInfo: Maybe<CurrencyInfo>,
tokenProtectionWarningOverride?: TokenProtectionWarning,
feePercentOverride?: number,
feeOnTransferOverride?: {
buyFeePercent?: number
sellFeePercent?: number
},
): { severity: WarningSeverity; heading: string | null; description: string | null } {
const { t } = useTranslation()
const { formatPercent } = useLocalizationContext()
const { heading: headingDefault, description: descriptionDefault } = useTokenWarningCardText(currencyInfo)
const { buyFeePercent, sellFeePercent } = getFeeOnTransfer(currencyInfo?.currency)
const severity = tokenProtectionWarningOverride
? getSeverityFromTokenProtectionWarning(tokenProtectionWarningOverride)
......@@ -52,7 +59,8 @@ function useTokenWarningOverrides(
t,
tokenProtectionWarning: tokenProtectionWarningOverride ?? TokenProtectionWarning.None,
tokenSymbol: currencyInfo?.currency.symbol,
feePercent: feePercentOverride ?? getFeeOnTransfer(currencyInfo?.currency),
buyFeePercent: feeOnTransferOverride?.buyFeePercent ?? buyFeePercent,
sellFeePercent: feeOnTransferOverride?.sellFeePercent ?? sellFeePercent,
formatPercent,
})
......@@ -65,7 +73,7 @@ function useTokenWarningOverrides(
export function TokenWarningCard({
currencyInfo,
tokenProtectionWarningOverride,
feePercentOverride,
feeOnTransferOverride,
headingTestId,
descriptionTestId,
hideCtaIcon,
......@@ -77,13 +85,14 @@ export function TokenWarningCard({
const { severity, heading, description } = useTokenWarningOverrides(
currencyInfo,
tokenProtectionWarningOverride,
feePercentOverride,
feeOnTransferOverride,
)
if (!currencyInfo || !severity || !description) {
return null
}
const { buyFeePercent, sellFeePercent } = getFeeOnTransfer(currencyInfo?.currency)
const analyticsProperties = {
tokenSymbol: currencyInfo.currency.symbol,
chainId: currencyInfo.currency.chainId,
......@@ -91,7 +100,8 @@ export function TokenWarningCard({
warningSeverity: WarningSeverity[severity],
tokenProtectionWarning:
TokenProtectionWarning[tokenProtectionWarningOverride ?? getTokenProtectionWarning(currencyInfo)],
feeOnTransfer: feePercentOverride ?? getFeeOnTransfer(currencyInfo.currency),
buyFeePercent: feeOnTransferOverride?.buyFeePercent ?? buyFeePercent,
sellFeePercent: feeOnTransferOverride?.sellFeePercent ?? sellFeePercent,
safetyInfo: currencyInfo.safetyInfo,
}
......
import { Percent } from '@uniswap/sdk-core'
import { TFunction } from 'i18next'
import { useState } from 'react'
import { Trans } from 'react-i18next'
......@@ -41,7 +40,7 @@ interface TokenWarningProps {
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`)
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 {
......@@ -74,13 +73,12 @@ function TokenWarningModalContent({
const { t } = useTranslation()
const { formatPercent } = useLocalizationContext()
const tokenProtectionWarning = feeOnTransferOverride
? getFeeWarning(feeOnTransferOverride.fee)
: getTokenProtectionWarning(currencyInfo0)
const tokenProtectionWarning =
feeOnTransferOverride?.buyFeePercent || feeOnTransferOverride?.sellFeePercent
? getFeeWarning(Math.max(feeOnTransferOverride.buyFeePercent ?? 0, feeOnTransferOverride.sellFeePercent ?? 0))
: getTokenProtectionWarning(currencyInfo0)
const severity = getSeverityFromTokenProtectionWarning(tokenProtectionWarning)
const feePercent = feeOnTransferOverride
? parseFloat(feeOnTransferOverride.fee.toFixed())
: getFeeOnTransfer(currencyInfo0.currency)
const { buyFeePercent, sellFeePercent } = getFeeOnTransfer(currencyInfo0.currency)
const isFeeRelatedWarning = getIsFeeRelatedWarning(tokenProtectionWarning)
const tokenSymbol = currencyInfo0.currency.symbol
const titleText = getModalHeaderText({
......@@ -94,7 +92,8 @@ function TokenWarningModalContent({
t,
tokenProtectionWarning,
tokenSymbol,
feePercent,
buyFeePercent: feeOnTransferOverride?.buyFeePercent ?? buyFeePercent,
sellFeePercent: feeOnTransferOverride?.sellFeePercent ?? sellFeePercent,
shouldHavePluralTreatment: shouldBeCombinedPlural,
formatPercent,
})
......@@ -136,7 +135,8 @@ function TokenWarningModalContent({
tokenAddress1: currencyInfo1 && currencyIdToAddress(currencyInfo1.currencyId),
warningSeverity: WarningSeverity[severity],
tokenProtectionWarning: TokenProtectionWarning[tokenProtectionWarning],
feeOnTransfer: feePercent,
buyFeePercent: feeOnTransferOverride?.buyFeePercent ?? buyFeePercent,
sellFeePercent: feeOnTransferOverride?.sellFeePercent ?? sellFeePercent,
safetyInfo: currencyInfo0.safetyInfo,
...(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 { ProtectionResult } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { AttackType, CurrencyInfo, SafetyInfo, TokenList } from 'uniswap/src/features/dataApi/types'
......@@ -377,25 +377,25 @@ describe('safetyUtils', () => {
describe('getFeeWarning', () => {
it.each([
[new Percent(0, 100), TokenProtectionWarning.None, '0% -> None'],
[0, TokenProtectionWarning.None, '0% -> None'],
// Low fees (0-5%)
[new Percent(3, 1000), TokenProtectionWarning.FotLow, '0.3% -> FotLow'],
[new Percent(42, 1000), TokenProtectionWarning.FotLow, '4.2% -> FotLow'],
[new Percent(99, 10000), TokenProtectionWarning.FotLow, '0.99% -> FotLow'],
[0.3, TokenProtectionWarning.FotLow, '0.3% -> FotLow'],
[4.2, TokenProtectionWarning.FotLow, '4.2% -> FotLow'],
[0.99, TokenProtectionWarning.FotLow, '0.99% -> FotLow'],
// High fees (5-80%)
[new Percent(5, 100), TokenProtectionWarning.FotHigh, '5% -> FotHigh'],
[new Percent(50, 100), TokenProtectionWarning.FotHigh, '50% -> FotHigh'],
[new Percent(799, 1000), TokenProtectionWarning.FotHigh, '79.9% -> FotHigh'],
[new Percent(5123, 10000), TokenProtectionWarning.FotHigh, '51.23% -> FotHigh'],
[new Percent(6789, 10000), TokenProtectionWarning.FotHigh, '67.89% -> FotHigh'],
[5, TokenProtectionWarning.FotHigh, '5% -> FotHigh'],
[50, TokenProtectionWarning.FotHigh, '50% -> FotHigh'],
[79.9, TokenProtectionWarning.FotHigh, '79.9% -> FotHigh'],
[51.23, TokenProtectionWarning.FotHigh, '51.23% -> FotHigh'],
[67.89, TokenProtectionWarning.FotHigh, '67.89% -> FotHigh'],
// Very high fees (80-100%)
[new Percent(80, 100), TokenProtectionWarning.FotVeryHigh, '80% -> FotVeryHigh'],
[new Percent(90, 100), TokenProtectionWarning.FotVeryHigh, '90% -> FotVeryHigh'],
[new Percent(8456, 10000), TokenProtectionWarning.FotVeryHigh, '84.56% -> FotVeryHigh'],
[new Percent(999, 1000), TokenProtectionWarning.FotVeryHigh, '99.9% -> FotVeryHigh'],
[80, TokenProtectionWarning.FotVeryHigh, '80% -> FotVeryHigh'],
[90, TokenProtectionWarning.FotVeryHigh, '90% -> FotVeryHigh'],
[84.56, TokenProtectionWarning.FotVeryHigh, '84.56% -> FotVeryHigh'],
[99.9, TokenProtectionWarning.FotVeryHigh, '99.9% -> FotVeryHigh'],
// Honeypot (100%)
[new Percent(100, 100), TokenProtectionWarning.MaliciousHoneypot, '100% -> MaliciousHoneypot'],
[new Percent(10000, 10000), TokenProtectionWarning.MaliciousHoneypot, '100% -> MaliciousHoneypot'],
[100, TokenProtectionWarning.MaliciousHoneypot, '100% -> MaliciousHoneypot'],
[100, TokenProtectionWarning.MaliciousHoneypot, '100% -> MaliciousHoneypot'],
])('%s', (fee, expectedWarning, _) => {
expect(getFeeWarning(fee)).toBe(expectedWarning)
})
......
......@@ -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 { useNetworkColors } from 'uniswap/src/utils/colors'
import { NumberType } from 'utilities/src/format/types'
import { logger } from 'utilities/src/logger/logger'
export function useInsufficientNativeTokenWarning({
flow,
......@@ -59,6 +60,21 @@ export function useInsufficientNativeTokenWarning({
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)
if (!supportedChainId) {
......@@ -70,7 +86,7 @@ export function useInsufficientNativeTokenWarning({
const modalOrTooltipMainMessage = (
<Trans
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
// which causes the value `<$0.01` to be incorrectly escaped.
fiatTokenAmount: (
......@@ -82,8 +98,8 @@ export function useInsufficientNativeTokenWarning({
i18nKey="transaction.warning.insufficientGas.modal.message"
values={{
networkName,
tokenSymbol: nativeCurrency?.symbol,
tokenAmount: gasAmount?.toSignificant(2),
tokenSymbol: nativeCurrency.symbol,
tokenAmount: gasAmount.toSignificant(2),
}}
/>
)
......
......@@ -41,8 +41,8 @@ export function SwapReviewTokenWarningCard({
const feeOnTransferOverride =
showFeeSeverityWarning && tokenFeeInfo && feeType
? {
fee: tokenFeeInfo.fee,
feeType,
buyFeePercent: feeType === 'buy' ? feePercent : undefined,
sellFeePercent: feeType === 'sell' ? feePercent : undefined,
}
: undefined
......@@ -59,7 +59,7 @@ export function SwapReviewTokenWarningCard({
hideCtaIcon
currencyInfo={currencyInfoToDisplay}
tokenProtectionWarningOverride={tokenProtectionWarningToDisplay}
feePercentOverride={showFeeSeverityWarning ? feePercent : undefined}
feeOnTransferOverride={feeOnTransferOverride}
checked={checked}
setChecked={setChecked}
onPress={onPress}
......
......@@ -22,7 +22,7 @@ export function getFeeSeverity(fee: Percent): {
} {
// 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
const tokenProtectionWarning = getFeeWarning(fee)
const tokenProtectionWarning = getFeeWarning(parseFloat(fee.toFixed()))
const severity = getSeverityFromTokenProtectionWarning(tokenProtectionWarning)
return { severity, tokenProtectionWarning }
}
......
......@@ -15,3 +15,11 @@ export const getShouldResetExactAmountToken = (
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'
import { useEffect, useMemo, useRef } from 'react'
import { WithV4Flag } from 'uniswap/src/data/apiClients/tradingApi/TradingApiClient'
import { useTradingApiSwapQuery } from 'uniswap/src/data/apiClients/tradingApi/useTradingApiSwapQuery'
import { getTradeSettingsDeadline } from 'uniswap/src/data/apiClients/tradingApi/utils/getTradeSettingsDeadline'
import {
CreateSwapRequest,
NullablePermit,
......@@ -19,6 +18,7 @@ import { useDynamicConfigValue, useFeatureFlag } from 'uniswap/src/features/gati
import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
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 { useWrapTransactionRequest } from 'uniswap/src/features/transactions/swap/hooks/useWrapTransactionRequest'
import { useSwapSettingsContext } from 'uniswap/src/features/transactions/swap/settings/contexts/SwapSettingsContext'
......
......@@ -8,7 +8,6 @@ import {
USDC_ASTROCHAIN_SEPOLIA,
USDC_AVALANCHE,
USDC_BASE,
USDC_BNB,
USDC_CELO,
USDC_OPTIMISM,
USDC_POLYGON,
......@@ -16,6 +15,7 @@ import {
USDC_WORLD_CHAIN,
USDC_ZKSYNC,
USDC_ZORA,
USDT_BNB,
} from 'uniswap/src/constants/tokens'
import { UniverseChainId } from 'uniswap/src/features/chains/types'
import { useTrade } from 'uniswap/src/features/transactions/swap/hooks/useTrade'
......@@ -29,7 +29,7 @@ export const STABLECOIN_AMOUNT_OUT: { [chainId: number]: CurrencyAmount<Token> }
[UniverseChainId.Sepolia]: CurrencyAmount.fromRawAmount(USDC_SEPOLIA, 100_000e6),
[UniverseChainId.ArbitrumOne]: CurrencyAmount.fromRawAmount(USDC_ARBITRUM, 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.Optimism]: CurrencyAmount.fromRawAmount(USDC_OPTIMISM, 10_000e6),
[UniverseChainId.Blast]: CurrencyAmount.fromRawAmount(USDB_BLAST, 10_000e18),
......
......@@ -32,7 +32,13 @@ export function FeeOnTransferWarning({
const { tokenProtectionWarning } = getFeeSeverity(feeInfo.fee)
const title = getModalHeaderText({ t, tokenProtectionWarning, tokenSymbol0: tokenSymbol }) ?? ''
const subtitle =
getModalSubtitleTokenWarningText({ t, tokenProtectionWarning, tokenSymbol, formattedFeePercent }) ?? ''
getModalSubtitleTokenWarningText({
t,
tokenProtectionWarning,
tokenSymbol,
formattedBuyFeePercent: feeType === 'buy' ? formattedFeePercent : undefined,
formattedSellFeePercent: feeType === 'sell' ? formattedFeePercent : undefined,
}) ?? ''
if (isInterface) {
return (
......@@ -77,8 +83,8 @@ export function FeeOnTransferWarning({
isVisible={showModal}
currencyInfo0={feeInfo.currencyInfo}
feeOnTransferOverride={{
fee: feeInfo.fee,
feeType,
buyFeePercent: feeType === 'buy' ? feePercent : undefined,
sellFeePercent: feeType === 'sell' ? feePercent : undefined,
}}
closeModalOnly={onClose}
onAcknowledge={onClose}
......
......@@ -197,6 +197,15 @@ export function computeRoutes(
}
})
} 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
}
}
......@@ -206,6 +215,11 @@ function parseTokenApi(token: TradingApiTokenInRoute): Token {
if (!chainId || !address || !decimals || !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(
chainId,
address,
......@@ -228,8 +242,16 @@ function parseV4PoolApi({
tokenIn,
tokenOut,
}: TradingApiV4PoolInRoute): V4Pool {
const currencyIn = parseTokenApi(tokenIn)
const currencyOut = parseTokenApi(tokenOut)
if (!tokenIn.address || !tokenOut.address || !tokenIn.chainId || !tokenOut.chainId) {
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(
currencyIn,
......
......@@ -149,6 +149,7 @@ export type FinalizedTransactionDetails = TransactionDetails &
export type TransactionOptions = {
request: providers.TransactionRequest
submittedTimestampMs?: number
timeoutTimestampMs?: number
submitViaPrivateRpc?: boolean
}
......
......@@ -837,8 +837,8 @@
"fee.tier.create": "Create 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.description": "The amount earned providing liquidity. 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": "The amount earned facilitating trades. Choose an amount that suits your risk tolerance and strategy.",
"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.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.",
......@@ -1996,11 +1996,11 @@
"token.safety.warning.fotLow.title": "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.title": "Honeypot detected",
"token.safety.warning.honeypot.title": "100% sell fee detected",
"token.safety.warning.impersonator": "Impersonates another token",
"token.safety.warning.impersonator.title": "Impersonator token detected",
"token.safety.warning.malicious.general.message": "{{tokenSymbol}} has been flagged as malicious.",
"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.general.message": "{{tokenSymbol}} has been flagged as malicious by Blockaid.",
"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.title": "Malicious token detected",
"token.safety.warning.mayResultInLoss": "Swapping it may result in a loss of funds.",
......@@ -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.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.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.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_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.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.message": "You can’t trade this token using the Uniswap Wallet.",
"token.safetyLevel.medium.header": "Caution",
......
......@@ -177,7 +177,6 @@
"common.approvePending": "Godkendelse afventer...",
"common.approveSpend": "Godkend {{symbol}} udgifter",
"common.approving": "Godkender",
"common.areYouSure": "Er du sikker?",
"common.automatic": "Auto",
"common.availableIn": "Uniswap tilgængelig i: <locale />",
"common.availableOnIOSAndroid": "Tilgængelig på iOS og Android",
......@@ -650,7 +649,6 @@
"common.unwrap.failed": "Udpakningen mislykkedes",
"common.unwrapped": "Udpakket",
"common.unwrapping": "Udpakning",
"common.version": "Version",
"common.view.profile": "Se profil",
"common.viewOnBlockExplorer": "Se på Block Explorer",
"common.viewOnExplorer": "Se på Explorer",
......@@ -837,8 +835,8 @@
"fee.tier.create": "Opret 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.description": "Beløbet tjent for at lette handler. 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": "Det tjente beløb giver likviditet. Vælg et beløb, der passer til din risikotolerance og strategi.",
"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.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.",
......@@ -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.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.resetDescription": "Dine tokens, pris og områdevalg nulstilles.",
"position.setRange": "Sæt prisinterval",
"position.setRange.inputsBelow": "Brug nedenstående input til at indstille dit område.",
"position.step.deposit": "Indtast indbetalingsbeløb",
......@@ -1540,7 +1537,6 @@
"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.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.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",
......@@ -1761,7 +1757,6 @@
"swap.approveInWallet": "Godkend i din tegnebog",
"swap.balance.amount": "Saldo: {{amount}}",
"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.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.",
......@@ -1848,7 +1843,6 @@
"swap.settings.protection.subtitle.unavailable": "Ikke tilgængelig på {{chainName}}",
"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.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.v3.title": "v3 puljer",
"swap.settings.routingPreference.option.v4.title": "v4 puljer",
......@@ -1877,7 +1871,6 @@
"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.total": "Total",
"swap.tradeRoutes": "Handelsruter",
"swap.transaction.deadline": "Transaktionsfrist",
"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",
......@@ -1916,6 +1909,7 @@
"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.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.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",
......@@ -1996,11 +1990,11 @@
"token.safety.warning.fotLow.title": "Gebyr opdaget",
"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.title": "Honeypot opdaget",
"token.safety.warning.honeypot.title": "100 % salgsgebyr registreret",
"token.safety.warning.impersonator": "Efterligner et andet token",
"token.safety.warning.impersonator.title": "Imitator-token fundet",
"token.safety.warning.malicious.general.message": "{{tokenSymbol}} er blevet markeret som ondsindet.",
"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.general.message": "{{tokenSymbol}} er blevet markeret som ondsindet af Blockaid.",
"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.title": "Ondsindet token fundet",
"token.safety.warning.mayResultInLoss": "At bytte det kan resultere i et tab af midler.",
......@@ -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_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.notAvailableToTrade": "Ikke tilgængelig for handel",
"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.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_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.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.message": "Du kan ikke bytte dette token ved at bruge Uniswap Wallet.",
"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