diff --git a/src/components/ReviewInfoText/ReviewInfoText.test.tsx b/src/components/ReviewInfoText/ReviewInfoText.test.tsx index a97373aa73..cd3fcac534 100644 --- a/src/components/ReviewInfoText/ReviewInfoText.test.tsx +++ b/src/components/ReviewInfoText/ReviewInfoText.test.tsx @@ -1,57 +1,64 @@ -import { useSelector } from 'react-redux' import { EstimationStatus } from 'src/logic/hooks/useEstimateTransactionGas' import { render, screen } from 'src/utils/test-utils' import { ReviewInfoText } from './index' -import { history } from 'src/routes/routes' -const safeAddress = '0xC245cb45B044d66fbE8Fb33C26c0b28B4fc367B2' -const url = `/rin:${safeAddress}/settings/advanced` -history.location.pathname = url +jest.mock('src/logic/hooks/useRecommendedNonce', () => ({ + __esModule: true, + default: () => 9, +})) describe('', () => { const initialData = { gasCostFormatted: '0', isExecution: true, - isCreation: false, + isCreation: true, isOffChainSignature: false, txEstimationExecutionStatus: EstimationStatus.SUCCESS, } - const customState = { - safes: { - safes: { - [safeAddress]: { - address: safeAddress, - nonce: 8, - modules: null, - guard: '', - currentVersion: '1.3.0', - }, - }, - }, - } - const testId = 'reviewInfoText-component' - const warningCommonCopy = - 'will need to be created and executed before this transaction, are you sure you want to do this?' - it('Renders ReviewInfoText with safeNonce being one lastTxNonce + 1', () => { - const lastTxNonce = 10 - const safeNonce = `${lastTxNonce + 1}` + it('renders ReviewInfoText with safeNonce in order', () => { + render() + + expect(screen.getByText(/You're about to create a transaction/)).toBeInTheDocument() + }) + + it('renders ReviewInfoText with safeNonce in order and not a creation', () => { + render() + + expect(screen.getByText(/You're about to execute a transaction/)).toBeInTheDocument() + }) - render(, customState) + it('renders ReviewInfoText that is not a creation and nonce in future', () => { + render() - expect(screen.getByTestId(testId)).toBeInTheDocument() - expect(screen.queryByText(warningCommonCopy)).not.toBeInTheDocument() + expect(screen.getByText(/You're about to execute a transaction/)).toBeInTheDocument() + expect(screen.queryByText(/will need to be created and executed before this transaction/)).not.toBeInTheDocument() }) - it('Renders ReviewInfoText with safeNonce more than one transaction ahead of lastTxNonce', () => { - const lastTxNonce = 10 - const safeNonce = `${lastTxNonce + 4}` - const expectedCopy = 'transactions ' + warningCommonCopy + it('renders ReviewInfoText with a safeNonce 1 tx in the future', () => { + render() + + expect(screen.getByText(/1/)).toBeInTheDocument() + expect( + screen.getByText(/transaction will need to be created and executed before this transaction/), + ).toBeInTheDocument() + }) + + it('renders ReviewInfoText with a safeNonce 2 txs the future', () => { + render() + + expect(screen.getByText(/2/)).toBeInTheDocument() + expect( + screen.getByText(/transactions will need to be created and executed before this transaction/), + ).toBeInTheDocument() + }) - render(, customState) + it('renders ReviewInfoText with already used safeNonce', () => { + render() - expect(screen.getByTestId(testId)).toBeInTheDocument() - expect(screen.getByText('6')).toBeInTheDocument() - expect(screen.queryByText(expectedCopy)).toBeInTheDocument() + expect(screen.getByText(/6/)).toBeInTheDocument() + expect(screen.getByText(/9/)).toBeInTheDocument() + expect(screen.getByText(/is below the latest transaction's nonce./)).toBeInTheDocument() + expect(screen.getByText(/Your transaction might fail./)).toBeInTheDocument() }) }) diff --git a/src/components/ReviewInfoText/index.tsx b/src/components/ReviewInfoText/index.tsx index e7a4f63891..b4cf8c3165 100644 --- a/src/components/ReviewInfoText/index.tsx +++ b/src/components/ReviewInfoText/index.tsx @@ -1,16 +1,13 @@ -import styled from 'styled-components' -import { Text } from '@gnosis.pm/safe-react-components' +import { ReactElement } from 'react' import { useSelector } from 'react-redux' +import styled from 'styled-components' import Paragraph from 'src/components/layout/Paragraph' -import { currentSafe } from 'src/logic/safe/store/selectors' -import { getLastTxNonce } from 'src/logic/safe/store/selectors/gatewayTransactions' import { lg } from 'src/theme/variables' -import { getRecommendedNonce } from 'src/logic/safe/api/fetchSafeTxGasEstimation' -import { extractSafeAddress } from 'src/routes/routes' -import { useEffect, useState } from 'react' import { TransactionFailText } from '../TransactionFailText' import { EstimationStatus } from 'src/logic/hooks/useEstimateTransactionGas' +import useRecommendedNonce from 'src/logic/hooks/useRecommendedNonce' +import { currentSafeWithNames } from 'src/logic/safe/store/selectors' const ReviewInfoTextWrapper = styled.div` padding: 0 ${lg}; @@ -27,54 +24,41 @@ type ReviewInfoTextProps = { export const ReviewInfoText = ({ isCreation, isExecution, - safeNonce: txParamsSafeNonce = '', + safeNonce = '', testId, txEstimationExecutionStatus, -}: ReviewInfoTextProps): React.ReactElement => { - const { nonce } = useSelector(currentSafe) - const safeNonceNumber = parseInt(txParamsSafeNonce, 10) - const lastTxNonce = useSelector(getLastTxNonce) - const storeNextNonce = `${lastTxNonce && lastTxNonce + 1}` - const safeAddress = extractSafeAddress() - const [recommendedNonce, setRecommendedNonce] = useState(storeNextNonce) - const transactionAction = isCreation ? 'create' : isExecution ? 'execute' : 'approve' +}: ReviewInfoTextProps): ReactElement => { + const safeTxNonce = parseInt(safeNonce, 10) + const { address: safeAddress } = useSelector(currentSafeWithNames) + const recommendedNonce = useRecommendedNonce(safeAddress) - useEffect(() => { - const fetchRecommendedNonce = async () => { - try { - const recommendedNonce = (await getRecommendedNonce(safeAddress)).toString() - setRecommendedNonce(recommendedNonce) - } catch (e) { - return - } - } - fetchRecommendedNonce() - }, [safeAddress]) + const isTxNonceOutOfOrder = () => { + // safeNonce can be undefined while waiting for the request. + if (isNaN(safeTxNonce)) return false + if (safeTxNonce === recommendedNonce) return false + return true + } - const warningMessage = () => { - const isTxNonceOutOfOrder = () => { - // safeNonce can be undefined while waiting for the request. - if (isNaN(safeNonceNumber) || safeNonceNumber === nonce) return false - if (lastTxNonce !== undefined && safeNonceNumber === lastTxNonce + 1) return false - return true - } - const shouldShowWarning = isTxNonceOutOfOrder() - if (!shouldShowWarning) return null + const getWarning = (): ReactElement | null => { + if (!isCreation) return null + if (!isTxNonceOutOfOrder()) return null + + const transactionsToGo = safeTxNonce - recommendedNonce - const transactionsToGo = safeNonceNumber - nonce return ( - {transactionsToGo < 0 ? ( - `Nonce ${txParamsSafeNonce} has already been used. Your transaction will fail. Please use nonce ${recommendedNonce}.` + {transactionsToGo > 0 ? ( + /* tx in the future */ <> + {transactionsToGo} +  {`transaction${transactionsToGo > 1 ? 's' : ''}`} will need to be created and executed before + this transaction, are you sure you want to do this? + ) : ( - <> - - {transactionsToGo} - - {` transaction${ - transactionsToGo > 1 ? 's' : '' - } will need to be created and executed before this transaction, - are you sure you want to do this?`} + /* tx in the past */ <> + Nonce  + {safeTxNonce} +  is below the latest transaction's nonce. Your transaction might fail. Please use nonce  + {recommendedNonce}. )} @@ -83,11 +67,11 @@ export const ReviewInfoText = ({ return ( - {warningMessage() || ( + {getWarning() || ( <> - You're about to {transactionAction} a transaction and will have to confirm it with your currently - connected wallet. + You're about to {isCreation ? 'create' : isExecution ? 'execute' : 'approve'} a transaction and will + have to confirm it with your currently connected wallet. )} diff --git a/src/logic/hooks/useGetRecommendedNonce.tsx b/src/logic/hooks/useRecommendedNonce.tsx similarity index 64% rename from src/logic/hooks/useGetRecommendedNonce.tsx rename to src/logic/hooks/useRecommendedNonce.tsx index af08fd512b..a820c60a15 100644 --- a/src/logic/hooks/useGetRecommendedNonce.tsx +++ b/src/logic/hooks/useRecommendedNonce.tsx @@ -1,16 +1,12 @@ import { SafeTransactionEstimation } from '@gnosis.pm/safe-react-gateway-sdk' import { useEffect, useState } from 'react' import { useSelector } from 'react-redux' -import { getRecommendedNonce } from '../safe/api/fetchSafeTxGasEstimation' -import { getLastTxNonce } from '../safe/store/selectors/gatewayTransactions' +import { getRecommendedNonce } from 'src/logic/safe/api/fetchSafeTxGasEstimation' +import { getLastTxNonce } from 'src/logic/safe/store/selectors/gatewayTransactions' -type UseGetRecommendedNonce = (safeAddress: string) => number | undefined - -const useGetRecommendedNonce: UseGetRecommendedNonce = (safeAddress) => { +const useRecommendedNonce = (safeAddress: string): number => { const lastTxNonce = useSelector(getLastTxNonce) - const storeNextNonce = lastTxNonce ? lastTxNonce + 1 : undefined - - const [recommendedNonce, setRecommendedNonce] = useState(storeNextNonce) + const [recommendedNonce, setRecommendedNonce] = useState(lastTxNonce ? lastTxNonce + 1 : 0) useEffect(() => { let isCurrent = true @@ -37,4 +33,4 @@ const useGetRecommendedNonce: UseGetRecommendedNonce = (safeAddress) => { return recommendedNonce } -export default useGetRecommendedNonce +export default useRecommendedNonce diff --git a/src/routes/safe/components/Transactions/helpers/EditTxParametersForm/index.tsx b/src/routes/safe/components/Transactions/helpers/EditTxParametersForm/index.tsx index 6d2f4c67db..2a615ac83f 100644 --- a/src/routes/safe/components/Transactions/helpers/EditTxParametersForm/index.tsx +++ b/src/routes/safe/components/Transactions/helpers/EditTxParametersForm/index.tsx @@ -23,7 +23,7 @@ import { import useSafeTxGas from 'src/routes/safe/components/Transactions/helpers/useSafeTxGas' import { isMaxFeeParam } from 'src/logic/safe/transactions/gas' import { extractSafeAddress } from 'src/routes/routes' -import useGetRecommendedNonce from 'src/logic/hooks/useGetRecommendedNonce' +import useRecommendedNonce from 'src/logic/hooks/useRecommendedNonce' import Paragraph from 'src/components/layout/Paragraph' const StyledDivider = styled(Divider)` @@ -103,7 +103,7 @@ export const EditTxParametersForm = ({ const { safeNonce, safeTxGas, ethNonce, ethGasLimit, ethGasPrice, ethMaxPrioFee } = txParameters const showSafeTxGas = useSafeTxGas() const safeAddress = extractSafeAddress() - const recommendedNonce = useGetRecommendedNonce(safeAddress) + const recommendedNonce = useRecommendedNonce(safeAddress) const onSubmit = (values: TxParameters) => { onClose(values)