feat: new review design (#6451)

* test: swap flow cypress tests

* fix: use default parameter

* feat: use Swap Component on TDP

* feat: auto nav for TDP tokens

* chore: merge

* chore: merge

* chore: merge

* chore: merge

* fix: remove extra inputCurrency URL parsing logic

* fix: undo last change

* fix: pass expected chain id to swap component

* fix: search for default tokens on unconnected networks if needed

* test: e2e test for l2 token

* fix: delete irrelevant tests

* fix: address comments

* fix: lint error

* test: update TDP e2e tests

* fix: use pageChainId for filter

* fix: rename chainId

* fix: typecheck

* fix: chainId bug

* fix: chainId required fixes

* fix: bad merge in e2e test

* fix: remove unused test util

* fix: remove unnecessary variable

* fix: token defaults

* fix: address comments

* fix: address comments and fix tests

* fix: e2e test formatting, remove Maybe<>

* fix: remove unused variable

* fix: use feature flag for swap component on TDP

* fix: back button

* feat: copy review screen UI from widgetg

* fix: modal padding

* feat: add final detail row

* fix: remove widget comment

* fix: update unit tests

* fix: code style consistency

* fix: remove padding from AutoColumn

* fix: update snapshots

* fix: use semantic gaps

* fix: more px and gaps

* fix: design feedbacks

* fix: button radius in summary modal

* fix: design nits

* feat: update design of summary modal

* fix: font weight and vertical spacing

* fix: update snapshots

* fix: css nits

* fix: modal flicker when refetching trade

* fix: comments

* fix: code style improvements

* feat: require trade to be defined

* fix: remove extra props from ThemedTexts

* fix: one more trans

* fix: remove unused export

* feat: remove undefined checks and other fixes

* fix: update test

* fix: add missing dollar sign

* fix: remove null check and update test

* fix: remove max width from detail row value

* fix: remove isOpen prop

* fix: isopen
This commit is contained in:
eddie 2023-05-16 15:15:30 -07:00 committed by GitHub
parent 1f755e8b0d
commit 504e09d3dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 1106 additions and 1042 deletions

@ -296,7 +296,7 @@ export function ButtonConfirmed({
}
}
export function ButtonError({ error, ...rest }: { error?: boolean } & ButtonProps) {
export function ButtonError({ error, ...rest }: { error?: boolean } & BaseButtonProps) {
if (error) {
return <ButtonErrorStyle {...rest} />
} else {

@ -1,6 +1,5 @@
import styled, { DefaultTheme } from 'styled-components/macro'
type Gap = keyof DefaultTheme['grids']
import styled from 'styled-components/macro'
import { Gap } from 'theme'
export const Column = styled.div<{
gap?: Gap

@ -1,7 +1,6 @@
import { Box } from 'rebass/styled-components'
import styled, { DefaultTheme } from 'styled-components/macro'
type Gap = keyof DefaultTheme['grids']
import styled from 'styled-components/macro'
import { Gap } from 'theme'
// TODO(WEB-3289):
// Setting `width: 100%` by default prevents composability in complex flex layouts.
@ -14,7 +13,7 @@ const Row = styled(Box)<{
padding?: string
border?: string
borderRadius?: string
gap?: string
gap?: Gap | string
}>`
width: ${({ width }) => width ?? '100%'};
display: flex;

@ -9,6 +9,7 @@ import { useRef } from 'react'
import { useModalIsOpen, useToggleSettingsMenu } from 'state/application/hooks'
import { ApplicationModal } from 'state/application/reducer'
import styled from 'styled-components/macro'
import { Divider } from 'theme'
import MaxSlippageSettings from './MaxSlippageSettings'
import MenuButton from './MenuButton'
@ -40,14 +41,6 @@ const MenuFlyout = styled(AutoColumn)`
padding: 1rem;
`
const Divider = styled.div`
width: 100%;
height: 1px;
border-width: 0;
margin: 0;
background-color: ${({ theme }) => theme.backgroundOutline};
`
export default function SettingsTab({ autoSlippage }: { autoSlippage: Percent }) {
const { chainId } = useWeb3React()
const showDeadlineSettings = Boolean(chainId && !L2_CHAIN_IDS.includes(chainId))

@ -20,7 +20,7 @@ import { TransactionSummary } from '../AccountDetails/TransactionSummary'
import { ButtonLight, ButtonPrimary } from '../Button'
import { AutoColumn, ColumnCenter } from '../Column'
import Modal from '../Modal'
import { RowBetween, RowFixed } from '../Row'
import Row, { RowBetween, RowFixed } from '../Row'
import AnimatedConfirmation from './AnimatedConfirmation'
const Wrapper = styled.div`
@ -28,16 +28,12 @@ const Wrapper = styled.div`
border-radius: 20px;
outline: 1px solid ${({ theme }) => theme.backgroundOutline};
width: 100%;
padding: 1rem;
`
const Section = styled(AutoColumn)<{ inline?: boolean }>`
padding: ${({ inline }) => (inline ? '0' : '0')};
padding: 16px;
`
const BottomSection = styled(Section)`
const BottomSection = styled(AutoColumn)`
border-bottom-left-radius: 20px;
border-bottom-right-radius: 20px;
padding-bottom: 10px;
`
const ConfirmedIcon = styled(ColumnCenter)<{ inline?: boolean }>`
@ -50,6 +46,10 @@ const StyledLogo = styled.img`
margin-left: 6px;
`
const ConfirmationModalContentWrapper = styled(AutoColumn)`
padding-bottom: 12px;
`
function ConfirmationPendingContent({
onDismiss,
pendingText,
@ -59,8 +59,6 @@ function ConfirmationPendingContent({
pendingText: ReactNode
inline?: boolean // not in modal
}) {
const theme = useTheme()
return (
<Wrapper>
<AutoColumn gap="md">
@ -74,15 +72,15 @@ function ConfirmationPendingContent({
<CustomLightSpinner src={Circle} alt="loader" size={inline ? '40px' : '90px'} />
</ConfirmedIcon>
<AutoColumn gap="md" justify="center">
<Text fontWeight={500} fontSize={20} color={theme.textPrimary} textAlign="center">
<ThemedText.SubHeaderLarge color="textPrimary" textAlign="center">
<Trans>Waiting for confirmation</Trans>
</Text>
<Text fontWeight={600} fontSize={16} color={theme.textPrimary} textAlign="center">
</ThemedText.SubHeaderLarge>
<ThemedText.SubHeader color="textPrimary" textAlign="center">
{pendingText}
</Text>
<Text fontWeight={400} fontSize={12} color={theme.textSecondary} textAlign="center" marginBottom="12px">
</ThemedText.SubHeader>
<ThemedText.SubHeaderSmall color="textSecondary" textAlign="center" marginBottom="12px">
<Trans>Confirm this transaction in your wallet</Trans>
</Text>
</ThemedText.SubHeaderSmall>
</AutoColumn>
</AutoColumn>
</Wrapper>
@ -125,7 +123,7 @@ function TransactionSubmittedContent({
return (
<Wrapper>
<Section inline={inline}>
<AutoColumn>
{!inline && (
<RowBetween>
<div />
@ -135,7 +133,7 @@ function TransactionSubmittedContent({
<ConfirmedIcon inline={inline}>
<ArrowUpCircle strokeWidth={1} size={inline ? '40px' : '75px'} color={theme.accentActive} />
</ConfirmedIcon>
<AutoColumn gap="md" justify="center" style={{ paddingBottom: '12px' }}>
<ConfirmationModalContentWrapper gap="md" justify="center">
<ThemedText.MediumHeader textAlign="center">
<Trans>Transaction submitted</Trans>
</ThemedText.MediumHeader>
@ -154,19 +152,19 @@ function TransactionSubmittedContent({
</ButtonLight>
)}
<ButtonPrimary onClick={onDismiss} style={{ margin: '20px 0 0 0' }} data-testid="dismiss-tx-confirmation">
<Text fontWeight={600} fontSize={20} color={theme.accentTextLightPrimary}>
<ThemedText.HeadlineSmall color={theme.accentTextLightPrimary}>
{inline ? <Trans>Return</Trans> : <Trans>Close</Trans>}
</Text>
</ThemedText.HeadlineSmall>
</ButtonPrimary>
{chainId && hash && (
<ExternalLink href={getExplorerLink(chainId, hash, ExplorerDataType.TRANSACTION)}>
<Text fontWeight={600} fontSize={14} color={theme.accentAction}>
<ThemedText.Link color={theme.accentAction}>
<Trans>View on {chainId === SupportedChainId.MAINNET ? 'Etherscan' : 'Block Explorer'}</Trans>
</Text>
</ThemedText.Link>
</ExternalLink>
)}
</AutoColumn>
</Section>
</ConfirmationModalContentWrapper>
</AutoColumn>
</Wrapper>
)
}
@ -184,15 +182,15 @@ export function ConfirmationModalContent({
}) {
return (
<Wrapper>
<Section>
<RowBetween>
<Text fontWeight={500} fontSize={16}>
{title}
</Text>
<AutoColumn gap="sm">
<Row>
<Row justify="center" marginLeft="24px">
<ThemedText.SubHeader>{title}</ThemedText.SubHeader>
</Row>
<CloseIcon onClick={onDismiss} data-cy="confirmation-close-icon" />
</RowBetween>
</Row>
{topContent()}
</Section>
</AutoColumn>
{bottomContent && <BottomSection gap="12px">{bottomContent()}</BottomSection>}
</Wrapper>
)
@ -202,7 +200,7 @@ export function TransactionErrorContent({ message, onDismiss }: { message: React
const theme = useTheme()
return (
<Wrapper>
<Section>
<AutoColumn>
<RowBetween>
<Text fontWeight={600} fontSize={16}>
<Trans>Error</Trans>
@ -213,7 +211,7 @@ export function TransactionErrorContent({ message, onDismiss }: { message: React
<AlertTriangle color={theme.accentCritical} style={{ strokeWidth: 1 }} size={90} />
<ThemedText.MediumHeader textAlign="center">{message}</ThemedText.MediumHeader>
</AutoColumn>
</Section>
</AutoColumn>
<BottomSection gap="12px">
<ButtonPrimary onClick={onDismiss}>
<Trans>Dismiss</Trans>
@ -252,7 +250,7 @@ function L2Content({
return (
<Wrapper>
<Section inline={inline}>
<AutoColumn>
{!inline && (
<RowBetween mb="16px">
<Badge>
@ -277,7 +275,7 @@ function L2Content({
)}
</ConfirmedIcon>
<AutoColumn gap="md" justify="center">
<Text fontWeight={500} fontSize={20} textAlign="center">
<ThemedText.SubHeaderLarge textAlign="center">
{!hash ? (
<Trans>Confirm transaction in wallet</Trans>
) : !confirmed ? (
@ -287,20 +285,20 @@ function L2Content({
) : (
<Trans>Error</Trans>
)}
</Text>
<Text fontWeight={400} fontSize={16} textAlign="center">
</ThemedText.SubHeaderLarge>
<ThemedText.BodySecondary textAlign="center">
{transaction ? <TransactionSummary info={transaction.info} /> : pendingText}
</Text>
</ThemedText.BodySecondary>
{chainId && hash ? (
<ExternalLink href={getExplorerLink(chainId, hash, ExplorerDataType.TRANSACTION)}>
<Text fontWeight={500} fontSize={14} color={theme.accentAction}>
<ThemedText.SubHeaderSmall color={theme.accentAction}>
<Trans>View on Explorer</Trans>
</Text>
</ThemedText.SubHeaderSmall>
</ExternalLink>
) : (
<div style={{ height: '17px' }} />
)}
<Text color={theme.textTertiary} style={{ margin: '20px 0 0 0' }} fontSize="14px">
<ThemedText.SubHeaderSmall color={theme.textTertiary} marginTop="20px">
{!secondsToConfirm ? (
<div style={{ height: '24px' }} />
) : (
@ -311,14 +309,14 @@ function L2Content({
</span>
</div>
)}
</Text>
</ThemedText.SubHeaderSmall>
<ButtonPrimary onClick={onDismiss} style={{ margin: '4px 0 0 0' }}>
<Text fontWeight={500} fontSize={20}>
<ThemedText.SubHeaderLarge>
{inline ? <Trans>Return</Trans> : <Trans>Close</Trans>}
</Text>
</ThemedText.SubHeaderLarge>
</ButtonPrimary>
</AutoColumn>
</Section>
</AutoColumn>
</Wrapper>
)
}

@ -1,9 +1,11 @@
import { Trans } from '@lingui/macro'
import { Trace } from '@uniswap/analytics'
import { InterfaceModalName } from '@uniswap/analytics-events'
import { sendAnalyticsEvent, Trace } from '@uniswap/analytics'
import { InterfaceModalName, SwapEventName, SwapPriceUpdateUserResponse } from '@uniswap/analytics-events'
import { Percent } from '@uniswap/sdk-core'
import { ReactNode, useCallback, useMemo, useState } from 'react'
import { getPriceUpdateBasisPoints } from 'lib/utils/analytics'
import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react'
import { InterfaceTrade } from 'state/routing/types'
import { formatSwapPriceUpdatedEventProperties } from 'utils/loggingFormatters'
import { tradeMeaningfullyDiffers } from 'utils/tradeMeaningFullyDiffer'
import TransactionConfirmationModal, {
@ -20,21 +22,17 @@ export default function ConfirmSwapModal({
allowedSlippage,
onConfirm,
onDismiss,
recipient,
swapErrorMessage,
isOpen,
attemptingTxn,
txHash,
swapQuoteReceivedDate,
fiatValueInput,
fiatValueOutput,
}: {
isOpen: boolean
trade: InterfaceTrade | undefined
trade: InterfaceTrade
originalTrade: InterfaceTrade | undefined
attemptingTxn: boolean
txHash: string | undefined
recipient: string | null
allowedSlippage: Percent
onAcceptChanges: () => void
onConfirm: () => void
@ -44,35 +42,34 @@ export default function ConfirmSwapModal({
fiatValueInput: { data?: number; isLoading: boolean }
fiatValueOutput: { data?: number; isLoading: boolean }
}) {
// shouldLogModalCloseEvent lets the child SwapModalHeader component know when modal has been closed
// and an event triggered by modal closing should be logged.
const [shouldLogModalCloseEvent, setShouldLogModalCloseEvent] = useState(false)
const showAcceptChanges = useMemo(
() => Boolean(trade && originalTrade && tradeMeaningfullyDiffers(trade, originalTrade)),
() => Boolean(originalTrade && tradeMeaningfullyDiffers(trade, originalTrade)),
[originalTrade, trade]
)
const [lastExecutionPrice, setLastExecutionPrice] = useState(trade?.executionPrice)
const [priceUpdate, setPriceUpdate] = useState<number>()
useEffect(() => {
if (lastExecutionPrice && !trade.executionPrice.equalTo(lastExecutionPrice)) {
setPriceUpdate(getPriceUpdateBasisPoints(lastExecutionPrice, trade.executionPrice))
setLastExecutionPrice(trade.executionPrice)
}
}, [lastExecutionPrice, setLastExecutionPrice, trade])
const onModalDismiss = useCallback(() => {
if (isOpen) setShouldLogModalCloseEvent(true)
sendAnalyticsEvent(
SwapEventName.SWAP_PRICE_UPDATE_ACKNOWLEDGED,
formatSwapPriceUpdatedEventProperties(trade, priceUpdate, SwapPriceUpdateUserResponse.REJECTED)
)
onDismiss()
}, [isOpen, onDismiss])
}, [onDismiss, priceUpdate, trade])
const modalHeader = useCallback(() => {
return trade ? (
<SwapModalHeader
trade={trade}
shouldLogModalCloseEvent={shouldLogModalCloseEvent}
setShouldLogModalCloseEvent={setShouldLogModalCloseEvent}
allowedSlippage={allowedSlippage}
recipient={recipient}
showAcceptChanges={showAcceptChanges}
onAcceptChanges={onAcceptChanges}
/>
) : null
}, [allowedSlippage, onAcceptChanges, recipient, showAcceptChanges, trade, shouldLogModalCloseEvent])
return <SwapModalHeader trade={trade} allowedSlippage={allowedSlippage} />
}, [allowedSlippage, trade])
const modalBottom = useCallback(() => {
return trade ? (
return (
<SwapModalFooter
onConfirm={onConfirm}
trade={trade}
@ -83,25 +80,28 @@ export default function ConfirmSwapModal({
swapQuoteReceivedDate={swapQuoteReceivedDate}
fiatValueInput={fiatValueInput}
fiatValueOutput={fiatValueOutput}
showAcceptChanges={showAcceptChanges}
onAcceptChanges={onAcceptChanges}
/>
) : null
)
}, [
trade,
onConfirm,
txHash,
allowedSlippage,
showAcceptChanges,
swapErrorMessage,
trade,
allowedSlippage,
txHash,
swapQuoteReceivedDate,
fiatValueInput,
fiatValueOutput,
onAcceptChanges,
])
// text to show while loading
const pendingText = (
<Trans>
Swapping {trade?.inputAmount?.toSignificant(6)} {trade?.inputAmount?.currency?.symbol} for{' '}
{trade?.outputAmount?.toSignificant(6)} {trade?.outputAmount?.currency?.symbol}
Swapping {trade.inputAmount.toSignificant(6)} {trade.inputAmount.currency?.symbol} for{' '}
{trade.outputAmount.toSignificant(6)} {trade.outputAmount.currency?.symbol}
</Trans>
)
@ -111,7 +111,7 @@ export default function ConfirmSwapModal({
<TransactionErrorContent onDismiss={onModalDismiss} message={swapErrorMessage} />
) : (
<ConfirmationModalContent
title={<Trans>Confirm Swap</Trans>}
title={<Trans>Review Swap</Trans>}
onDismiss={onModalDismiss}
topContent={modalHeader}
bottomContent={modalBottom}
@ -123,13 +123,13 @@ export default function ConfirmSwapModal({
return (
<Trace modal={InterfaceModalName.CONFIRM_SWAP}>
<TransactionConfirmationModal
isOpen={isOpen}
isOpen
onDismiss={onModalDismiss}
attemptingTxn={attemptingTxn}
hash={txHash}
content={confirmationContent}
pendingText={pendingText}
currencyToAdd={trade?.outputAmount.currency}
currencyToAdd={trade.outputAmount.currency}
/>
</Trace>
)

@ -1,27 +1,103 @@
import { TEST_ALLOWED_SLIPPAGE, TEST_TRADE_EXACT_INPUT } from 'test-utils/constants'
import { render, screen } from 'test-utils/render'
import { TEST_ALLOWED_SLIPPAGE, TEST_TRADE_EXACT_INPUT, TEST_TRADE_EXACT_OUTPUT } from 'test-utils/constants'
import { render, screen, within } from 'test-utils/render'
import SwapModalFooter from './SwapModalFooter'
const swapErrorMessage = 'swap error'
const fiatValue = { data: 123, isLoading: false }
describe('SwapModalFooter.tsx', () => {
it('renders with a disabled button with no account', () => {
it('matches base snapshot, test trade exact input', () => {
const { asFragment } = render(
<SwapModalFooter
trade={TEST_TRADE_EXACT_INPUT}
allowedSlippage={TEST_ALLOWED_SLIPPAGE}
hash={undefined}
onConfirm={() => null}
disabledConfirm
swapErrorMessage={swapErrorMessage}
onConfirm={jest.fn()}
swapErrorMessage={undefined}
disabledConfirm={false}
swapQuoteReceivedDate={undefined}
fiatValueInput={fiatValue}
fiatValueOutput={fiatValue}
fiatValueInput={{
data: undefined,
isLoading: false,
}}
fiatValueOutput={{
data: undefined,
isLoading: false,
}}
showAcceptChanges={false}
onAcceptChanges={jest.fn()}
/>
)
expect(asFragment()).toMatchSnapshot()
expect(screen.getByTestId('confirm-swap-button')).toBeDisabled()
expect(
screen.getByText(
'The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert.'
)
).toBeInTheDocument()
expect(
screen.getByText('The fee paid to miners who process your transaction. This must be paid in $ETH.')
).toBeInTheDocument()
expect(screen.getByText('The impact your trade has on the market price of this pool.')).toBeInTheDocument()
})
it('shows accept changes section when available', () => {
const mockAcceptChanges = jest.fn()
render(
<SwapModalFooter
trade={TEST_TRADE_EXACT_INPUT}
allowedSlippage={TEST_ALLOWED_SLIPPAGE}
hash={undefined}
onConfirm={jest.fn()}
swapErrorMessage={undefined}
disabledConfirm={false}
swapQuoteReceivedDate={undefined}
fiatValueInput={{
data: undefined,
isLoading: false,
}}
fiatValueOutput={{
data: undefined,
isLoading: false,
}}
showAcceptChanges={true}
onAcceptChanges={mockAcceptChanges}
/>
)
const showAcceptChanges = screen.getByTestId('show-accept-changes')
expect(showAcceptChanges).toBeInTheDocument()
expect(within(showAcceptChanges).getByText('Price updated')).toBeVisible()
expect(within(showAcceptChanges).getByText('Accept')).toBeVisible()
})
it('test trade exact output, no recipient', () => {
render(
<SwapModalFooter
trade={TEST_TRADE_EXACT_OUTPUT}
allowedSlippage={TEST_ALLOWED_SLIPPAGE}
hash={undefined}
onConfirm={jest.fn()}
swapErrorMessage={undefined}
disabledConfirm={false}
swapQuoteReceivedDate={undefined}
fiatValueInput={{
data: undefined,
isLoading: false,
}}
fiatValueOutput={{
data: undefined,
isLoading: false,
}}
showAcceptChanges={true}
onAcceptChanges={jest.fn()}
/>
)
expect(
screen.getByText(
'The maximum amount you are guaranteed to spend. If the price slips any further, your transaction will revert.'
)
).toBeInTheDocument()
expect(
screen.getByText('The fee paid to miners who process your transaction. This must be paid in $ETH.')
).toBeInTheDocument()
expect(screen.getByText('The impact your trade has on the market price of this pool.')).toBeInTheDocument()
})
})

@ -1,105 +1,48 @@
import { Trans } from '@lingui/macro'
import { TraceEvent } from '@uniswap/analytics'
import { BrowserEvent, InterfaceElementName, SwapEventName } from '@uniswap/analytics-events'
import { Percent } from '@uniswap/sdk-core'
import { formatPriceImpact } from '@uniswap/conedison/format'
import { Percent, TradeType } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import Column from 'components/Column'
import { MouseoverTooltip } from 'components/Tooltip'
import useTransactionDeadline from 'hooks/useTransactionDeadline'
import {
formatPercentInBasisPointsNumber,
formatPercentNumber,
formatToDecimal,
getDurationFromDateMilliseconds,
getDurationUntilTimestampSeconds,
getTokenAddress,
} from 'lib/utils/analytics'
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
import { ReactNode } from 'react'
import { Text } from 'rebass'
import { AlertTriangle } from 'react-feather'
import { RouterPreference } from 'state/routing/slice'
import { InterfaceTrade } from 'state/routing/types'
import { useRouterPreference, useUserSlippageTolerance } from 'state/user/hooks'
import getRoutingDiagramEntries, { RoutingDiagramEntry } from 'utils/getRoutingDiagramEntries'
import { computeRealizedPriceImpact } from 'utils/prices'
import styled, { useTheme } from 'styled-components/macro'
import { ThemedText } from 'theme'
import { formatTransactionAmount, priceToPreciseFloat } from 'utils/formatNumbers'
import getRoutingDiagramEntries from 'utils/getRoutingDiagramEntries'
import { formatSwapButtonClickEventProperties } from 'utils/loggingFormatters'
import { getPriceImpactWarning } from 'utils/prices'
import { ButtonError } from '../Button'
import { AutoRow } from '../Row'
import { SwapCallbackError } from './styleds'
import { ButtonError, SmallButtonPrimary } from '../Button'
import Row, { AutoRow, RowBetween, RowFixed } from '../Row'
import { SwapCallbackError, SwapShowAcceptChanges } from './styleds'
import { Label } from './SwapModalHeaderAmount'
interface AnalyticsEventProps {
trade: InterfaceTrade
hash: string | undefined
allowedSlippage: Percent
transactionDeadlineSecondsSinceEpoch: number | undefined
isAutoSlippage: boolean
isAutoRouterApi: boolean
swapQuoteReceivedDate: Date | undefined
routes: RoutingDiagramEntry[]
fiatValueInput?: number
fiatValueOutput?: number
}
const DetailsContainer = styled(Column)`
padding: 0 8px;
`
const formatRoutesEventProperties = (routes: RoutingDiagramEntry[]) => {
const routesEventProperties: Record<string, any[]> = {
routes_percentages: [],
routes_protocols: [],
}
const StyledAlertTriangle = styled(AlertTriangle)`
margin-right: 8px;
min-width: 24px;
`
routes.forEach((route, index) => {
routesEventProperties['routes_percentages'].push(formatPercentNumber(route.percent))
routesEventProperties['routes_protocols'].push(route.protocol)
routesEventProperties[`route_${index}_input_currency_symbols`] = route.path.map(
(pathStep) => pathStep[0].symbol ?? ''
)
routesEventProperties[`route_${index}_output_currency_symbols`] = route.path.map(
(pathStep) => pathStep[1].symbol ?? ''
)
routesEventProperties[`route_${index}_input_currency_addresses`] = route.path.map((pathStep) =>
getTokenAddress(pathStep[0])
)
routesEventProperties[`route_${index}_output_currency_addresses`] = route.path.map((pathStep) =>
getTokenAddress(pathStep[1])
)
routesEventProperties[`route_${index}_fee_amounts_hundredths_of_bps`] = route.path.map((pathStep) => pathStep[2])
})
const ConfirmButton = styled(ButtonError)`
height: 56px;
margin-top: 10px;
`
return routesEventProperties
}
const formatAnalyticsEventProperties = ({
trade,
hash,
allowedSlippage,
transactionDeadlineSecondsSinceEpoch,
isAutoSlippage,
isAutoRouterApi,
swapQuoteReceivedDate,
routes,
fiatValueInput,
fiatValueOutput,
}: AnalyticsEventProps) => ({
estimated_network_fee_usd: trade.gasUseEstimateUSD ?? undefined,
transaction_hash: hash,
transaction_deadline_seconds: getDurationUntilTimestampSeconds(transactionDeadlineSecondsSinceEpoch),
token_in_address: getTokenAddress(trade.inputAmount.currency),
token_out_address: getTokenAddress(trade.outputAmount.currency),
token_in_symbol: trade.inputAmount.currency.symbol,
token_out_symbol: trade.outputAmount.currency.symbol,
token_in_amount: formatToDecimal(trade.inputAmount, trade.inputAmount.currency.decimals),
token_out_amount: formatToDecimal(trade.outputAmount, trade.outputAmount.currency.decimals),
token_in_amount_usd: fiatValueInput,
token_out_amount_usd: fiatValueOutput,
price_impact_basis_points: formatPercentInBasisPointsNumber(computeRealizedPriceImpact(trade)),
allowed_slippage_basis_points: formatPercentInBasisPointsNumber(allowedSlippage),
is_auto_router_api: isAutoRouterApi,
is_auto_slippage: isAutoSlippage,
chain_id:
trade.inputAmount.currency.chainId === trade.outputAmount.currency.chainId
? trade.inputAmount.currency.chainId
: undefined,
duration_from_first_quote_to_swap_submission_milliseconds: swapQuoteReceivedDate
? getDurationFromDateMilliseconds(swapQuoteReceivedDate)
: undefined,
swap_quote_block_number: trade.blockNumber,
...formatRoutesEventProperties(routes),
})
const DetailRowValue = styled(ThemedText.BodySmall)`
text-align: right;
overflow-wrap: break-word;
`
export default function SwapModalFooter({
trade,
@ -111,6 +54,8 @@ export default function SwapModalFooter({
swapQuoteReceivedDate,
fiatValueInput,
fiatValueOutput,
showAcceptChanges,
onAcceptChanges,
}: {
trade: InterfaceTrade
hash: string | undefined
@ -121,46 +66,142 @@ export default function SwapModalFooter({
swapQuoteReceivedDate: Date | undefined
fiatValueInput: { data?: number; isLoading: boolean }
fiatValueOutput: { data?: number; isLoading: boolean }
showAcceptChanges: boolean
onAcceptChanges: () => void
}) {
const transactionDeadlineSecondsSinceEpoch = useTransactionDeadline()?.toNumber() // in seconds since epoch
const isAutoSlippage = useUserSlippageTolerance()[0] === 'auto'
const [routerPreference] = useRouterPreference()
const routes = getRoutingDiagramEntries(trade)
const theme = useTheme()
const { chainId } = useWeb3React()
const nativeCurrency = useNativeCurrency(chainId)
const label = `${trade.executionPrice.baseCurrency?.symbol} `
const labelInverted = `${trade.executionPrice.quoteCurrency?.symbol}`
const formattedPrice = formatTransactionAmount(priceToPreciseFloat(trade.executionPrice))
return (
<>
<AutoRow>
<TraceEvent
events={[BrowserEvent.onClick]}
element={InterfaceElementName.CONFIRM_SWAP_BUTTON}
name={SwapEventName.SWAP_SUBMITTED_BUTTON_CLICKED}
properties={formatAnalyticsEventProperties({
trade,
hash,
allowedSlippage,
transactionDeadlineSecondsSinceEpoch,
isAutoSlippage,
isAutoRouterApi: routerPreference === RouterPreference.AUTO || routerPreference === RouterPreference.API,
swapQuoteReceivedDate,
routes,
fiatValueInput: fiatValueInput.data,
fiatValueOutput: fiatValueOutput.data,
})}
>
<ButtonError
data-testid="confirm-swap-button"
onClick={onConfirm}
disabled={disabledConfirm}
style={{ margin: '10px 0 0 0' }}
id={InterfaceElementName.CONFIRM_SWAP_BUTTON}
<DetailsContainer gap="md">
<ThemedText.BodySmall>
<Row align="flex-start" justify="space-between" gap="sm">
<Label>
<Trans>Exchange rate</Trans>
</Label>
<DetailRowValue>{`1 ${labelInverted} = ${formattedPrice ?? '-'} ${label}`}</DetailRowValue>
</Row>
</ThemedText.BodySmall>
<ThemedText.BodySmall>
<Row align="flex-start" justify="space-between" gap="sm">
<MouseoverTooltip
text={
<Trans>
The fee paid to miners who process your transaction. This must be paid in ${nativeCurrency.symbol}.
</Trans>
}
>
<Label cursor="help">
<Trans>Network fee</Trans>
</Label>
</MouseoverTooltip>
<DetailRowValue>{trade.gasUseEstimateUSD ? `~$${trade.gasUseEstimateUSD}` : '-'}</DetailRowValue>
</Row>
</ThemedText.BodySmall>
<ThemedText.BodySmall>
<Row align="flex-start" justify="space-between" gap="sm">
<MouseoverTooltip text={<Trans>The impact your trade has on the market price of this pool.</Trans>}>
<Label cursor="help">
<Trans>Price impact</Trans>
</Label>
</MouseoverTooltip>
<DetailRowValue color={getPriceImpactWarning(trade.priceImpact)}>
{trade.priceImpact ? formatPriceImpact(trade.priceImpact) : '-'}
</DetailRowValue>
</Row>
</ThemedText.BodySmall>
<ThemedText.BodySmall>
<Row align="flex-start" justify="space-between" gap="sm">
<MouseoverTooltip
text={
trade.tradeType === TradeType.EXACT_INPUT ? (
<Trans>
The minimum amount you are guaranteed to receive. If the price slips any further, your transaction
will revert.
</Trans>
) : (
<Trans>
The maximum amount you are guaranteed to spend. If the price slips any further, your transaction
will revert.
</Trans>
)
}
>
<Label cursor="help">
{trade.tradeType === TradeType.EXACT_INPUT ? (
<Trans>Minimum received</Trans>
) : (
<Trans>Maximum sent</Trans>
)}
</Label>
</MouseoverTooltip>
<DetailRowValue>
{trade.tradeType === TradeType.EXACT_INPUT
? `${trade.minimumAmountOut(allowedSlippage).toSignificant(6)} ${trade.outputAmount.currency.symbol}`
: `${trade.maximumAmountIn(allowedSlippage).toSignificant(6)} ${trade.inputAmount.currency.symbol}`}
</DetailRowValue>
</Row>
</ThemedText.BodySmall>
</DetailsContainer>
{showAcceptChanges ? (
<SwapShowAcceptChanges data-testid="show-accept-changes">
<RowBetween>
<RowFixed>
<StyledAlertTriangle size={20} />
<ThemedText.DeprecatedMain color={theme.accentAction}>
<Trans>Price updated</Trans>
</ThemedText.DeprecatedMain>
</RowFixed>
<SmallButtonPrimary onClick={onAcceptChanges}>
<Trans>Accept</Trans>
</SmallButtonPrimary>
</RowBetween>
</SwapShowAcceptChanges>
) : (
<AutoRow>
<TraceEvent
events={[BrowserEvent.onClick]}
element={InterfaceElementName.CONFIRM_SWAP_BUTTON}
name={SwapEventName.SWAP_SUBMITTED_BUTTON_CLICKED}
properties={formatSwapButtonClickEventProperties({
trade,
hash,
allowedSlippage,
transactionDeadlineSecondsSinceEpoch,
isAutoSlippage,
isAutoRouterApi: routerPreference === RouterPreference.AUTO || routerPreference === RouterPreference.API,
swapQuoteReceivedDate,
routes,
fiatValueInput: fiatValueInput.data,
fiatValueOutput: fiatValueOutput.data,
})}
>
<Text fontSize={20} fontWeight={500}>
<Trans>Confirm Swap</Trans>
</Text>
</ButtonError>
</TraceEvent>
{swapErrorMessage ? <SwapCallbackError error={swapErrorMessage} /> : null}
</AutoRow>
<ConfirmButton
data-testid="confirm-swap-button"
onClick={onConfirm}
disabled={disabledConfirm}
$borderRadius="12px"
id={InterfaceElementName.CONFIRM_SWAP_BUTTON}
>
<ThemedText.HeadlineSmall color="accentTextLightPrimary">
<Trans>Swap</Trans>
</ThemedText.HeadlineSmall>
</ConfirmButton>
</TraceEvent>
{swapErrorMessage ? <SwapCallbackError error={swapErrorMessage} /> : null}
</AutoRow>
)}
</>
)
}

@ -1,83 +1,44 @@
import { sendAnalyticsEvent } from '@uniswap/analytics'
import {
TEST_ALLOWED_SLIPPAGE,
TEST_RECIPIENT_ADDRESS,
TEST_TRADE_EXACT_INPUT,
TEST_TRADE_EXACT_OUTPUT,
} from 'test-utils/constants'
import { render, screen, within } from 'test-utils/render'
import noop from 'utils/noop'
import { formatCurrencyAmount, NumberType } from '@uniswap/conedison/format'
import { TEST_ALLOWED_SLIPPAGE, TEST_TRADE_EXACT_INPUT, TEST_TRADE_EXACT_OUTPUT } from 'test-utils/constants'
import { render, screen } from 'test-utils/render'
import SwapModalHeader from './SwapModalHeader'
jest.mock('@uniswap/analytics')
const mockSendAnalyticsEvent = sendAnalyticsEvent as jest.MockedFunction<typeof sendAnalyticsEvent>
describe('SwapModalHeader.tsx', () => {
let sendAnalyticsEventMock: jest.Mock<any, any>
beforeAll(() => {
sendAnalyticsEventMock = jest.fn()
})
it('matches base snapshot for test trade with exact input', () => {
it('matches base snapshot, test trade exact input', () => {
const { asFragment } = render(
<SwapModalHeader
trade={TEST_TRADE_EXACT_INPUT}
allowedSlippage={TEST_ALLOWED_SLIPPAGE}
shouldLogModalCloseEvent={false}
showAcceptChanges={false}
setShouldLogModalCloseEvent={noop}
onAcceptChanges={noop}
recipient={TEST_RECIPIENT_ADDRESS}
/>
<SwapModalHeader trade={TEST_TRADE_EXACT_INPUT} allowedSlippage={TEST_ALLOWED_SLIPPAGE} />
)
expect(asFragment()).toMatchSnapshot()
expect(screen.getByText(/Output is estimated. You will receive at least /i)).toBeInTheDocument()
expect(screen.getByTestId('INPUT-amount')).toHaveTextContent(
`${formatCurrencyAmount(TEST_TRADE_EXACT_INPUT.inputAmount, NumberType.TokenTx)} ${
TEST_TRADE_EXACT_INPUT.inputAmount.currency.symbol ?? ''
}`
)
expect(screen.getByTestId('OUTPUT-amount')).toHaveTextContent(
`${formatCurrencyAmount(TEST_TRADE_EXACT_INPUT.outputAmount, NumberType.TokenTx)} ${
TEST_TRADE_EXACT_INPUT.outputAmount.currency.symbol ?? ''
}`
)
})
it('shows accept changes section and logs amplitude event', () => {
const setShouldLogModalCloseEventFn = jest.fn()
mockSendAnalyticsEvent.mockImplementation(sendAnalyticsEventMock)
render(
<SwapModalHeader
trade={TEST_TRADE_EXACT_INPUT}
allowedSlippage={TEST_ALLOWED_SLIPPAGE}
shouldLogModalCloseEvent
showAcceptChanges
setShouldLogModalCloseEvent={setShouldLogModalCloseEventFn}
onAcceptChanges={noop}
recipient={TEST_RECIPIENT_ADDRESS}
/>
it('test trade exact output, no recipient', () => {
const { asFragment } = render(
<SwapModalHeader trade={TEST_TRADE_EXACT_OUTPUT} allowedSlippage={TEST_ALLOWED_SLIPPAGE} />
)
expect(setShouldLogModalCloseEventFn).toHaveBeenCalledWith(false)
const showAcceptChanges = screen.getByTestId('show-accept-changes')
expect(showAcceptChanges).toBeInTheDocument()
expect(within(showAcceptChanges).getByText('Price Updated')).toBeVisible()
expect(within(showAcceptChanges).getByText('Accept')).toBeVisible()
expect(sendAnalyticsEventMock).toHaveBeenCalledTimes(1)
})
it('renders correctly for test trade with exact output and no recipient', () => {
const rendered = render(
<SwapModalHeader
trade={TEST_TRADE_EXACT_OUTPUT}
allowedSlippage={TEST_ALLOWED_SLIPPAGE}
shouldLogModalCloseEvent={false}
showAcceptChanges={false}
setShouldLogModalCloseEvent={noop}
onAcceptChanges={noop}
recipient={null}
/>
)
expect(rendered.queryByTestId('recipient-info')).toBeNull()
expect(asFragment()).toMatchSnapshot()
expect(screen.getByText(/Input is estimated. You will sell at most/i)).toBeInTheDocument()
expect(screen.getByTestId('input-symbol')).toHaveTextContent(
TEST_TRADE_EXACT_OUTPUT.inputAmount.currency.symbol ?? ''
expect(screen.getByTestId('INPUT-amount')).toHaveTextContent(
`${formatCurrencyAmount(TEST_TRADE_EXACT_OUTPUT.inputAmount, NumberType.TokenTx)} ${
TEST_TRADE_EXACT_OUTPUT.inputAmount.currency.symbol ?? ''
}`
)
expect(screen.getByTestId('output-symbol')).toHaveTextContent(
TEST_TRADE_EXACT_OUTPUT.outputAmount.currency.symbol ?? ''
expect(screen.getByTestId('OUTPUT-amount')).toHaveTextContent(
`${formatCurrencyAmount(TEST_TRADE_EXACT_OUTPUT.outputAmount, NumberType.TokenTx)} ${
TEST_TRADE_EXACT_OUTPUT.outputAmount.currency.symbol ?? ''
}`
)
expect(screen.getByTestId('input-amount')).toHaveTextContent(TEST_TRADE_EXACT_OUTPUT.inputAmount.toExact())
expect(screen.getByTestId('output-amount')).toHaveTextContent(TEST_TRADE_EXACT_OUTPUT.outputAmount.toExact())
})
})

@ -1,216 +1,72 @@
import { Trans } from '@lingui/macro'
import { sendAnalyticsEvent } from '@uniswap/analytics'
import { SwapEventName, SwapPriceUpdateUserResponse } from '@uniswap/analytics-events'
import { Percent, TradeType } from '@uniswap/sdk-core'
import Column, { AutoColumn } from 'components/Column'
import { useUSDPrice } from 'hooks/useUSDPrice'
import { getPriceUpdateBasisPoints } from 'lib/utils/analytics'
import { useEffect, useState } from 'react'
import { AlertTriangle, ArrowDown } from 'react-feather'
import { Text } from 'rebass'
import { InterfaceTrade } from 'state/routing/types'
import styled, { useTheme } from 'styled-components/macro'
import { Field } from 'state/swap/actions'
import styled from 'styled-components/macro'
import { Divider, ThemedText } from 'theme'
import { ThemedText } from '../../theme'
import { isAddress, shortenAddress } from '../../utils'
import { computeFiatValuePriceImpact } from '../../utils/computeFiatValuePriceImpact'
import { ButtonPrimary } from '../Button'
import { LightCard } from '../Card'
import { AutoColumn } from '../Column'
import { FiatValue } from '../CurrencyInputPanel/FiatValue'
import CurrencyLogo from '../Logo/CurrencyLogo'
import { RowBetween, RowFixed } from '../Row'
import TradePrice from '../swap/TradePrice'
import { AdvancedSwapDetails } from './AdvancedSwapDetails'
import { SwapShowAcceptChanges, TruncatedText } from './styleds'
import { SwapModalHeaderAmount } from './SwapModalHeaderAmount'
const ArrowWrapper = styled.div`
padding: 4px;
border-radius: 12px;
height: 40px;
width: 40px;
position: relative;
margin-top: -18px;
margin-bottom: -18px;
left: calc(50% - 16px);
display: flex;
justify-content: center;
align-items: center;
background-color: ${({ theme }) => theme.backgroundSurface};
border: 4px solid;
border-color: ${({ theme }) => theme.backgroundModule};
z-index: 2;
const Rule = styled(Divider)`
margin: 16px 2px 24px 2px;
`
const formatAnalyticsEventProperties = (
trade: InterfaceTrade,
priceUpdate: number | undefined,
response: SwapPriceUpdateUserResponse
) => ({
chain_id:
trade.inputAmount.currency.chainId === trade.outputAmount.currency.chainId
? trade.inputAmount.currency.chainId
: undefined,
response,
token_in_symbol: trade.inputAmount.currency.symbol,
token_out_symbol: trade.outputAmount.currency.symbol,
price_update_basis_points: priceUpdate,
})
const HeaderContainer = styled(AutoColumn)`
margin-top: 16px;
`
export default function SwapModalHeader({
trade,
shouldLogModalCloseEvent,
setShouldLogModalCloseEvent,
allowedSlippage,
recipient,
showAcceptChanges,
onAcceptChanges,
}: {
trade: InterfaceTrade
shouldLogModalCloseEvent: boolean
setShouldLogModalCloseEvent: (shouldLog: boolean) => void
allowedSlippage: Percent
recipient: string | null
showAcceptChanges: boolean
onAcceptChanges: () => void
}) {
const theme = useTheme()
const [lastExecutionPrice, setLastExecutionPrice] = useState(trade.executionPrice)
const [priceUpdate, setPriceUpdate] = useState<number | undefined>()
const fiatValueInput = useUSDPrice(trade.inputAmount)
const fiatValueOutput = useUSDPrice(trade.outputAmount)
useEffect(() => {
if (!trade.executionPrice.equalTo(lastExecutionPrice)) {
setPriceUpdate(getPriceUpdateBasisPoints(lastExecutionPrice, trade.executionPrice))
setLastExecutionPrice(trade.executionPrice)
}
}, [lastExecutionPrice, setLastExecutionPrice, trade.executionPrice])
useEffect(() => {
if (shouldLogModalCloseEvent && showAcceptChanges) {
sendAnalyticsEvent(
SwapEventName.SWAP_PRICE_UPDATE_ACKNOWLEDGED,
formatAnalyticsEventProperties(trade, priceUpdate, SwapPriceUpdateUserResponse.REJECTED)
)
}
setShouldLogModalCloseEvent(false)
}, [shouldLogModalCloseEvent, showAcceptChanges, setShouldLogModalCloseEvent, trade, priceUpdate])
return (
<AutoColumn gap="4px" style={{ marginTop: '1rem' }}>
<LightCard padding="0.75rem 1rem">
<AutoColumn gap="sm">
<RowBetween align="center">
<RowFixed gap="0px">
<TruncatedText
fontSize={24}
fontWeight={500}
color={showAcceptChanges && trade.tradeType === TradeType.EXACT_OUTPUT ? theme.accentAction : ''}
data-testid="input-amount"
>
{trade.inputAmount.toSignificant(6)}
</TruncatedText>
</RowFixed>
<RowFixed gap="0px">
<CurrencyLogo currency={trade.inputAmount.currency} size="20px" style={{ marginRight: '12px' }} />
<Text fontSize={20} fontWeight={500} data-testid="input-symbol">
{trade.inputAmount.currency.symbol}
</Text>
</RowFixed>
</RowBetween>
<RowBetween>
<FiatValue fiatValue={fiatValueInput} />
</RowBetween>
</AutoColumn>
</LightCard>
<ArrowWrapper>
<ArrowDown size="16" color={theme.textPrimary} />
</ArrowWrapper>
<LightCard padding="0.75rem 1rem" style={{ marginBottom: '0.25rem' }}>
<AutoColumn gap="sm">
<RowBetween align="flex-end">
<RowFixed gap="0px">
<TruncatedText fontSize={24} fontWeight={500} data-testid="output-amount">
{trade.outputAmount.toSignificant(6)}
</TruncatedText>
</RowFixed>
<RowFixed gap="0px">
<CurrencyLogo currency={trade.outputAmount.currency} size="20px" style={{ marginRight: '12px' }} />
<Text fontSize={20} fontWeight={500} data-testid="output-symbol">
{trade.outputAmount.currency.symbol}
</Text>
</RowFixed>
</RowBetween>
<RowBetween>
<ThemedText.DeprecatedBody fontSize={14} color={theme.textTertiary}>
<FiatValue
fiatValue={fiatValueOutput}
priceImpact={computeFiatValuePriceImpact(fiatValueInput.data, fiatValueOutput.data)}
/>
</ThemedText.DeprecatedBody>
</RowBetween>
</AutoColumn>
</LightCard>
<RowBetween style={{ marginTop: '0.25rem', padding: '0 1rem' }}>
<TradePrice price={trade.executionPrice} />
</RowBetween>
<LightCard style={{ padding: '.75rem', marginTop: '0.5rem' }}>
<AdvancedSwapDetails trade={trade} allowedSlippage={allowedSlippage} />
</LightCard>
{showAcceptChanges ? (
<SwapShowAcceptChanges justify="flex-start" gap="0px" data-testid="show-accept-changes">
<RowBetween>
<RowFixed>
<AlertTriangle size={20} style={{ marginRight: '8px', minWidth: 24 }} />
<ThemedText.DeprecatedMain color={theme.accentAction}>
<Trans>Price Updated</Trans>
</ThemedText.DeprecatedMain>
</RowFixed>
<ButtonPrimary
style={{ padding: '.5rem', width: 'fit-content', fontSize: '0.825rem', borderRadius: '12px' }}
onClick={onAcceptChanges}
>
<Trans>Accept</Trans>
</ButtonPrimary>
</RowBetween>
</SwapShowAcceptChanges>
) : null}
<AutoColumn justify="flex-start" gap="sm" style={{ padding: '.75rem 1rem' }}>
{trade.tradeType === TradeType.EXACT_INPUT ? (
<ThemedText.DeprecatedItalic fontWeight={400} textAlign="left" style={{ width: '100%' }}>
<Trans>
Output is estimated. You will receive at least{' '}
<b>
{trade.minimumAmountOut(allowedSlippage).toSignificant(6)} {trade.outputAmount.currency.symbol}
</b>{' '}
or the transaction will revert.
</Trans>
</ThemedText.DeprecatedItalic>
) : (
<ThemedText.DeprecatedItalic fontWeight={400} textAlign="left" style={{ width: '100%' }}>
<Trans>
Input is estimated. You will sell at most{' '}
<b>
{trade.maximumAmountIn(allowedSlippage).toSignificant(6)} {trade.inputAmount.currency.symbol}
</b>{' '}
or the transaction will revert.
</Trans>
</ThemedText.DeprecatedItalic>
)}
</AutoColumn>
{recipient !== null ? (
<AutoColumn justify="flex-start" gap="sm" style={{ padding: '12px 0 0 0px' }} data-testid="recipient-info">
<ThemedText.DeprecatedMain>
<Trans>
Output will be sent to{' '}
<b title={recipient}>{isAddress(recipient) ? shortenAddress(recipient) : recipient}</b>
</Trans>
</ThemedText.DeprecatedMain>
</AutoColumn>
) : null}
</AutoColumn>
<HeaderContainer gap="sm">
<Column gap="lg">
<SwapModalHeaderAmount
field={Field.INPUT}
label={<Trans>You pay</Trans>}
amount={trade.inputAmount}
usdAmount={fiatValueInput.data}
/>
<SwapModalHeaderAmount
field={Field.OUTPUT}
label={<Trans>You receive</Trans>}
amount={trade.outputAmount}
usdAmount={fiatValueOutput.data}
tooltipText={
trade.tradeType === TradeType.EXACT_INPUT ? (
<ThemedText.Caption>
<Trans>
Output is estimated. You will receive at least{' '}
<b>
{trade.minimumAmountOut(allowedSlippage).toSignificant(6)} {trade.outputAmount.currency.symbol}
</b>{' '}
or the transaction will revert.
</Trans>
</ThemedText.Caption>
) : (
<ThemedText.Caption>
<Trans>
Input is estimated. You will sell at most{' '}
<b>
{trade.maximumAmountIn(allowedSlippage).toSignificant(6)} {trade.inputAmount.currency.symbol}
</b>{' '}
or the transaction will revert.
</Trans>
</ThemedText.Caption>
)
}
/>
</Column>
<Rule />
</HeaderContainer>
)
}

@ -0,0 +1,68 @@
import { formatCurrencyAmount, formatNumber, NumberType } from '@uniswap/conedison/format'
import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
import Column from 'components/Column'
import CurrencyLogo from 'components/Logo/CurrencyLogo'
import Row from 'components/Row'
import { MouseoverTooltip } from 'components/Tooltip'
import { useWindowSize } from 'hooks/useWindowSize'
import { PropsWithChildren, ReactNode } from 'react'
import { TextProps } from 'rebass'
import { Field } from 'state/swap/actions'
import styled from 'styled-components/macro'
import { BREAKPOINTS, ThemedText } from 'theme'
const MAX_AMOUNT_STR_LENGTH = 9
export const Label = styled(ThemedText.BodySmall)<{ cursor?: string }>`
cursor: ${({ cursor }) => cursor};
color: ${({ theme }) => theme.textSecondary};
margin-right: 8px;
`
const ResponsiveHeadline = ({ children, ...textProps }: PropsWithChildren<TextProps>) => {
const { width } = useWindowSize()
if (width && width < BREAKPOINTS.xs) {
return <ThemedText.HeadlineMedium {...textProps}>{children}</ThemedText.HeadlineMedium>
}
return <ThemedText.HeadlineLarge {...textProps}>{children}</ThemedText.HeadlineLarge>
}
interface AmountProps {
field: Field
tooltipText?: ReactNode
label: ReactNode
amount: CurrencyAmount<Currency> | undefined
usdAmount?: number
}
export function SwapModalHeaderAmount({ tooltipText, label, amount, usdAmount, field }: AmountProps) {
let formattedAmount = formatCurrencyAmount(amount, NumberType.TokenTx)
if (formattedAmount.length > MAX_AMOUNT_STR_LENGTH) {
formattedAmount = formatCurrencyAmount(amount, NumberType.SwapTradeAmount)
}
return (
<Row align="center" justify="space-between" gap="md">
<Column gap="xs">
<ThemedText.BodySecondary>
<MouseoverTooltip text={tooltipText} disabled={!tooltipText}>
<Label cursor="help">{label}</Label>
</MouseoverTooltip>
</ThemedText.BodySecondary>
<Column gap="xs">
<ResponsiveHeadline data-testid={`${field}-amount`}>
{formattedAmount} {amount?.currency.symbol}
</ResponsiveHeadline>
{usdAmount && (
<ThemedText.BodySmall color="textTertiary">
{formatNumber(usdAmount, NumberType.FiatTokenQuantity)}
</ThemedText.BodySmall>
)}
</Column>
</Column>
{amount?.currency && <CurrencyLogo currency={amount.currency} size="36px" />}
</Row>
)
}

@ -1,7 +1,172 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SwapModalFooter.tsx renders with a disabled button with no account 1`] = `
exports[`SwapModalFooter.tsx matches base snapshot, test trade exact input 1`] = `
<DocumentFragment>
.c3 {
box-sizing: border-box;
margin: 0;
min-width: 0;
}
.c4 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: flex-start;
-webkit-box-align: flex-start;
-ms-flex-align: flex-start;
align-items: flex-start;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
gap: 8px;
}
.c2 {
color: #0D111C;
}
.c0 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
gap: 12px;
}
.c7 {
display: inline-block;
height: inherit;
}
.c5 {
color: #7780A0;
margin-right: 8px;
}
.c8 {
cursor: help;
color: #7780A0;
margin-right: 8px;
}
.c1 {
padding: 0 8px;
}
.c6 {
text-align: right;
overflow-wrap: break-word;
}
<div
class="c0 c1"
>
<div
class="c2 css-zhpkf8"
>
<div
class="c3 c4"
>
<div
class="c2 c5 css-zhpkf8"
>
Exchange rate
</div>
<div
class="c2 c6 css-zhpkf8"
>
1 DEF = 1.00 ABC
</div>
</div>
</div>
<div
class="c2 css-zhpkf8"
>
<div
class="c3 c4"
>
<div
class="c7"
>
<div>
<div
class="c2 c8 css-zhpkf8"
cursor="help"
>
Network fee
</div>
</div>
</div>
<div
class="c2 c6 css-zhpkf8"
>
~$1.00
</div>
</div>
</div>
<div
class="c2 css-zhpkf8"
>
<div
class="c3 c4"
>
<div
class="c7"
>
<div>
<div
class="c2 c8 css-zhpkf8"
cursor="help"
>
Price impact
</div>
</div>
</div>
<div
class="c6 css-zhpkf8"
>
105566.373%
</div>
</div>
</div>
<div
class="c2 css-zhpkf8"
>
<div
class="c3 c4"
>
<div
class="c7"
>
<div>
<div
class="c2 c8 css-zhpkf8"
cursor="help"
>
Minimum received
</div>
</div>
</div>
<div
class="c2 c6 css-zhpkf8"
>
0.00000000000000098 DEF
</div>
</div>
</div>
</div>
.c0 {
box-sizing: border-box;
margin: 0;
@ -58,6 +223,10 @@ exports[`SwapModalFooter.tsx renders with a disabled button with no account 1`]
margin: !important;
}
.c7 {
color: #F5F6FC;
}
.c4 {
padding: 16px;
width: 100%;
@ -146,106 +315,24 @@ exports[`SwapModalFooter.tsx renders with a disabled button with no account 1`]
}
.c6 {
background-color: rgba(250,43,57,0.1);
border-radius: 1rem;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
font-size: 0.825rem;
width: 100%;
padding: 3rem 1.25rem 1rem 1rem;
margin-top: -2rem;
color: #FA2B39;
z-index: -1;
}
.c6 p {
padding: 0;
margin: 0;
font-weight: 500;
}
.c7 {
background-color: rgba(250,43,57,0.1);
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
margin-right: 12px;
border-radius: 12px;
min-width: 48px;
height: 48px;
height: 56px;
margin-top: 10px;
}
<div
class="c0 c1 c2"
>
<button
class="c3 c4 c5"
class="c3 c4 c5 c6"
data-testid="confirm-swap-button"
disabled=""
id="confirm-swap-or-send"
style="margin: 10px 0px 0px 0px;"
>
<div
class="css-10ob8xa"
class="c7 css-iapcxi"
>
Confirm Swap
Swap
</div>
</button>
<div
class="c6"
>
<div
class="c7"
>
<svg
fill="none"
height="24"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
/>
<line
x1="12"
x2="12"
y1="9"
y2="13"
/>
<line
x1="12"
x2="12.01"
y1="17"
y2="17"
/>
</svg>
</div>
<p
style="word-break: break-word;"
>
swap error
</p>
</div>
</div>
</DocumentFragment>
`;

@ -1,21 +1,14 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SwapModalHeader.tsx matches base snapshot for test trade with exact input 1`] = `
exports[`SwapModalHeader.tsx matches base snapshot, test trade exact input 1`] = `
<DocumentFragment>
.c1 {
box-sizing: border-box;
margin: 0;
min-width: 0;
padding: 0.75rem 1rem;
}
.c5 {
.c3 {
box-sizing: border-box;
margin: 0;
min-width: 0;
}
.c6 {
.c4 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
@ -26,99 +19,30 @@ exports[`SwapModalHeader.tsx matches base snapshot for test trade with exact inp
-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;
}
.c8 {
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: 0px;
}
.c16 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: flex-end;
-webkit-box-align: flex-end;
-ms-flex-align: flex-end;
align-items: flex-end;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
}
.c7 {
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
gap: 12px;
}
.c9 {
width: -webkit-fit-content;
width: -moz-fit-content;
width: fit-content;
margin: -0px;
}
.c24 {
width: -webkit-fit-content;
width: -moz-fit-content;
width: fit-content;
}
.c18 {
color: #0D111C;
}
.c23 {
.c6 {
color: #7780A0;
}
.c21 {
.c8 {
color: #0D111C;
}
.c12 {
width: 100%;
height: 1px;
border-width: 0;
margin: 0;
background-color: #D2D9EE;
}
.c2 {
width: 100%;
padding: 0.75rem 1rem;
border-radius: 16px;
}
.c19 {
width: 100%;
padding: 1rem;
border-radius: 16px;
}
.c3 {
border: 1px solid #E8ECFB;
background-color: #F5F6FC;
}
.c20 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
@ -130,63 +54,39 @@ exports[`SwapModalHeader.tsx matches base snapshot for test trade with exact inp
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
gap: 12px;
gap: 24px;
}
.c5 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
gap: 4px;
}
.c0 {
display: grid;
grid-auto-rows: auto;
grid-row-gap: 4px;
}
.c4 {
display: grid;
grid-auto-rows: auto;
grid-row-gap: 8px;
}
.c25 {
display: grid;
grid-auto-rows: auto;
grid-row-gap: 8px;
justify-items: flex-start;
}
.c13 {
border-radius: 12px;
border-radius: 12px;
height: 24px;
width: 50%;
width: 50%;
-webkit-animation: fAQEyV 1.5s infinite;
animation: fAQEyV 1.5s infinite;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
background: linear-gradient( to left,#E8ECFB 25%,#fff 50%,#E8ECFB 75% );
will-change: background-position;
background-size: 400%;
}
.c22 {
display: inline-block;
height: inherit;
}
.c14 {
border-radius: 4px;
width: 4rem;
height: 1rem;
}
.c12 {
width: 20px;
height: 20px;
.c11 {
width: 36px;
height: 36px;
border-radius: 50%;
background: radial-gradient(white 60%,#ffffff00 calc(70% + 1px));
box-shadow: 0 0 1px white;
}
.c11 {
.c10 {
position: relative;
display: -webkit-box;
display: -webkit-flex;
@ -194,362 +94,334 @@ exports[`SwapModalHeader.tsx matches base snapshot for test trade with exact inp
display: flex;
}
.c17 {
background-color: transparent;
border: none;
cursor: pointer;
.c7 {
display: inline-block;
height: inherit;
}
.c9 {
cursor: help;
color: #7780A0;
margin-right: 8px;
}
.c13 {
margin: 16px 2px 24px 2px;
}
.c1 {
margin-top: 16px;
}
<div
class="c0 c1"
>
<div
class="c2"
>
<div
class="c3 c4"
>
<div
class="c5"
>
<div
class="c6 css-1jljtub"
>
<div
class="c7"
>
<div>
<div
class="c8 c9 css-zhpkf8"
cursor="help"
>
You pay
</div>
</div>
</div>
</div>
<div
class="c5"
>
<div
class="c8 css-xdrz3"
data-testid="INPUT-amount"
>
&lt;0.00001 ABC
</div>
</div>
</div>
<div
class="c10"
>
<img
alt="ABC logo"
class="c11"
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x0000000000000000000000000000000000000001/logo.png"
/>
</div>
</div>
<div
class="c3 c4"
>
<div
class="c5"
>
<div
class="c6 css-1jljtub"
>
<div
class="c7"
>
<div>
<div
class="c8 c9 css-zhpkf8"
cursor="help"
>
You receive
</div>
</div>
</div>
</div>
<div
class="c5"
>
<div
class="c8 css-xdrz3"
data-testid="OUTPUT-amount"
>
&lt;0.00001 DEF
</div>
</div>
</div>
<div
class="c10"
>
<img
alt="DEF logo"
class="c11"
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x0000000000000000000000000000000000000002/logo.png"
/>
</div>
</div>
</div>
<div
class="c12 c13"
/>
</div>
</DocumentFragment>
`;
exports[`SwapModalHeader.tsx test trade exact output, no recipient 1`] = `
<DocumentFragment>
.c3 {
box-sizing: border-box;
margin: 0;
min-width: 0;
}
.c4 {
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: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
gap: 12px;
}
.c6 {
color: #7780A0;
}
.c8 {
color: #0D111C;
}
.c12 {
width: 100%;
height: 1px;
border-width: 0;
margin: 0;
background-color: #D2D9EE;
}
.c2 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
padding: 0;
grid-template-columns: 1fr auto;
grid-gap: 0.25rem;
gap: 24px;
}
.c5 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
text-align: left;
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
-webkit-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
gap: 4px;
}
.c0 {
display: grid;
grid-auto-rows: auto;
grid-row-gap: 8px;
}
.c11 {
width: 36px;
height: 36px;
border-radius: 50%;
background: radial-gradient(white 60%,#ffffff00 calc(70% + 1px));
box-shadow: 0 0 1px white;
}
.c10 {
text-overflow: ellipsis;
max-width: 220px;
overflow: hidden;
text-align: right;
}
.c15 {
padding: 4px;
border-radius: 12px;
height: 40px;
width: 40px;
position: relative;
margin-top: -18px;
margin-bottom: -18px;
left: calc(50% - 16px);
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
background-color: #FFFFFF;
border: 4px solid;
border-color: #F5F6FC;
z-index: 2;
}
.c7 {
display: inline-block;
height: inherit;
}
.c9 {
cursor: help;
color: #7780A0;
margin-right: 8px;
}
.c13 {
margin: 16px 2px 24px 2px;
}
.c1 {
margin-top: 16px;
}
<div
class="c0"
style="margin-top: 1rem;"
class="c0 c1"
>
<div
class="c1 c2 c3"
class="c2"
>
<div
class="c4"
class="c3 c4"
>
<div
class="c5 c6 c7"
class="c5"
>
<div
class="c5 c8 c9"
class="c6 css-1jljtub"
>
<div
class="c10 css-13xjr5l"
data-testid="input-amount"
>
0.000000000000001
</div>
</div>
<div
class="c5 c8 c9"
>
<div
class="c11"
style="margin-right: 12px;"
>
<img
alt="ABC logo"
class="c12"
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x0000000000000000000000000000000000000001/logo.png"
/>
</div>
<div
class="css-10ob8xa"
data-testid="input-symbol"
>
ABC
</div>
</div>
</div>
<div
class="c5 c6 c7"
>
<div
class="css-zhpkf8"
>
<div
class="c13 c14"
/>
</div>
</div>
</div>
</div>
<div
class="c15"
>
<svg
fill="none"
height="16"
stroke="#0D111C"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<line
x1="12"
x2="12"
y1="5"
y2="19"
/>
<polyline
points="19 12 12 19 5 12"
/>
</svg>
</div>
<div
class="c1 c2 c3"
style="margin-bottom: 0.25rem;"
>
<div
class="c4"
>
<div
class="c5 c16 c7"
>
<div
class="c5 c8 c9"
>
<div
class="c10 css-1kwqs79"
data-testid="output-amount"
>
0.000000000000001
</div>
</div>
<div
class="c5 c8 c9"
>
<div
class="c11"
style="margin-right: 12px;"
>
<img
alt="DEF logo"
class="c12"
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x0000000000000000000000000000000000000002/logo.png"
/>
</div>
<div
class="css-10ob8xa"
data-testid="output-symbol"
>
DEF
</div>
</div>
</div>
<div
class="c5 c6 c7"
>
<div
class="css-zhpkf8"
>
<div
class="css-zhpkf8"
>
<div
class="c13 c14"
/>
</div>
</div>
</div>
</div>
</div>
<div
class="c5 c6 c7"
style="margin-top: 0.25rem; padding: 0px 1rem;"
>
<button
class="c17"
title="1 DEF = 1.00 ABC "
>
<div
class="c18 css-zhpkf8"
>
1 DEF = 1.00 ABC
</div>
</button>
</div>
<div
class="c5 c19 c3"
style="padding: .75rem; margin-top: 0.5rem;"
>
<div
class="c20"
>
<div
class="c21"
/>
<div
class="c5 c6 c7"
>
<div
class="c22"
>
<div>
<div
class="c23 css-zhpkf8"
>
Network fee
</div>
</div>
</div>
<div
class="c18 css-zhpkf8"
>
~$1.00
</div>
</div>
<div
class="c5 c6 c7"
>
<div
class="c5 c6 c24"
>
<div
class="c22"
class="c7"
>
<div>
<div
class="c23 css-zhpkf8"
class="c8 c9 css-zhpkf8"
cursor="help"
>
Minimum output
You pay
</div>
</div>
</div>
</div>
<div
class="c18 css-zhpkf8"
class="c5"
>
0.00000000000000098 DEF
<div
class="c8 css-xdrz3"
data-testid="INPUT-amount"
>
&lt;0.00001 ABC
</div>
</div>
</div>
<div
class="c5 c6 c7"
class="c10"
>
<img
alt="ABC logo"
class="c11"
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x0000000000000000000000000000000000000001/logo.png"
/>
</div>
</div>
<div
class="c3 c4"
>
<div
class="c5"
>
<div
class="c5 c6 c24"
class="c6 css-1jljtub"
>
<div
class="c22"
class="c7"
>
<div>
<div
class="c23 css-zhpkf8"
class="c8 c9 css-zhpkf8"
cursor="help"
>
Expected output
You receive
</div>
</div>
</div>
</div>
<div
class="c18 css-zhpkf8"
class="c5"
>
0.000000000000001 DEF
</div>
</div>
<div
class="c21"
/>
<div
class="c5 c6 c7"
>
<div
class="c23 css-zhpkf8"
>
Order routing
</div>
<div
class="c22"
>
<div>
<div
class="c18 css-zhpkf8"
>
Uniswap API
</div>
<div
class="c8 css-xdrz3"
data-testid="OUTPUT-amount"
>
&lt;0.00001 GHI
</div>
</div>
</div>
</div>
</div>
<div
class="c25"
style="padding: .75rem 1rem;"
>
<div
class="c23 css-k51stg"
style="width: 100%;"
>
Output is estimated. You will receive at least
<b>
0.00000000000000098 DEF
</b>
or the transaction will revert.
</div>
</div>
<div
class="c25"
data-testid="recipient-info"
style="padding: 12px 0px 0px 0px;"
>
<div
class="c23 css-8mokm4"
>
Output will be sent to
<b
title="0x0000000000000000000000000000000000000004"
<div
class="c10"
>
0x0000...0004
</b>
<img
alt="GHI logo"
class="c11"
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x0000000000000000000000000000000000000003/logo.png"
/>
</div>
</div>
</div>
<div
class="c12 c13"
/>
</div>
</DocumentFragment>
`;

@ -2,7 +2,6 @@ import { SupportedChainId } from 'constants/chains'
import { transparentize } from 'polished'
import { ReactNode } from 'react'
import { AlertTriangle } from 'react-feather'
import { Text } from 'rebass'
import styled, { css } from 'styled-components/macro'
import { Z_INDEX } from 'theme/zIndex'
@ -64,13 +63,6 @@ export const ArrowWrapper = styled.div<{ clickable: boolean }>`
: null}
`
export const TruncatedText = styled(Text)`
text-overflow: ellipsis;
max-width: 220px;
overflow: hidden;
text-align: right;
`
// styles
export const Dots = styled.span`
&::after {
@ -136,7 +128,7 @@ export function SwapCallbackError({ error }: { error: ReactNode }) {
export const SwapShowAcceptChanges = styled(AutoColumn)`
background-color: ${({ theme }) => transparentize(0.95, theme.deprecated_primary3)};
color: ${({ theme }) => theme.accentAction};
padding: 0.5rem;
padding: 12px;
border-radius: 12px;
margin-top: 8px;
`

@ -46,7 +46,7 @@ import useIsArgentWallet from '../../hooks/useIsArgentWallet'
import { useTotalSupply } from '../../hooks/useTotalSupply'
import { useTokenBalance } from '../../state/connection/hooks'
import { TransactionType } from '../../state/transactions/types'
import { BackArrow, ExternalLink, ThemedText } from '../../theme'
import { BackArrowLink, ExternalLink, ThemedText } from '../../theme'
import { isAddress } from '../../utils'
import { calculateGasMargin } from '../../utils/calculateGasMargin'
import { currencyId } from '../../utils/currencyId'
@ -725,7 +725,7 @@ export default function MigrateV2Pair() {
<BodyWrapper style={{ padding: 24 }}>
<AutoColumn gap="16px">
<AutoRow style={{ alignItems: 'center', justifyContent: 'space-between' }} gap="8px">
<BackArrow to="/migrate/v2" />
<BackArrowLink to="/migrate/v2" />
<ThemedText.DeprecatedMediumHeader>
<Trans>Migrate V2 Liquidity</Trans>
</ThemedText.DeprecatedMediumHeader>

@ -20,7 +20,7 @@ import { Dots } from '../../components/swap/styleds'
import { V2_FACTORY_ADDRESSES } from '../../constants/addresses'
import { useTokenBalancesWithLoadingIndicator } from '../../state/connection/hooks'
import { toV2LiquidityToken, useTrackedTokenPairs } from '../../state/user/hooks'
import { BackArrow, StyledInternalLink, ThemedText } from '../../theme'
import { BackArrowLink, StyledInternalLink, ThemedText } from '../../theme'
import { BodyWrapper } from '../AppBody'
function EmptyState({ message }: { message: ReactNode }) {
@ -116,7 +116,7 @@ export default function MigrateV2() {
<BodyWrapper style={{ padding: 24 }}>
<AutoColumn gap="16px">
<AutoRow style={{ alignItems: 'center', justifyContent: 'space-between' }} gap="8px">
<BackArrow to="/pools" />
<BackArrowLink to="/pools" />
<ThemedText.DeprecatedMediumHeader>
<Trans>Migrate V2 Liquidity</Trans>
</ThemedText.DeprecatedMediumHeader>

@ -571,22 +571,22 @@ export function Swap({
showCancel={true}
/>
<SwapHeader autoSlippage={autoSlippage} />
<ConfirmSwapModal
isOpen={showConfirm}
trade={trade}
originalTrade={tradeToConfirm}
onAcceptChanges={handleAcceptChanges}
attemptingTxn={attemptingTxn}
txHash={txHash}
recipient={recipient}
allowedSlippage={allowedSlippage}
onConfirm={handleSwap}
swapErrorMessage={swapErrorMessage}
onDismiss={handleConfirmDismiss}
swapQuoteReceivedDate={swapQuoteReceivedDate}
fiatValueInput={fiatValueTradeInput}
fiatValueOutput={fiatValueTradeOutput}
/>
{trade && showConfirm && (
<ConfirmSwapModal
trade={trade}
originalTrade={tradeToConfirm}
onAcceptChanges={handleAcceptChanges}
attemptingTxn={attemptingTxn}
txHash={txHash}
allowedSlippage={allowedSlippage}
onConfirm={handleSwap}
swapErrorMessage={swapErrorMessage}
onDismiss={handleConfirmDismiss}
swapQuoteReceivedDate={swapQuoteReceivedDate}
fiatValueInput={fiatValueTradeInput}
fiatValueOutput={fiatValueTradeOutput}
/>
)}
<div style={{ display: 'relative' }}>
<SwapSection>

@ -467,14 +467,15 @@ export const SpinnerSVG = styled.svg`
${SpinnerCss}
`
const BackArrowLink = styled(StyledInternalLink)`
const BackArrowIcon = styled(ArrowLeft)`
color: ${({ theme }) => theme.textPrimary};
`
export function BackArrow({ to }: { to: string }) {
export function BackArrowLink({ to }: { to: string }) {
return (
<BackArrowLink to={to}>
<ArrowLeft />
</BackArrowLink>
<StyledInternalLink to={to}>
<BackArrowIcon />
</StyledInternalLink>
)
}
@ -523,3 +524,11 @@ export const GlowEffect = styled.div`
export const CautionTriangle = styled(AlertTriangle)`
color: ${({ theme }) => theme.accentWarning};
`
export const Divider = styled.div`
width: 100%;
height: 1px;
border-width: 0;
margin: 0;
background-color: ${({ theme }) => theme.backgroundOutline};
`

@ -52,6 +52,9 @@ export const ThemedText = {
MediumHeader(props: TextProps) {
return <TextWrapper fontWeight={400} fontSize={20} color="textPrimary" {...props} />
},
SubHeaderLarge(props: TextProps) {
return <TextWrapper fontWeight={500} fontSize={20} color="textPrimary" {...props} />
},
SubHeader(props: TextProps) {
return <TextWrapper fontWeight={500} fontSize={16} color="textPrimary" lineHeight="24px" {...props} />
},

@ -67,15 +67,18 @@ const fonts = {
code: 'courier, courier new, serif',
}
const gapValues = {
xs: '4px',
sm: '8px',
md: '12px',
lg: '24px',
xl: '32px',
}
export type Gap = keyof typeof gapValues
function getSettings(darkMode: boolean) {
return {
grids: {
xs: '4px',
sm: '8px',
md: '12px',
lg: '24px',
xl: '32px',
},
grids: gapValues,
fonts,
// shadows
@ -118,7 +121,7 @@ export const ThemedGlobalStyle = createGlobalStyle`
background-color: ${({ theme }) => theme.background} !important;
}
summary::-webkit-details-marker {
summary::-webkit-details-marker {
display:none;
}

@ -0,0 +1,107 @@
import { SwapPriceUpdateUserResponse } from '@uniswap/analytics-events'
import { Percent } from '@uniswap/sdk-core'
import {
formatPercentInBasisPointsNumber,
formatPercentNumber,
formatToDecimal,
getDurationFromDateMilliseconds,
getDurationUntilTimestampSeconds,
getTokenAddress,
} from 'lib/utils/analytics'
import { InterfaceTrade } from 'state/routing/types'
import { RoutingDiagramEntry } from './getRoutingDiagramEntries'
import { computeRealizedPriceImpact } from './prices'
const formatRoutesEventProperties = (routes: RoutingDiagramEntry[]) => {
const routesEventProperties: Record<string, any[]> = {
routes_percentages: [],
routes_protocols: [],
}
routes.forEach((route, index) => {
routesEventProperties['routes_percentages'].push(formatPercentNumber(route.percent))
routesEventProperties['routes_protocols'].push(route.protocol)
routesEventProperties[`route_${index}_input_currency_symbols`] = route.path.map(
(pathStep) => pathStep[0].symbol ?? ''
)
routesEventProperties[`route_${index}_output_currency_symbols`] = route.path.map(
(pathStep) => pathStep[1].symbol ?? ''
)
routesEventProperties[`route_${index}_input_currency_addresses`] = route.path.map((pathStep) =>
getTokenAddress(pathStep[0])
)
routesEventProperties[`route_${index}_output_currency_addresses`] = route.path.map((pathStep) =>
getTokenAddress(pathStep[1])
)
routesEventProperties[`route_${index}_fee_amounts_hundredths_of_bps`] = route.path.map((pathStep) => pathStep[2])
})
return routesEventProperties
}
export const formatSwapPriceUpdatedEventProperties = (
trade: InterfaceTrade,
priceUpdate: number | undefined,
response: SwapPriceUpdateUserResponse
) => ({
chain_id:
trade.inputAmount.currency.chainId === trade.outputAmount.currency.chainId
? trade.inputAmount.currency.chainId
: undefined,
response,
token_in_symbol: trade.inputAmount.currency.symbol,
token_out_symbol: trade.outputAmount.currency.symbol,
price_update_basis_points: priceUpdate,
})
interface AnalyticsEventProps {
trade: InterfaceTrade
hash: string | undefined
allowedSlippage: Percent
transactionDeadlineSecondsSinceEpoch: number | undefined
isAutoSlippage: boolean
isAutoRouterApi: boolean
swapQuoteReceivedDate: Date | undefined
routes: RoutingDiagramEntry[]
fiatValueInput?: number
fiatValueOutput?: number
}
export const formatSwapButtonClickEventProperties = ({
trade,
hash,
allowedSlippage,
transactionDeadlineSecondsSinceEpoch,
isAutoSlippage,
isAutoRouterApi,
swapQuoteReceivedDate,
routes,
fiatValueInput,
fiatValueOutput,
}: AnalyticsEventProps) => ({
estimated_network_fee_usd: trade.gasUseEstimateUSD,
transaction_hash: hash,
transaction_deadline_seconds: getDurationUntilTimestampSeconds(transactionDeadlineSecondsSinceEpoch),
token_in_address: trade ? getTokenAddress(trade.inputAmount.currency) : undefined,
token_out_address: trade ? getTokenAddress(trade.outputAmount.currency) : undefined,
token_in_symbol: trade.inputAmount.currency.symbol,
token_out_symbol: trade.outputAmount.currency.symbol,
token_in_amount: trade ? formatToDecimal(trade.inputAmount, trade.inputAmount.currency.decimals) : undefined,
token_out_amount: trade ? formatToDecimal(trade.outputAmount, trade.outputAmount.currency.decimals) : undefined,
token_in_amount_usd: fiatValueInput,
token_out_amount_usd: fiatValueOutput,
price_impact_basis_points: trade ? formatPercentInBasisPointsNumber(computeRealizedPriceImpact(trade)) : undefined,
allowed_slippage_basis_points: formatPercentInBasisPointsNumber(allowedSlippage),
is_auto_router_api: isAutoRouterApi,
is_auto_slippage: isAutoSlippage,
chain_id:
trade.inputAmount.currency.chainId === trade.outputAmount.currency.chainId
? trade.inputAmount.currency.chainId
: undefined,
duration_from_first_quote_to_swap_submission_milliseconds: swapQuoteReceivedDate
? getDurationFromDateMilliseconds(swapQuoteReceivedDate)
: undefined,
swap_quote_block_number: trade.blockNumber,
...formatRoutesEventProperties(routes),
})