diff --git a/cypress/e2e/permit2.test.ts b/cypress/e2e/permit2.test.ts
index e3fa74af7e..9ad65c2f9f 100644
--- a/cypress/e2e/permit2.test.ts
+++ b/cypress/e2e/permit2.test.ts
@@ -75,8 +75,9 @@ describe('Permit2', () => {
cy.contains('Allow DAI to be used for swapping')
cy.wait('@eth_signTypedData_v4')
cy.wait('@eth_sendRawTransaction')
+ cy.contains('Swap submitted')
cy.hardhat().then((hardhat) => hardhat.mine())
- cy.contains('Success')
+ cy.contains('Swap success!')
cy.get(getTestSelector('popups')).contains('Swapped')
expectPermit2AllowanceForUniversalRouterToBeMax(DAI)
})
@@ -99,7 +100,7 @@ describe('Permit2', () => {
// Verify transaction
cy.wait('@eth_sendRawTransaction')
cy.hardhat().then((hardhat) => hardhat.mine())
- cy.contains('Success')
+ cy.contains('Swap success!')
cy.get(getTestSelector('popups')).contains('Swapped')
})
@@ -142,7 +143,7 @@ describe('Permit2', () => {
// Verify transaction
cy.wait('@eth_sendRawTransaction')
cy.hardhat().then((hardhat) => hardhat.mine())
- cy.contains('Success')
+ cy.contains('Swap success!')
cy.get(getTestSelector('popups')).contains('Swapped')
})
})
@@ -158,7 +159,7 @@ describe('Permit2', () => {
initiateSwap()
// Verify transaction
- cy.contains('Success')
+ cy.contains('Swap success!')
cy.get(getTestSelector('popups')).contains('Swapped')
})
@@ -197,7 +198,7 @@ describe('Permit2', () => {
cy.contains('Confirm swap').click()
// Verify permit2 approval
- cy.contains('Success')
+ cy.contains('Swap success!')
cy.get(getTestSelector('popups')).contains('Swapped')
expectPermit2AllowanceForUniversalRouterToBeMax(DAI)
})
@@ -231,7 +232,7 @@ describe('Permit2', () => {
// Verify permit2 approval
cy.wait('@eth_signTypedData_v4')
- cy.contains('Success')
+ cy.contains('Swap success!')
cy.get(getTestSelector('popups')).contains('Swapped')
expectPermit2AllowanceForUniversalRouterToBeMax(DAI)
})
@@ -249,7 +250,7 @@ describe('Permit2', () => {
// Verify permit2 approval
cy.wait('@eth_signTypedData_v4')
- cy.contains('Success')
+ cy.contains('Swap success!')
cy.get(getTestSelector('popups')).contains('Swapped')
expectPermit2AllowanceForUniversalRouterToBeMax(DAI)
})
diff --git a/src/components/swap/PendingModalContent/Logos.tsx b/src/components/swap/PendingModalContent/Logos.tsx
index 938e6c510e..efb3c745b0 100644
--- a/src/components/swap/PendingModalContent/Logos.tsx
+++ b/src/components/swap/PendingModalContent/Logos.tsx
@@ -162,7 +162,38 @@ function ConfirmedIcon({ className }: { className?: string }) {
)
}
-export const AnimatedEntranceConfirmationIcon = styled(ConfirmedIcon)`
+function SubmittedIcon({ className }: { className?: string }) {
+ const theme = useTheme()
+ return (
+
+
+
+ )
+}
+
+const IconCss = css`
+ position: absolute;
height: 48px;
width: 48px;
`
+
+export const AnimatedEntranceConfirmationIcon = styled(ConfirmedIcon)`
+ ${IconCss}
+`
+
+export const AnimatedEntranceSubmittedIcon = styled(SubmittedIcon)`
+ ${IconCss}
+`
diff --git a/src/components/swap/PendingModalContent/PendingModalContent.test.tsx b/src/components/swap/PendingModalContent/PendingModalContent.test.tsx
index 8bd0599ed8..9b1bc5cdb5 100644
--- a/src/components/swap/PendingModalContent/PendingModalContent.test.tsx
+++ b/src/components/swap/PendingModalContent/PendingModalContent.test.tsx
@@ -128,6 +128,38 @@ describe('PendingModalContent', () => {
expect(screen.queryByTestId('pending-modal-currency-logo-loader')).toBeNull()
})
+ it('renders the submitted icon instead of the given logo on mainnet when the transaction is submitted', () => {
+ mocked(useSwapTransactionStatus).mockReturnValue(TransactionStatus.Pending)
+
+ render(
+
+ )
+ expect(screen.queryByTestId('pending-modal-failure-icon')).toBeNull()
+ expect(screen.queryByTestId('pending-modal-currency-logo-loader')).toBeNull()
+ expect(screen.getByTestId('submitted-icon')).toBeInTheDocument()
+ })
+
it('renders the success icon instead of the given logo when confirmed and successful', () => {
mocked(useSwapTransactionStatus).mockReturnValue(TransactionStatus.Confirmed)
diff --git a/src/components/swap/PendingModalContent/index.tsx b/src/components/swap/PendingModalContent/index.tsx
index cb8c800f81..04abaaa4fc 100644
--- a/src/components/swap/PendingModalContent/index.tsx
+++ b/src/components/swap/PendingModalContent/index.tsx
@@ -9,7 +9,7 @@ import { TransactionStatus } from 'graphql/data/__generated__/types-and-hooks'
import { SwapResult } from 'hooks/useSwapCallback'
import { useUnmountingAnimation } from 'hooks/useUnmountingAnimation'
import { UniswapXOrderStatus } from 'lib/hooks/orders/types'
-import { ReactNode, useRef } from 'react'
+import { ReactNode, useMemo, useRef } from 'react'
import { InterfaceTrade, TradeFillType } from 'state/routing/types'
import { useOrder } from 'state/signatures/hooks'
import { UniswapXOrderDetails } from 'state/signatures/types'
@@ -23,6 +23,7 @@ import { ExplorerDataType } from 'utils/getExplorerLink'
import { ConfirmModalState } from '../ConfirmSwapModal'
import {
AnimatedEntranceConfirmationIcon,
+ AnimatedEntranceSubmittedIcon,
AnimationType,
CurrencyLoader,
LoadingIndicatorOverlay,
@@ -102,7 +103,7 @@ export type PendingConfirmModalState = Extract<
interface PendingModalStep {
title: ReactNode
subtitle?: ReactNode
- label?: ReactNode
+ bottomLabel?: ReactNode
logo?: ReactNode
button?: ReactNode
}
@@ -119,7 +120,6 @@ interface PendingModalContentProps {
}
interface ContentArgs {
- step: PendingConfirmModalState
approvalCurrency?: Currency
trade?: InterfaceTrade
swapConfirmed: boolean
@@ -132,9 +132,59 @@ interface ContentArgs {
order?: UniswapXOrderDetails
}
-function getContent(args: ContentArgs): PendingModalStep {
+function getPendingConfirmationContent({
+ swapConfirmed,
+ swapPending,
+ trade,
+ chainId,
+ swapResult,
+}: Pick): PendingModalStep {
+ const title = swapPending ? t`Swap submitted` : swapConfirmed ? t`Swap success!` : t`Confirm Swap`
+ const tradeSummary = trade ? : null
+ if (swapPending && trade?.fillType === TradeFillType.UniswapX) {
+ return {
+ title,
+ subtitle: tradeSummary,
+ bottomLabel: (
+
+ Learn more about swapping with UniswapX
+
+ ),
+ }
+ } else if ((swapPending || swapConfirmed) && chainId && swapResult?.type === TradeFillType.Classic) {
+ const explorerLink = (
+
+ View on Explorer
+
+ )
+ if (swapPending) {
+ // On Mainnet, we show a "submitted" state while the transaction is pending confirmation.
+ return {
+ title,
+ subtitle: chainId === ChainId.MAINNET ? explorerLink : tradeSummary,
+ bottomLabel: chainId === ChainId.MAINNET ? t`Transaction pending...` : explorerLink,
+ }
+ } else {
+ return {
+ title,
+ subtitle: explorerLink,
+ bottomLabel: null,
+ }
+ }
+ } else {
+ return {
+ title,
+ subtitle: tradeSummary,
+ bottomLabel: t`Proceed in your wallet`,
+ }
+ }
+}
+
+function useStepContents(args: ContentArgs): Record {
const {
- step,
wrapPending,
approvalCurrency,
swapConfirmed,
@@ -146,70 +196,60 @@ function getContent(args: ContentArgs): PendingModalStep {
chainId,
} = args
- switch (step) {
- case ConfirmModalState.WRAPPING:
- return {
+ return useMemo(
+ () => ({
+ [ConfirmModalState.WRAPPING]: {
title: t`Wrap ETH`,
subtitle: (
Why is this required?
),
- label: wrapPending ? t`Pending...` : t`Proceed in your wallet`,
- }
- case ConfirmModalState.RESETTING_USDT:
- return {
+ bottomLabel: wrapPending ? t`Pending...` : t`Proceed in your wallet`,
+ },
+ [ConfirmModalState.RESETTING_USDT]: {
title: t`Reset USDT`,
subtitle: t`USDT requires resetting approval when spending limits are too low.`,
- label: revocationPending ? t`Pending...` : t`Proceed in your wallet`,
- }
- case ConfirmModalState.APPROVING_TOKEN:
- return {
+ bottomLabel: revocationPending ? t`Pending...` : t`Proceed in your wallet`,
+ },
+ [ConfirmModalState.APPROVING_TOKEN]: {
title: t`Enable spending ${approvalCurrency?.symbol ?? 'this token'} on Uniswap`,
subtitle: (
Why is this required?
),
- label: tokenApprovalPending ? t`Pending...` : t`Proceed in your wallet`,
- }
- case ConfirmModalState.PERMITTING:
- return {
+ bottomLabel: tokenApprovalPending ? t`Pending...` : t`Proceed in your wallet`,
+ },
+ [ConfirmModalState.PERMITTING]: {
title: t`Allow ${approvalCurrency?.symbol ?? 'this token'} to be used for swapping`,
subtitle: (
Why is this required?
),
- label: t`Proceed in your wallet`,
- }
- case ConfirmModalState.PENDING_CONFIRMATION: {
- let labelText: string | null = null
- let href: string | null = null
-
- if (swapPending && trade?.fillType === TradeFillType.UniswapX) {
- labelText = t`Learn more about swapping with UniswapX`
- href = 'https://support.uniswap.org/hc/en-us/articles/17515415311501'
- } else if (chainId && (swapConfirmed || swapPending) && swapResult && swapResult.type === TradeFillType.Classic) {
- labelText = t`View on Explorer`
- href = getExplorerLink(chainId, swapResult.response.hash, ExplorerDataType.TRANSACTION)
- } else {
- labelText = t`Proceed in your wallet`
- }
-
- return {
- title: swapPending ? t`Swap submitted` : swapConfirmed ? t`Success` : t`Confirm Swap`,
- subtitle: trade ? : null,
- label: href ? (
-
- {labelText}
-
- ) : (
- labelText
- ),
- }
- }
- }
+ bottomLabel: t`Proceed in your wallet`,
+ },
+ [ConfirmModalState.PENDING_CONFIRMATION]: getPendingConfirmationContent({
+ chainId,
+ swapConfirmed,
+ swapPending,
+ swapResult,
+ trade,
+ }),
+ }),
+ [
+ approvalCurrency?.symbol,
+ chainId,
+ revocationPending,
+ swapConfirmed,
+ swapPending,
+ swapResult,
+ tokenApprovalPending,
+ trade,
+ wrapPending,
+ ]
+ )
}
export function PendingModalContent({
@@ -236,8 +276,7 @@ export function PendingModalContent({
const swapPending = swapResult !== undefined && !swapConfirmed
const wrapPending = wrapTxHash != undefined && !wrapConfirmed
- const { label, button } = getContent({
- step: currentStep,
+ const stepContents = useStepContents({
approvalCurrency: trade?.inputAmount.currency,
swapConfirmed,
swapPending,
@@ -263,8 +302,11 @@ export function PendingModalContent({
return
}
- // On mainnet, we show the success icon once the tx is sent, since it takes longer to confirm than on L2s.
- const showSuccess = swapConfirmed || (swapPending && chainId === ChainId.MAINNET)
+ // On mainnet, we show a different icon when the transaction is submitted but pending confirmation.
+ const showSubmitted = swapPending && !swapConfirmed && chainId === ChainId.MAINNET
+ const showSuccess = swapConfirmed || (chainId !== ChainId.MAINNET && swapPending)
+
+ const transactionPending = revocationPending || tokenApprovalPending || wrapPending || swapPending
return (
@@ -282,31 +324,18 @@ export function PendingModalContent({
)}
{/* Shown only during the final step under "success" conditions, and scales in. */}
{currentStep === ConfirmModalState.PENDING_CONFIRMATION && showSuccess && }
- {/* Scales in for the USDT revoke allowance step if the revoke is pending onchain confirmation. */}
- {/* Scales in for the setup approval step if the approval is pending onchain confirmation. */}
- {/* Scales in for the final step if the swap is pending user signature or onchain confirmation. */}
- {((currentStep === ConfirmModalState.PENDING_CONFIRMATION && !showSuccess) ||
- tokenApprovalPending ||
- wrapPending ||
- revocationPending) && }
+ {/* Shown only during the final step on mainnet, when the transaction is sent but pending confirmation. */}
+ {currentStep === ConfirmModalState.PENDING_CONFIRMATION && showSubmitted && }
+ {/* Scales in for any step that waits for an onchain transaction, while the transaction is pending. */}
+ {/* On the last step, appears while waiting for the transaction to be signed too. */}
+ {((currentStep !== ConfirmModalState.PENDING_CONFIRMATION && transactionPending) ||
+ (currentStep === ConfirmModalState.PENDING_CONFIRMATION && !showSuccess && !showSubmitted)) && (
+
+ )}
-
+
{steps.map((step) => {
- const { title, subtitle } = getContent({
- step,
- approvalCurrency: trade?.inputAmount.currency,
- swapConfirmed,
- swapPending,
- wrapPending,
- revocationPending,
- tokenApprovalPending,
- swapResult,
- trade,
- })
// We only render one step at a time, but looping through the array allows us to keep
// the exiting step in the DOM during its animation.
return (
@@ -318,19 +347,19 @@ export function PendingModalContent({
ref={step === currentStep ? currentStepContainerRef : undefined}
>
- {title}
+ {stepContents[step].title}
- {subtitle}
+ {stepContents[step].subtitle}
)
)
})}
- {label}
+ {stepContents[currentStep].bottomLabel}
- {button && {button}
}
+ {stepContents[currentStep].button && {stepContents[currentStep].button}
}
{!hideStepIndicators && !showSuccess && (
{steps.map((_, i) => {