feat: updated slippage ui (#7409)
* feat: updated slippage ui * fix: update settings to also have period in max slippage string * test: update e2e test search string
This commit is contained in:
parent
e6519a7dd1
commit
1be62f0bec
@ -6,7 +6,7 @@ describe('Swap settings', () => {
|
|||||||
cy.contains('Settings').should('not.exist')
|
cy.contains('Settings').should('not.exist')
|
||||||
cy.get(getTestSelector('open-settings-dialog-button')).click()
|
cy.get(getTestSelector('open-settings-dialog-button')).click()
|
||||||
cy.get(getTestSelector('mobile-settings-menu')).should('not.exist')
|
cy.get(getTestSelector('mobile-settings-menu')).should('not.exist')
|
||||||
cy.contains('Max slippage').should('exist')
|
cy.contains('Max. slippage').should('exist')
|
||||||
cy.contains('Transaction deadline').should('exist')
|
cy.contains('Transaction deadline').should('exist')
|
||||||
cy.contains('UniswapX').should('exist')
|
cy.contains('UniswapX').should('exist')
|
||||||
cy.contains('Local routing').should('exist')
|
cy.contains('Local routing').should('exist')
|
||||||
@ -26,7 +26,7 @@ describe('Swap settings', () => {
|
|||||||
cy.get(getTestSelector('mobile-settings-menu'))
|
cy.get(getTestSelector('mobile-settings-menu'))
|
||||||
.should('exist')
|
.should('exist')
|
||||||
.within(() => {
|
.within(() => {
|
||||||
cy.contains('Max slippage').should('exist')
|
cy.contains('Max. slippage').should('exist')
|
||||||
cy.contains('UniswapX').should('exist')
|
cy.contains('UniswapX').should('exist')
|
||||||
cy.contains('Local routing').should('exist')
|
cy.contains('Local routing').should('exist')
|
||||||
cy.contains('Transaction deadline').should('exist')
|
cy.contains('Transaction deadline').should('exist')
|
||||||
|
@ -46,7 +46,7 @@ describe('MaxSlippageSettings', () => {
|
|||||||
|
|
||||||
fireEvent.change(getSlippageInput(), { target: { value: '0.5' } })
|
fireEvent.change(getSlippageInput(), { target: { value: '0.5' } })
|
||||||
|
|
||||||
expect(screen.queryAllByText('0.50%').length).toEqual(1)
|
expect(screen.queryAllByText('0.5%').length).toEqual(1)
|
||||||
})
|
})
|
||||||
it('updates input value on blur with the slippage in store', () => {
|
it('updates input value on blur with the slippage in store', () => {
|
||||||
renderSlippageSettings()
|
renderSlippageSettings()
|
||||||
@ -56,7 +56,7 @@ describe('MaxSlippageSettings', () => {
|
|||||||
fireEvent.change(input, { target: { value: '0.5' } })
|
fireEvent.change(input, { target: { value: '0.5' } })
|
||||||
fireEvent.blur(input)
|
fireEvent.blur(input)
|
||||||
|
|
||||||
expect(input.value).toBe('0.50')
|
expect(input.value).toBe('0.5')
|
||||||
})
|
})
|
||||||
it('clears errors on blur and overwrites incorrect value with the latest correct value', () => {
|
it('clears errors on blur and overwrites incorrect value with the latest correct value', () => {
|
||||||
renderSlippageSettings()
|
renderSlippageSettings()
|
||||||
@ -68,7 +68,7 @@ describe('MaxSlippageSettings', () => {
|
|||||||
fireEvent.change(input, { target: { value: '500' } })
|
fireEvent.change(input, { target: { value: '500' } })
|
||||||
fireEvent.blur(input)
|
fireEvent.blur(input)
|
||||||
|
|
||||||
expect(input.value).toBe('50.00')
|
expect(input.value).toBe('50')
|
||||||
})
|
})
|
||||||
it('does not allow to enter more than 2 digits after the decimal point', () => {
|
it('does not allow to enter more than 2 digits after the decimal point', () => {
|
||||||
renderSlippageSettings()
|
renderSlippageSettings()
|
||||||
|
@ -8,6 +8,7 @@ import { useUserSlippageTolerance } from 'state/user/hooks'
|
|||||||
import { SlippageTolerance } from 'state/user/types'
|
import { SlippageTolerance } from 'state/user/types'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import { CautionTriangle, ThemedText } from 'theme/components'
|
import { CautionTriangle, ThemedText } from 'theme/components'
|
||||||
|
import { useFormatter } from 'utils/formatNumbers'
|
||||||
|
|
||||||
import { Input, InputContainer } from '../Input'
|
import { Input, InputContainer } from '../Input'
|
||||||
|
|
||||||
@ -37,15 +38,23 @@ const NUMBER_WITH_MAX_TWO_DECIMAL_PLACES = /^(?:\d*\.\d{0,2}|\d+)$/
|
|||||||
const MINIMUM_RECOMMENDED_SLIPPAGE = new Percent(5, 10_000)
|
const MINIMUM_RECOMMENDED_SLIPPAGE = new Percent(5, 10_000)
|
||||||
const MAXIMUM_RECOMMENDED_SLIPPAGE = new Percent(1, 100)
|
const MAXIMUM_RECOMMENDED_SLIPPAGE = new Percent(1, 100)
|
||||||
|
|
||||||
|
function useFormatSlippageInput() {
|
||||||
|
const { formatSlippage } = useFormatter()
|
||||||
|
|
||||||
|
return (slippage: Percent) => formatSlippage(slippage).slice(0, -1) // remove % sign
|
||||||
|
}
|
||||||
|
|
||||||
export default function MaxSlippageSettings({ autoSlippage }: { autoSlippage: Percent }) {
|
export default function MaxSlippageSettings({ autoSlippage }: { autoSlippage: Percent }) {
|
||||||
const [userSlippageTolerance, setUserSlippageTolerance] = useUserSlippageTolerance()
|
const [userSlippageTolerance, setUserSlippageTolerance] = useUserSlippageTolerance()
|
||||||
|
const { formatSlippage } = useFormatter()
|
||||||
|
const formatSlippageInput = useFormatSlippageInput()
|
||||||
|
|
||||||
// In order to trigger `custom` mode, we need to set `userSlippageTolerance` to a value that is not `auto`.
|
// In order to trigger `custom` mode, we need to set `userSlippageTolerance` to a value that is not `auto`.
|
||||||
// To do so, we use `autoSlippage` value. However, since users are likely to change that value,
|
// To do so, we use `autoSlippage` value. However, since users are likely to change that value,
|
||||||
// we render it as a placeholder instead of a value.
|
// we render it as a placeholder instead of a value.
|
||||||
const defaultSlippageInputValue =
|
const defaultSlippageInputValue =
|
||||||
userSlippageTolerance !== SlippageTolerance.Auto && !userSlippageTolerance.equalTo(autoSlippage)
|
userSlippageTolerance !== SlippageTolerance.Auto && !userSlippageTolerance.equalTo(autoSlippage)
|
||||||
? userSlippageTolerance.toFixed(2)
|
? formatSlippageInput(userSlippageTolerance)
|
||||||
: ''
|
: ''
|
||||||
|
|
||||||
// If user has previously entered a custom slippage, we want to show that value in the input field
|
// If user has previously entered a custom slippage, we want to show that value in the input field
|
||||||
@ -101,7 +110,7 @@ export default function MaxSlippageSettings({ autoSlippage }: { autoSlippage: Pe
|
|||||||
header={
|
header={
|
||||||
<Row width="auto">
|
<Row width="auto">
|
||||||
<ThemedText.BodySecondary>
|
<ThemedText.BodySecondary>
|
||||||
<Trans>Max slippage</Trans>
|
<Trans>Max. slippage</Trans>
|
||||||
</ThemedText.BodySecondary>
|
</ThemedText.BodySecondary>
|
||||||
<QuestionHelper
|
<QuestionHelper
|
||||||
text={
|
text={
|
||||||
@ -115,7 +124,7 @@ export default function MaxSlippageSettings({ autoSlippage }: { autoSlippage: Pe
|
|||||||
{userSlippageTolerance === SlippageTolerance.Auto ? (
|
{userSlippageTolerance === SlippageTolerance.Auto ? (
|
||||||
<Trans>Auto</Trans>
|
<Trans>Auto</Trans>
|
||||||
) : (
|
) : (
|
||||||
`${userSlippageTolerance.toFixed(2)}%`
|
formatSlippage(userSlippageTolerance)
|
||||||
)}
|
)}
|
||||||
</ThemedText.BodyPrimary>
|
</ThemedText.BodyPrimary>
|
||||||
}
|
}
|
||||||
@ -149,7 +158,7 @@ export default function MaxSlippageSettings({ autoSlippage }: { autoSlippage: Pe
|
|||||||
<InputContainer gap="md" error={!!slippageError}>
|
<InputContainer gap="md" error={!!slippageError}>
|
||||||
<Input
|
<Input
|
||||||
data-testid="slippage-input"
|
data-testid="slippage-input"
|
||||||
placeholder={autoSlippage.toFixed(2)}
|
placeholder={formatSlippageInput(autoSlippage)}
|
||||||
value={slippageInput}
|
value={slippageInput}
|
||||||
onChange={(e) => parseSlippageInput(e.target.value)}
|
onChange={(e) => parseSlippageInput(e.target.value)}
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
@ -167,7 +176,7 @@ export default function MaxSlippageSettings({ autoSlippage }: { autoSlippage: Pe
|
|||||||
<ThemedText.BodySmall color="deprecated_accentWarning">
|
<ThemedText.BodySmall color="deprecated_accentWarning">
|
||||||
{tooLow ? (
|
{tooLow ? (
|
||||||
<Trans>
|
<Trans>
|
||||||
Slippage below {MINIMUM_RECOMMENDED_SLIPPAGE.toFixed(2)}% may result in a failed transaction
|
Slippage below {formatSlippage(MINIMUM_RECOMMENDED_SLIPPAGE)} may result in a failed transaction
|
||||||
</Trans>
|
</Trans>
|
||||||
) : (
|
) : (
|
||||||
<Trans>Your transaction may be frontrun and result in an unfavorable trade.</Trans>
|
<Trans>Your transaction may be frontrun and result in an unfavorable trade.</Trans>
|
||||||
|
@ -5,6 +5,7 @@ import { useUserSlippageTolerance } from 'state/user/hooks'
|
|||||||
import { SlippageTolerance } from 'state/user/types'
|
import { SlippageTolerance } from 'state/user/types'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import { ThemedText } from 'theme/components'
|
import { ThemedText } from 'theme/components'
|
||||||
|
import { useFormatter } from 'utils/formatNumbers'
|
||||||
import validateUserSlippageTolerance, { SlippageValidationResult } from 'utils/validateUserSlippageTolerance'
|
import validateUserSlippageTolerance, { SlippageValidationResult } from 'utils/validateUserSlippageTolerance'
|
||||||
|
|
||||||
const Icon = styled(Settings)`
|
const Icon = styled(Settings)`
|
||||||
@ -46,6 +47,7 @@ const IconContainerWithSlippage = styled(IconContainer)<{ displayWarning?: boole
|
|||||||
|
|
||||||
const ButtonContent = () => {
|
const ButtonContent = () => {
|
||||||
const [userSlippageTolerance] = useUserSlippageTolerance()
|
const [userSlippageTolerance] = useUserSlippageTolerance()
|
||||||
|
const { formatSlippage } = useFormatter()
|
||||||
|
|
||||||
if (userSlippageTolerance === SlippageTolerance.Auto) {
|
if (userSlippageTolerance === SlippageTolerance.Auto) {
|
||||||
return (
|
return (
|
||||||
@ -60,7 +62,7 @@ const ButtonContent = () => {
|
|||||||
return (
|
return (
|
||||||
<IconContainerWithSlippage data-testid="settings-icon-with-slippage" gap="sm" displayWarning={isInvalidSlippage}>
|
<IconContainerWithSlippage data-testid="settings-icon-with-slippage" gap="sm" displayWarning={isInvalidSlippage}>
|
||||||
<ThemedText.BodySmall>
|
<ThemedText.BodySmall>
|
||||||
<Trans>{userSlippageTolerance.toFixed(2)}% slippage</Trans>
|
<Trans>{formatSlippage(userSlippageTolerance)} slippage</Trans>
|
||||||
</ThemedText.BodySmall>
|
</ThemedText.BodySmall>
|
||||||
<Icon />
|
<Icon />
|
||||||
</IconContainerWithSlippage>
|
</IconContainerWithSlippage>
|
||||||
|
53
src/components/swap/MaxSlippageTooltip.tsx
Normal file
53
src/components/swap/MaxSlippageTooltip.tsx
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { Trans } from '@lingui/macro'
|
||||||
|
import { Percent, TradeType } from '@uniswap/sdk-core'
|
||||||
|
import Column from 'components/Column'
|
||||||
|
import { RowBetween } from 'components/Row'
|
||||||
|
import { InterfaceTrade } from 'state/routing/types'
|
||||||
|
import { ExternalLink, Separator, ThemedText } from 'theme/components'
|
||||||
|
import { NumberType, useFormatter } from 'utils/formatNumbers'
|
||||||
|
|
||||||
|
const ExactInMessage = ({ amount }: { amount: string }) => (
|
||||||
|
<Trans>
|
||||||
|
If the price moves so that you will receive less than {amount}, your transaction will be reverted. This is the
|
||||||
|
minimum amount you are guaranteed to receive.
|
||||||
|
</Trans>
|
||||||
|
)
|
||||||
|
|
||||||
|
const ExactOutMessage = ({ amount }: { amount: string }) => (
|
||||||
|
<Trans>
|
||||||
|
If the price moves so that you will pay more than {amount}, your transaction will be reverted. This is the maximum
|
||||||
|
amount you are guaranteed to pay.
|
||||||
|
</Trans>
|
||||||
|
)
|
||||||
|
|
||||||
|
function SlippageHeader({ amount, isExactIn }: { amount: string; isExactIn: boolean }) {
|
||||||
|
return (
|
||||||
|
<RowBetween>
|
||||||
|
<ThemedText.Caption color="neutral1">
|
||||||
|
{isExactIn ? <Trans>Receive at least</Trans> : <Trans>Pay at most</Trans>}
|
||||||
|
</ThemedText.Caption>
|
||||||
|
<ThemedText.Caption color="neutral1">{amount}</ThemedText.Caption>
|
||||||
|
</RowBetween>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MaxSlippageTooltip({ trade, allowedSlippage }: { trade: InterfaceTrade; allowedSlippage: Percent }) {
|
||||||
|
const isExactIn = trade.tradeType === TradeType.EXACT_INPUT
|
||||||
|
const amount = isExactIn ? trade.minimumAmountOut(allowedSlippage) : trade.maximumAmountIn(allowedSlippage)
|
||||||
|
|
||||||
|
const formattedAmount = useFormatter().formatCurrencyAmount({ amount, type: NumberType.SwapDetailsAmount })
|
||||||
|
const displayAmount = `${formattedAmount} ${amount.currency.symbol}`
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Column gap="xs">
|
||||||
|
<SlippageHeader amount={displayAmount} isExactIn={isExactIn} />
|
||||||
|
<Separator />
|
||||||
|
<div>
|
||||||
|
{isExactIn ? <ExactInMessage amount={displayAmount} /> : <ExactOutMessage amount={displayAmount} />}{' '}
|
||||||
|
<ExternalLink href="https://support.uniswap.org/hc/en-us/articles/8643879653261-What-is-Price-Slippage-">
|
||||||
|
Learn more
|
||||||
|
</ExternalLink>
|
||||||
|
</div>
|
||||||
|
</Column>
|
||||||
|
)
|
||||||
|
}
|
@ -108,11 +108,9 @@ function AdvancedSwapDetails(props: SwapDetailsProps & { open: boolean }) {
|
|||||||
<SwapDetailsWrapper gap="md" data-testid="advanced-swap-details">
|
<SwapDetailsWrapper gap="md" data-testid="advanced-swap-details">
|
||||||
<Separator />
|
<Separator />
|
||||||
<SwapLineItem {...lineItemProps} type={SwapLineItemType.PRICE_IMPACT} />
|
<SwapLineItem {...lineItemProps} type={SwapLineItemType.PRICE_IMPACT} />
|
||||||
|
<SwapLineItem {...lineItemProps} type={SwapLineItemType.MAX_SLIPPAGE} />
|
||||||
<SwapLineItem {...lineItemProps} type={SwapLineItemType.INPUT_TOKEN_FEE_ON_TRANSFER} />
|
<SwapLineItem {...lineItemProps} type={SwapLineItemType.INPUT_TOKEN_FEE_ON_TRANSFER} />
|
||||||
<SwapLineItem {...lineItemProps} type={SwapLineItemType.OUTPUT_TOKEN_FEE_ON_TRANSFER} />
|
<SwapLineItem {...lineItemProps} type={SwapLineItemType.OUTPUT_TOKEN_FEE_ON_TRANSFER} />
|
||||||
<SwapLineItem {...lineItemProps} type={SwapLineItemType.MAXIMUM_INPUT} />
|
|
||||||
<SwapLineItem {...lineItemProps} type={SwapLineItemType.MINIMUM_OUTPUT} />
|
|
||||||
<SwapLineItem {...lineItemProps} type={SwapLineItemType.EXPECTED_OUTPUT} />
|
|
||||||
<SwapLineItem {...lineItemProps} type={SwapLineItemType.NETWORK_COST} />
|
<SwapLineItem {...lineItemProps} type={SwapLineItemType.NETWORK_COST} />
|
||||||
<Separator />
|
<Separator />
|
||||||
<SwapLineItem {...lineItemProps} type={SwapLineItemType.ROUTING_INFO} />
|
<SwapLineItem {...lineItemProps} type={SwapLineItemType.ROUTING_INFO} />
|
||||||
|
@ -11,12 +11,15 @@ import { useIsMobile } from 'nft/hooks'
|
|||||||
import React, { PropsWithChildren, useEffect, useState } from 'react'
|
import React, { PropsWithChildren, useEffect, useState } from 'react'
|
||||||
import { InterfaceTrade, TradeFillType } from 'state/routing/types'
|
import { InterfaceTrade, TradeFillType } from 'state/routing/types'
|
||||||
import { isPreviewTrade, isUniswapXTrade } from 'state/routing/utils'
|
import { isPreviewTrade, isUniswapXTrade } from 'state/routing/utils'
|
||||||
|
import { useUserSlippageTolerance } from 'state/user/hooks'
|
||||||
|
import { SlippageTolerance } from 'state/user/types'
|
||||||
import styled, { DefaultTheme } from 'styled-components'
|
import styled, { DefaultTheme } from 'styled-components'
|
||||||
import { ExternalLink, ThemedText } from 'theme/components'
|
import { ExternalLink, ThemedText } from 'theme/components'
|
||||||
import { NumberType, useFormatter } from 'utils/formatNumbers'
|
import { NumberType, useFormatter } from 'utils/formatNumbers'
|
||||||
import { getPriceImpactColor } from 'utils/prices'
|
import { getPriceImpactColor } from 'utils/prices'
|
||||||
|
|
||||||
import { GasBreakdownTooltip, UniswapXDescription } from './GasBreakdownTooltip'
|
import { GasBreakdownTooltip, UniswapXDescription } from './GasBreakdownTooltip'
|
||||||
|
import { MaxSlippageTooltip } from './MaxSlippageTooltip'
|
||||||
import SwapRoute from './SwapRoute'
|
import SwapRoute from './SwapRoute'
|
||||||
|
|
||||||
export enum SwapLineItemType {
|
export enum SwapLineItemType {
|
||||||
@ -25,9 +28,9 @@ export enum SwapLineItemType {
|
|||||||
INPUT_TOKEN_FEE_ON_TRANSFER,
|
INPUT_TOKEN_FEE_ON_TRANSFER,
|
||||||
OUTPUT_TOKEN_FEE_ON_TRANSFER,
|
OUTPUT_TOKEN_FEE_ON_TRANSFER,
|
||||||
PRICE_IMPACT,
|
PRICE_IMPACT,
|
||||||
|
MAX_SLIPPAGE,
|
||||||
MAXIMUM_INPUT,
|
MAXIMUM_INPUT,
|
||||||
MINIMUM_OUTPUT,
|
MINIMUM_OUTPUT,
|
||||||
EXPECTED_OUTPUT,
|
|
||||||
ROUTING_INFO,
|
ROUTING_INFO,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,6 +46,18 @@ const ColorWrapper = styled.span<{ textColor?: keyof DefaultTheme }>`
|
|||||||
${({ textColor, theme }) => textColor && `color: ${theme[textColor]};`}
|
${({ textColor, theme }) => textColor && `color: ${theme[textColor]};`}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const AutoBadge = styled(ThemedText.LabelMicro).attrs({ fontWeight: 535 })`
|
||||||
|
background: ${({ theme }) => theme.surface3};
|
||||||
|
border-radius: 8px;
|
||||||
|
color: ${({ theme }) => theme.neutral2};
|
||||||
|
height: 20px;
|
||||||
|
padding: 0 6px;
|
||||||
|
|
||||||
|
::after {
|
||||||
|
content: '${t`Auto`}';
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
function FOTTooltipContent() {
|
function FOTTooltipContent() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -91,7 +106,8 @@ type LineItemData = {
|
|||||||
|
|
||||||
function useLineItem(props: SwapLineItemProps): LineItemData | undefined {
|
function useLineItem(props: SwapLineItemProps): LineItemData | undefined {
|
||||||
const { trade, syncing, allowedSlippage, type } = props
|
const { trade, syncing, allowedSlippage, type } = props
|
||||||
const { formatNumber } = useFormatter()
|
const { formatNumber, formatSlippage } = useFormatter()
|
||||||
|
const isAutoSlippage = useUserSlippageTolerance()[0] === SlippageTolerance.Auto
|
||||||
|
|
||||||
const isUniswapX = isUniswapXTrade(trade)
|
const isUniswapX = isUniswapXTrade(trade)
|
||||||
const isPreview = isPreviewTrade(trade)
|
const isPreview = isPreviewTrade(trade)
|
||||||
@ -132,10 +148,20 @@ function useLineItem(props: SwapLineItemProps): LineItemData | undefined {
|
|||||||
TooltipBody: () => <Trans>The impact your trade has on the market price of this pool.</Trans>,
|
TooltipBody: () => <Trans>The impact your trade has on the market price of this pool.</Trans>,
|
||||||
Value: () => (isPreview ? <Loading /> : <ColoredPercentRow percent={trade.priceImpact} />),
|
Value: () => (isPreview ? <Loading /> : <ColoredPercentRow percent={trade.priceImpact} />),
|
||||||
}
|
}
|
||||||
|
case SwapLineItemType.MAX_SLIPPAGE:
|
||||||
|
return {
|
||||||
|
Label: () => <Trans>Max. slippage</Trans>,
|
||||||
|
TooltipBody: () => <MaxSlippageTooltip {...props} />,
|
||||||
|
Value: () => (
|
||||||
|
<Row gap="8px">
|
||||||
|
{isAutoSlippage && <AutoBadge />} {formatSlippage(allowedSlippage)}
|
||||||
|
</Row>
|
||||||
|
),
|
||||||
|
}
|
||||||
case SwapLineItemType.MAXIMUM_INPUT:
|
case SwapLineItemType.MAXIMUM_INPUT:
|
||||||
if (trade.tradeType === TradeType.EXACT_INPUT) return
|
if (trade.tradeType === TradeType.EXACT_INPUT) return
|
||||||
return {
|
return {
|
||||||
Label: () => <Trans>Maximum input</Trans>,
|
Label: () => <Trans>Pay at most</Trans>,
|
||||||
TooltipBody: () => (
|
TooltipBody: () => (
|
||||||
<Trans>
|
<Trans>
|
||||||
The maximum amount you are guaranteed to spend. If the price slips any further, your transaction will
|
The maximum amount you are guaranteed to spend. If the price slips any further, your transaction will
|
||||||
@ -148,7 +174,7 @@ function useLineItem(props: SwapLineItemProps): LineItemData | undefined {
|
|||||||
case SwapLineItemType.MINIMUM_OUTPUT:
|
case SwapLineItemType.MINIMUM_OUTPUT:
|
||||||
if (trade.tradeType === TradeType.EXACT_OUTPUT) return
|
if (trade.tradeType === TradeType.EXACT_OUTPUT) return
|
||||||
return {
|
return {
|
||||||
Label: () => <Trans>Minimum output</Trans>,
|
Label: () => <Trans>Receive at least</Trans>,
|
||||||
TooltipBody: () => (
|
TooltipBody: () => (
|
||||||
<Trans>
|
<Trans>
|
||||||
The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will
|
The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will
|
||||||
@ -158,18 +184,6 @@ function useLineItem(props: SwapLineItemProps): LineItemData | undefined {
|
|||||||
Value: () => <CurrencyAmountRow amount={trade.minimumAmountOut(allowedSlippage)} />,
|
Value: () => <CurrencyAmountRow amount={trade.minimumAmountOut(allowedSlippage)} />,
|
||||||
loaderWidth: 70,
|
loaderWidth: 70,
|
||||||
}
|
}
|
||||||
case SwapLineItemType.EXPECTED_OUTPUT:
|
|
||||||
return {
|
|
||||||
Label: () => <Trans>Expected output</Trans>,
|
|
||||||
TooltipBody: () => (
|
|
||||||
<Trans>
|
|
||||||
The amount you expect to receive at the current market price. You may receive less or more if the market
|
|
||||||
price changes while your transaction is pending.
|
|
||||||
</Trans>
|
|
||||||
),
|
|
||||||
Value: () => <CurrencyAmountRow amount={trade.postTaxOutputAmount} />,
|
|
||||||
loaderWidth: 65,
|
|
||||||
}
|
|
||||||
case SwapLineItemType.ROUTING_INFO:
|
case SwapLineItemType.ROUTING_INFO:
|
||||||
if (isPreview) return { Label: () => <Trans>Order routing</Trans>, Value: () => <Loading /> }
|
if (isPreview) return { Label: () => <Trans>Order routing</Trans>, Value: () => <Loading /> }
|
||||||
return {
|
return {
|
||||||
|
@ -74,10 +74,11 @@ export default function SwapModalFooter({
|
|||||||
<DetailsContainer gap="md">
|
<DetailsContainer gap="md">
|
||||||
<SwapLineItem {...lineItemProps} type={SwapLineItemType.EXCHANGE_RATE} />
|
<SwapLineItem {...lineItemProps} type={SwapLineItemType.EXCHANGE_RATE} />
|
||||||
<SwapLineItem {...lineItemProps} type={SwapLineItemType.PRICE_IMPACT} />
|
<SwapLineItem {...lineItemProps} type={SwapLineItemType.PRICE_IMPACT} />
|
||||||
<SwapLineItem {...lineItemProps} type={SwapLineItemType.INPUT_TOKEN_FEE_ON_TRANSFER} />
|
<SwapLineItem {...lineItemProps} type={SwapLineItemType.MAX_SLIPPAGE} />
|
||||||
<SwapLineItem {...lineItemProps} type={SwapLineItemType.OUTPUT_TOKEN_FEE_ON_TRANSFER} />
|
|
||||||
<SwapLineItem {...lineItemProps} type={SwapLineItemType.MAXIMUM_INPUT} />
|
<SwapLineItem {...lineItemProps} type={SwapLineItemType.MAXIMUM_INPUT} />
|
||||||
<SwapLineItem {...lineItemProps} type={SwapLineItemType.MINIMUM_OUTPUT} />
|
<SwapLineItem {...lineItemProps} type={SwapLineItemType.MINIMUM_OUTPUT} />
|
||||||
|
<SwapLineItem {...lineItemProps} type={SwapLineItemType.INPUT_TOKEN_FEE_ON_TRANSFER} />
|
||||||
|
<SwapLineItem {...lineItemProps} type={SwapLineItemType.OUTPUT_TOKEN_FEE_ON_TRANSFER} />
|
||||||
<SwapLineItem {...lineItemProps} type={SwapLineItemType.NETWORK_COST} />
|
<SwapLineItem {...lineItemProps} type={SwapLineItemType.NETWORK_COST} />
|
||||||
</DetailsContainer>
|
</DetailsContainer>
|
||||||
{showAcceptChanges ? (
|
{showAcceptChanges ? (
|
||||||
|
@ -43,6 +43,24 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
|
|||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.c21 {
|
||||||
|
width: 100%;
|
||||||
|
display: -webkit-box;
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: -ms-flexbox;
|
||||||
|
display: flex;
|
||||||
|
padding: 0;
|
||||||
|
-webkit-align-items: center;
|
||||||
|
-webkit-box-align: center;
|
||||||
|
-ms-flex-align: center;
|
||||||
|
align-items: center;
|
||||||
|
-webkit-box-pack: start;
|
||||||
|
-webkit-justify-content: flex-start;
|
||||||
|
-ms-flex-pack: start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.c4 {
|
.c4 {
|
||||||
-webkit-box-pack: justify;
|
-webkit-box-pack: justify;
|
||||||
-webkit-justify-content: space-between;
|
-webkit-justify-content: space-between;
|
||||||
@ -138,6 +156,18 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
|
|||||||
color: #7D7D7D;
|
color: #7D7D7D;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.c22 {
|
||||||
|
background: #22222212;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #7D7D7D;
|
||||||
|
height: 20px;
|
||||||
|
padding: 0 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c22::after {
|
||||||
|
content: 'Auto';
|
||||||
|
}
|
||||||
|
|
||||||
.c8 {
|
.c8 {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
@ -329,7 +359,7 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
|
|||||||
class="c9 c19 css-142zc9n"
|
class="c9 c19 css-142zc9n"
|
||||||
data-testid="swap-li-label"
|
data-testid="swap-li-label"
|
||||||
>
|
>
|
||||||
Minimum output
|
Max. slippage
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="c12"
|
class="c12"
|
||||||
@ -338,28 +368,14 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
|
|||||||
<div
|
<div
|
||||||
class="c9 c20 css-142zc9n"
|
class="c9 c20 css-142zc9n"
|
||||||
>
|
>
|
||||||
0.00000000000000098 DEF
|
<div
|
||||||
</div>
|
class="c2 c21"
|
||||||
</div>
|
>
|
||||||
</div>
|
<div
|
||||||
</div>
|
class="c14 c22 css-1lgneq0"
|
||||||
<div
|
/>
|
||||||
class="c2 c3 c4"
|
2%
|
||||||
>
|
</div>
|
||||||
<div
|
|
||||||
class="c9 c19 css-142zc9n"
|
|
||||||
data-testid="swap-li-label"
|
|
||||||
>
|
|
||||||
Expected output
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="c12"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="c9 c20 css-142zc9n"
|
|
||||||
>
|
|
||||||
0.000000000000001 DEF
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -26,6 +26,24 @@ exports[`SwapModalFooter.tsx matches base snapshot, test trade exact input 1`] =
|
|||||||
}
|
}
|
||||||
|
|
||||||
.c10 {
|
.c10 {
|
||||||
|
width: 100%;
|
||||||
|
display: -webkit-box;
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: -ms-flexbox;
|
||||||
|
display: flex;
|
||||||
|
padding: 0;
|
||||||
|
-webkit-align-items: center;
|
||||||
|
-webkit-box-align: center;
|
||||||
|
-ms-flex-align: center;
|
||||||
|
align-items: center;
|
||||||
|
-webkit-box-pack: start;
|
||||||
|
-webkit-justify-content: flex-start;
|
||||||
|
-ms-flex-pack: start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c13 {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
display: -webkit-flex;
|
display: -webkit-flex;
|
||||||
@ -54,6 +72,10 @@ exports[`SwapModalFooter.tsx matches base snapshot, test trade exact input 1`] =
|
|||||||
color: #222222;
|
color: #222222;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.c11 {
|
||||||
|
color: #7D7D7D;
|
||||||
|
}
|
||||||
|
|
||||||
.c0 {
|
.c0 {
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
display: -webkit-flex;
|
display: -webkit-flex;
|
||||||
@ -89,6 +111,18 @@ exports[`SwapModalFooter.tsx matches base snapshot, test trade exact input 1`] =
|
|||||||
color: #7D7D7D;
|
color: #7D7D7D;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.c12 {
|
||||||
|
background: #22222212;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #7D7D7D;
|
||||||
|
height: 20px;
|
||||||
|
padding: 0 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c12::after {
|
||||||
|
content: 'Auto';
|
||||||
|
}
|
||||||
|
|
||||||
.c1 {
|
.c1 {
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
}
|
}
|
||||||
@ -143,7 +177,35 @@ exports[`SwapModalFooter.tsx matches base snapshot, test trade exact input 1`] =
|
|||||||
class="c5 c8 css-142zc9n"
|
class="c5 c8 css-142zc9n"
|
||||||
data-testid="swap-li-label"
|
data-testid="swap-li-label"
|
||||||
>
|
>
|
||||||
Minimum output
|
Max. slippage
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="c9"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="c5 c7 css-142zc9n"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="c2 c10"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="c11 c12 css-1lgneq0"
|
||||||
|
/>
|
||||||
|
2%
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="c2 c3 c4"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="c5 c8 css-142zc9n"
|
||||||
|
data-testid="swap-li-label"
|
||||||
|
>
|
||||||
|
Receive at least
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="c9"
|
class="c9"
|
||||||
@ -174,7 +236,7 @@ exports[`SwapModalFooter.tsx matches base snapshot, test trade exact input 1`] =
|
|||||||
class="c5 c7 css-142zc9n"
|
class="c5 c7 css-142zc9n"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="c2 c10"
|
class="c2 c13"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="gas cost icon"
|
alt="gas cost icon"
|
||||||
@ -385,6 +447,24 @@ exports[`SwapModalFooter.tsx renders a preview trade while disabling submission
|
|||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.c11 {
|
||||||
|
width: 100%;
|
||||||
|
display: -webkit-box;
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: -ms-flexbox;
|
||||||
|
display: flex;
|
||||||
|
padding: 0;
|
||||||
|
-webkit-align-items: center;
|
||||||
|
-webkit-box-align: center;
|
||||||
|
-ms-flex-align: center;
|
||||||
|
align-items: center;
|
||||||
|
-webkit-box-pack: start;
|
||||||
|
-webkit-justify-content: flex-start;
|
||||||
|
-ms-flex-pack: start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.c4 {
|
.c4 {
|
||||||
-webkit-box-pack: justify;
|
-webkit-box-pack: justify;
|
||||||
-webkit-justify-content: space-between;
|
-webkit-justify-content: space-between;
|
||||||
@ -396,6 +476,10 @@ exports[`SwapModalFooter.tsx renders a preview trade while disabling submission
|
|||||||
color: #222222;
|
color: #222222;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.c12 {
|
||||||
|
color: #7D7D7D;
|
||||||
|
}
|
||||||
|
|
||||||
.c0 {
|
.c0 {
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
display: -webkit-flex;
|
display: -webkit-flex;
|
||||||
@ -444,6 +528,18 @@ exports[`SwapModalFooter.tsx renders a preview trade while disabling submission
|
|||||||
color: #7D7D7D;
|
color: #7D7D7D;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.c13 {
|
||||||
|
background: #22222212;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #7D7D7D;
|
||||||
|
height: 20px;
|
||||||
|
padding: 0 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c13::after {
|
||||||
|
content: 'Auto';
|
||||||
|
}
|
||||||
|
|
||||||
.c1 {
|
.c1 {
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
}
|
}
|
||||||
@ -503,7 +599,35 @@ exports[`SwapModalFooter.tsx renders a preview trade while disabling submission
|
|||||||
class="c5 c8 css-142zc9n"
|
class="c5 c8 css-142zc9n"
|
||||||
data-testid="swap-li-label"
|
data-testid="swap-li-label"
|
||||||
>
|
>
|
||||||
Minimum output
|
Max. slippage
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="c9"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="c5 c7 css-142zc9n"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="c2 c11"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="c12 c13 css-1lgneq0"
|
||||||
|
/>
|
||||||
|
2%
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="c2 c3 c4"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="c5 c8 css-142zc9n"
|
||||||
|
data-testid="swap-li-label"
|
||||||
|
>
|
||||||
|
Receive at least
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="c9"
|
class="c9"
|
||||||
|
@ -402,25 +402,25 @@ describe('formatSlippage', () => {
|
|||||||
expect(formatSlippage(undefined)).toBe('-')
|
expect(formatSlippage(undefined)).toBe('-')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('correctly formats a percent with 3 significant digits', () => {
|
it('correctly formats a percent with no trailing digits', () => {
|
||||||
const { formatSlippage } = renderHook(() => useFormatter()).result.current
|
const { formatSlippage } = renderHook(() => useFormatter()).result.current
|
||||||
|
|
||||||
expect(formatSlippage(new Percent(1, 100000))).toBe('0.001%')
|
expect(formatSlippage(new Percent(1, 100000))).toBe('0.001%')
|
||||||
expect(formatSlippage(new Percent(1, 1000))).toBe('0.100%')
|
expect(formatSlippage(new Percent(1, 1000))).toBe('0.1%')
|
||||||
expect(formatSlippage(new Percent(1, 100))).toBe('1.000%')
|
expect(formatSlippage(new Percent(1, 100))).toBe('1%')
|
||||||
expect(formatSlippage(new Percent(1, 10))).toBe('10.000%')
|
expect(formatSlippage(new Percent(1, 10))).toBe('10%')
|
||||||
expect(formatSlippage(new Percent(1, 1))).toBe('100.000%')
|
expect(formatSlippage(new Percent(1, 1))).toBe('100%')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('correctly formats a percent with 3 significant digits with french locale', () => {
|
it('correctly formats a percent with french locale', () => {
|
||||||
mocked(useActiveLocale).mockReturnValue('fr-FR')
|
mocked(useActiveLocale).mockReturnValue('fr-FR')
|
||||||
const { formatSlippage } = renderHook(() => useFormatter()).result.current
|
const { formatSlippage } = renderHook(() => useFormatter()).result.current
|
||||||
|
|
||||||
expect(formatSlippage(new Percent(1, 100000))).toBe('0,001%')
|
expect(formatSlippage(new Percent(1, 100000))).toBe('0,001%')
|
||||||
expect(formatSlippage(new Percent(1, 1000))).toBe('0,100%')
|
expect(formatSlippage(new Percent(1, 1000))).toBe('0,1%')
|
||||||
expect(formatSlippage(new Percent(1, 100))).toBe('1,000%')
|
expect(formatSlippage(new Percent(1, 100))).toBe('1%')
|
||||||
expect(formatSlippage(new Percent(1, 10))).toBe('10,000%')
|
expect(formatSlippage(new Percent(1, 10))).toBe('10%')
|
||||||
expect(formatSlippage(new Percent(1, 1))).toBe('100,000%')
|
expect(formatSlippage(new Percent(1, 1))).toBe('100%')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -472,7 +472,6 @@ function formatSlippage(slippage: Percent | undefined, locale: SupportedLocale =
|
|||||||
if (!slippage) return '-'
|
if (!slippage) return '-'
|
||||||
|
|
||||||
return `${Number(slippage.toFixed(3)).toLocaleString(locale, {
|
return `${Number(slippage.toFixed(3)).toLocaleString(locale, {
|
||||||
minimumFractionDigits: 3,
|
|
||||||
maximumFractionDigits: 3,
|
maximumFractionDigits: 3,
|
||||||
useGrouping: false,
|
useGrouping: false,
|
||||||
})}%`
|
})}%`
|
||||||
|
Loading…
Reference in New Issue
Block a user