diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index c786d0c8f65a..40976ba0955f 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -4787,6 +4787,14 @@ "message": "View on $1", "description": "Additional content in a notification that appears when a transaction is confirmed and has a block explorer URL." }, + "notificationTransactionWithoutNonceFailedMessage": { + "message": "Transaction failed! $1", + "description": "Content of the browser notification that appears when a EIP-7702 transaction fails" + }, + "notificationTransactionWithoutNonceSuccessMessage": { + "message": "Transaction confirmed!", + "description": "Content of the browser notification that appears when a nonce-less transaction is confirmed" + }, "notifications": { "message": "Notifications" }, diff --git a/app/_locales/en_GB/messages.json b/app/_locales/en_GB/messages.json index c786d0c8f65a..40976ba0955f 100644 --- a/app/_locales/en_GB/messages.json +++ b/app/_locales/en_GB/messages.json @@ -4787,6 +4787,14 @@ "message": "View on $1", "description": "Additional content in a notification that appears when a transaction is confirmed and has a block explorer URL." }, + "notificationTransactionWithoutNonceFailedMessage": { + "message": "Transaction failed! $1", + "description": "Content of the browser notification that appears when a EIP-7702 transaction fails" + }, + "notificationTransactionWithoutNonceSuccessMessage": { + "message": "Transaction confirmed!", + "description": "Content of the browser notification that appears when a nonce-less transaction is confirmed" + }, "notifications": { "message": "Notifications" }, diff --git a/app/scripts/platforms/extension.js b/app/scripts/platforms/extension.js index a77aa108b6bd..1fe59d8a31d1 100644 --- a/app/scripts/platforms/extension.js +++ b/app/scripts/platforms/extension.js @@ -201,7 +201,9 @@ export default class ExtensionPlatform { ); const title = t('notificationTransactionSuccessTitle'); - let message = t('notificationTransactionSuccessMessage', nonce); + let message = Number.isNaN(nonce) + ? t('notificationTransactionWithoutNonceSuccessMessage') + : t('notificationTransactionSuccessMessage', nonce); if (url.length) { message += ` ${t('notificationTransactionSuccessView', view)}`; @@ -213,11 +215,10 @@ export default class ExtensionPlatform { async _showFailedTransaction(txMeta, errorMessage) { const nonce = parseInt(txMeta.txParams.nonce, 16); const title = t('notificationTransactionFailedTitle'); - const message = t( - 'notificationTransactionFailedMessage', - nonce, - errorMessage || txMeta.error.message, - ); + const errorMessageText = errorMessage || txMeta.error.message; + const message = Number.isNaN(nonce) + ? t('notificationTransactionWithoutNonceFailedMessage', errorMessageText) + : t('notificationTransactionFailedMessage', nonce, errorMessageText); await this._showNotification(title, message); } diff --git a/app/scripts/platforms/extension.test.js b/app/scripts/platforms/extension.test.js index 1f90ca9d179b..92ed8212f8fd 100644 --- a/app/scripts/platforms/extension.test.js +++ b/app/scripts/platforms/extension.test.js @@ -1,5 +1,6 @@ import browser from 'webextension-polyfill'; import { TransactionStatus } from '@metamask/transaction-controller'; +import { getBlockExplorerLink } from '@metamask/etherscan-link'; import { t } from '../../../shared/lib/translate'; import ExtensionPlatform from './extension'; @@ -14,10 +15,19 @@ jest.mock('webextension-polyfill', () => { }, notifications: { create: jest.fn(), + onClicked: { + hasListener: jest.fn(), + }, }, }; }); +jest.mock('@metamask/etherscan-link', () => { + return { + getBlockExplorerLink: jest.fn(), + }; +}); + describe('extension platform', () => { const metamaskVersion = process.env.METAMASK_VERSION; beforeEach(() => { @@ -94,6 +104,54 @@ describe('extension platform', () => { }); }); + describe('_showConfirmedTransaction', () => { + it('should show confirmed transaction with nonce', async () => { + const txMeta = { + txParams: { nonce: '0x1' }, + error: { message: 'Error message' }, + }; + browser.notifications.onClicked.hasListener.mockReturnValue(true); + getBlockExplorerLink.mockReturnValue('http://explorer-mock'); + + const extensionPlatform = new ExtensionPlatform(); + const showNotificationSpy = jest.spyOn( + extensionPlatform, + '_showNotification', + ); + + await extensionPlatform._showConfirmedTransaction(txMeta); + + expect(showNotificationSpy).toHaveBeenCalledWith( + 'Confirmed transaction', + 'Transaction 1 confirmed! View on Explorer Mock', + 'http://explorer-mock', + ); + }); + + it('should show confirmed transaction without nonce', async () => { + const txMeta = { + txParams: { nonce: undefined }, + error: { message: 'Error message' }, + }; + browser.notifications.onClicked.hasListener.mockReturnValue(true); + getBlockExplorerLink.mockReturnValue('http://explorer-mock'); + + const extensionPlatform = new ExtensionPlatform(); + const showNotificationSpy = jest.spyOn( + extensionPlatform, + '_showNotification', + ); + + await extensionPlatform._showConfirmedTransaction(txMeta); + + expect(showNotificationSpy).toHaveBeenCalledWith( + 'Confirmed transaction', + 'Transaction confirmed! View on Explorer Mock', + 'http://explorer-mock', + ); + }); + }); + describe('_showFailedTransaction', () => { it('should show failed transaction with nonce', async () => { const txMeta = { @@ -114,7 +172,26 @@ describe('extension platform', () => { ); }); - it('should show failed transaction with errorMessage', async () => { + it('should show failed transaction without nonce', async () => { + const txMeta = { + txParams: { nonce: undefined }, + error: { message: 'Error message' }, + }; + const extensionPlatform = new ExtensionPlatform(); + const showNotificationSpy = jest.spyOn( + extensionPlatform, + '_showNotification', + ); + + await extensionPlatform._showFailedTransaction(txMeta); + + expect(showNotificationSpy).toHaveBeenCalledWith( + 'Failed transaction', + `Transaction failed! ${txMeta.error.message}`, + ); + }); + + it('should show failed transaction with nonce and with errorMessage', async () => { const errorMessage = 'Test error message'; const txMeta = { txParams: { nonce: '0x1' }, @@ -133,6 +210,26 @@ describe('extension platform', () => { `Transaction 1 failed! ${errorMessage}`, ); }); + + it('should show failed transaction without nonce and with errorMessage', async () => { + const errorMessage = 'Test error message'; + const txMeta = { + txParams: { nonce: undefined }, + error: { message: 'Error message' }, + }; + const extensionPlatform = new ExtensionPlatform(); + const showNotificationSpy = jest.spyOn( + extensionPlatform, + '_showNotification', + ); + + await extensionPlatform._showFailedTransaction(txMeta, errorMessage); + + expect(showNotificationSpy).toHaveBeenCalledWith( + 'Failed transaction', + `Transaction failed! ${errorMessage}`, + ); + }); }); describe('showTransactionNotification', () => { diff --git a/ui/components/app/transaction-breakdown/transaction-breakdown.component.js b/ui/components/app/transaction-breakdown/transaction-breakdown.component.js index c177c8ae7dd6..f1231efd43b5 100644 --- a/ui/components/app/transaction-breakdown/transaction-breakdown.component.js +++ b/ui/components/app/transaction-breakdown/transaction-breakdown.component.js @@ -72,14 +72,14 @@ export default class TransactionBreakdown extends PureComponent { return (
{t('transaction')}
- - {typeof nonce === 'undefined' ? null : ( + {nonce !== undefined && ( + - )} - + + )} {sourceAmountFormatted && ( { }); }); + describe('with a typical transaction without nonce', () => { + it('renders properly', () => { + const { getAllByTestId } = renderWithProvider( + , + store, + ); + + expect( + getActualDataFrom(getAllByTestId('transaction-breakdown-row')), + ).toStrictEqual([ + // No nonce visible + ['Amount', '-0.01 ETH'], + ['Gas limit (units)', '46890'], + ['Gas price', '2.467043803'], + ['Total', '0.01011568ETH'], + ]); + }); + }); + describe('with a typical EIP-1559 transaction', () => { it('renders properly', () => { const { getAllByTestId } = renderWithProvider(