diff --git a/CHANGELOG.md b/CHANGELOG.md index b280c5649d70..d1e593908fb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [11.16.1] + ## [11.16.0] ### Added - Revamped the UI for Snap installation and update processes ([#23870](https://github.com/MetaMask/metamask-extension/pull/23870)) @@ -4748,7 +4750,8 @@ Update styles and spacing on the critical error page ([#20350](https://github.c - Added the ability to restore accounts from seed words. -[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v11.16.0...HEAD +[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v11.16.1...HEAD +[11.16.1]: https://github.com/MetaMask/metamask-extension/compare/v11.16.0...v11.16.1 [11.16.0]: https://github.com/MetaMask/metamask-extension/compare/v11.15.6...v11.16.0 [11.15.6]: https://github.com/MetaMask/metamask-extension/compare/v11.15.5...v11.15.6 [11.15.5]: https://github.com/MetaMask/metamask-extension/compare/v11.15.4...v11.15.5 diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 033c22b26a44..66df11979872 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -757,6 +757,9 @@ "closeExtension": { "message": "Close extension" }, + "closeWindowAnytime": { + "message": "You may close this window anytime." + }, "coingecko": { "message": "CoinGecko" }, @@ -1445,9 +1448,6 @@ "done": { "message": "Done" }, - "dontEnableEnhancedProtection": { - "message": "Don't enable enhanced protection" - }, "dontShowThisAgain": { "message": "Don't show this again" }, @@ -2526,6 +2526,9 @@ "message": "Make sure nobody is looking", "description": "Warning to users to be care while creating and saving their new Secret Recovery Phrase" }, + "manageInSettings": { + "message": "Manage in settings" + }, "max": { "message": "Max" }, @@ -4889,7 +4892,7 @@ "description": "This message is shown to a user if their swap fails. The $1 will be replaced by support.metamask.io" }, "stxOptInDescription": { - "message": "Turn on Smart Transactions for more reliable and secure transactions on ETH Mainnet. $1" + "message": "Turn on Smart Transactions for more reliable and secure transactions on Ethereum Mainnet. $1" }, "stxPendingPrivatelySubmittingSwap": { "message": "Privately submitting your Swap..." @@ -5881,7 +5884,7 @@ "message": "View on Opensea" }, "viewTransaction": { - "message": "View transaction" + "message": "View transaction" }, "viewinCustodianApp": { "message": "View in custodian app" diff --git a/app/scripts/lib/transaction/metrics.ts b/app/scripts/lib/transaction/metrics.ts index 667643bd8408..197271a02be1 100644 --- a/app/scripts/lib/transaction/metrics.ts +++ b/app/scripts/lib/transaction/metrics.ts @@ -76,12 +76,12 @@ export type TransactionMetricsRequest = { getEIP1559GasFeeEstimates(options?: FetchGasFeeEstimateOptions): Promise; getParticipateInMetrics: () => boolean; getSelectedAddress: () => string; - getTokenStandardAndDetails: () => { + getTokenStandardAndDetails: () => Promise<{ decimals?: string; balance?: string; symbol?: string; standard?: TokenStandard; - }; + }>; getTransaction: (transactionId: string) => TransactionMeta; provider: Provider; snapAndHardwareMessenger: SnapAndHardwareMessenger; diff --git a/package.json b/package.json index 0f9a2e4df9cc..8932a3b407a0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "metamask-crx", - "version": "11.16.0", + "version": "11.16.1", "private": true, "repository": { "type": "git", diff --git a/shared/modules/transaction.utils.ts b/shared/modules/transaction.utils.ts index 3f4089488f7c..e6a5f96a126d 100644 --- a/shared/modules/transaction.utils.ts +++ b/shared/modules/transaction.utils.ts @@ -198,12 +198,12 @@ export async function determineTransactionType( return { type: TransactionType.simpleSend, getCodeResponse: contractCode }; } -type GetTokenStandardAndDetails = (to: string | undefined) => { +type GetTokenStandardAndDetails = (to: string | undefined) => Promise<{ decimals?: string; balance?: string; symbol?: string; standard?: TokenStandard; -}; +}>; /** * Given a transaction meta object, determine the asset type that the * transaction is dealing with, as well as the standard for the token if it @@ -254,7 +254,7 @@ export async function determineTransactionAssetType( try { // We don't need a balance check, so the second parameter to // getTokenStandardAndDetails is omitted. - const details = getTokenStandardAndDetails(txMeta.txParams.to); + const details = await getTokenStandardAndDetails(txMeta.txParams.to); if (details.standard) { return { assetType: diff --git a/test/e2e/mmi/pageObjects/mmi-signup-page.ts b/test/e2e/mmi/pageObjects/mmi-signup-page.ts index 332e4843518a..55c59a153e59 100644 --- a/test/e2e/mmi/pageObjects/mmi-signup-page.ts +++ b/test/e2e/mmi/pageObjects/mmi-signup-page.ts @@ -13,7 +13,7 @@ export class MMISignUpPage { readonly agreeBtn: Locator; - readonly noThanksBtn: Locator; + readonly enableBtn: Locator; readonly passwordTxt: Locator; @@ -44,9 +44,7 @@ export class MMISignUpPage { 'button:has-text("Confirm Secret Recovery Phrase")', ); this.agreeBtn = page.locator('button:has-text("I agree")'); - this.noThanksBtn = page.locator( - 'button:has-text("Don\'t enable enhanced protection")', - ); + this.enableBtn = page.locator('button:has-text("Enable")'); // It shows in the Smart Transactions Opt-In Modal. this.passwordTxt = page.getByTestId('create-password-new'); this.passwordConfirmTxt = page.getByTestId('create-password-confirm'); this.agreeCheck = page.getByTestId('create-new-vault__terms-checkbox'); diff --git a/test/e2e/mock-e2e.js b/test/e2e/mock-e2e.js index 1a9ebad4c1f1..8a7f54b0e4fb 100644 --- a/test/e2e/mock-e2e.js +++ b/test/e2e/mock-e2e.js @@ -577,6 +577,17 @@ async function setupMocking(server, testSpecificMock, { chainId }) { await mockLensNameProvider(server); await mockTokenNameProvider(server, chainId); + // IPFS endpoint for NFT metadata + await server + .forGet( + 'https://bafybeidxfmwycgzcp4v2togflpqh2gnibuexjy4m4qqwxp7nh3jx5zlh4y.ipfs.dweb.link/1.json', + ) + .thenCallback(() => { + return { + statusCode: 200, + }; + }); + /** * Returns an array of alphanumerically sorted hostnames that were requested * during the current test suite. diff --git a/test/e2e/vault-decryption-chrome.spec.js b/test/e2e/vault-decryption-chrome.spec.js index d735de9a6a2a..8465c905856e 100644 --- a/test/e2e/vault-decryption-chrome.spec.js +++ b/test/e2e/vault-decryption-chrome.spec.js @@ -50,12 +50,13 @@ async function getExtensionStorageFilePath(driver) { */ async function closePopoverIfPresent(driver) { const popoverButtonSelector = '[data-testid="popover-close"]'; - const linkNoThanks = { - text: "Don't enable enhanced protection", + // It shows in the Smart Transactions Opt-In Modal. + const enableButtonSelector = { + text: 'Enable', tag: 'button', }; await driver.clickElementSafe(popoverButtonSelector); - await driver.clickElementSafe(linkNoThanks); + await driver.clickElementSafe(enableButtonSelector); } /** diff --git a/ui/components/app/smart-transactions/__snapshots__/smart-transactions-opt-in-modal.test.tsx.snap b/ui/components/app/smart-transactions/__snapshots__/smart-transactions-opt-in-modal.test.tsx.snap new file mode 100644 index 000000000000..cdd10e5d38fb --- /dev/null +++ b/ui/components/app/smart-transactions/__snapshots__/smart-transactions-opt-in-modal.test.tsx.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`SmartTransactionsOptInModal displays the correct text in the modal 1`] = `
`; diff --git a/ui/components/app/smart-transactions/smart-transactions-opt-in-modal.test.tsx b/ui/components/app/smart-transactions/smart-transactions-opt-in-modal.test.tsx new file mode 100644 index 000000000000..3224d85f52a9 --- /dev/null +++ b/ui/components/app/smart-transactions/smart-transactions-opt-in-modal.test.tsx @@ -0,0 +1,74 @@ +import React from 'react'; +import { fireEvent } from '@testing-library/react'; +import thunk from 'redux-thunk'; +import configureMockStore from 'redux-mock-store'; +import { useHistory } from 'react-router-dom'; + +import { + renderWithProvider, + createSwapsMockStore, +} from '../../../../test/jest'; +import { setSmartTransactionsOptInStatus } from '../../../store/actions'; +import { ADVANCED_ROUTE } from '../../../helpers/constants/routes'; +import SmartTransactionsOptInModal from './smart-transactions-opt-in-modal'; + +const middleware = [thunk]; + +jest.mock('../../../store/actions'); + +describe('SmartTransactionsOptInModal', () => { + it('displays the correct text in the modal', () => { + const store = configureMockStore(middleware)(createSwapsMockStore()); + const { getByText, container } = renderWithProvider( + , + store, + ); + expect(getByText('Enable')).toBeInTheDocument(); + expect(getByText('Manage in settings')).toBeInTheDocument(); + expect(container).toMatchSnapshot(); + }); + + it('calls setSmartTransactionsOptInStatus with false when the "Manage in settings" link is clicked and redirects to Advanced Settings', () => { + (setSmartTransactionsOptInStatus as jest.Mock).mockImplementationOnce(() => + jest.fn(), + ); + const historyPushMock = jest.fn(); + (useHistory as jest.Mock).mockImplementationOnce(() => ({ + push: historyPushMock, + })); + const store = configureMockStore(middleware)(createSwapsMockStore()); + const { getByText } = renderWithProvider( + , + store, + ); + const manageInSettingsLink = getByText('Manage in settings'); + fireEvent.click(manageInSettingsLink); + expect(setSmartTransactionsOptInStatus).toHaveBeenCalledWith(false); + expect(historyPushMock).toHaveBeenCalledWith( + `${ADVANCED_ROUTE}#smart-transactions`, + ); + }); + + it('calls setSmartTransactionsOptInStatus with true when the "Enable" button is clicked', () => { + (setSmartTransactionsOptInStatus as jest.Mock).mockImplementationOnce(() => + jest.fn(), + ); + const store = configureMockStore(middleware)(createSwapsMockStore()); + const { getByText } = renderWithProvider( + , + store, + ); + const enableButton = getByText('Enable'); + fireEvent.click(enableButton); + expect(setSmartTransactionsOptInStatus).toHaveBeenCalledWith(true); + }); +}); diff --git a/ui/components/app/smart-transactions/smart-transactions-opt-in-modal.tsx b/ui/components/app/smart-transactions/smart-transactions-opt-in-modal.tsx index 8d9c194aa496..982c3d463a71 100644 --- a/ui/components/app/smart-transactions/smart-transactions-opt-in-modal.tsx +++ b/ui/components/app/smart-transactions/smart-transactions-opt-in-modal.tsx @@ -1,4 +1,5 @@ import React, { useCallback, useEffect } from 'react'; +import { useHistory } from 'react-router-dom'; import { useDispatch } from 'react-redux'; import { useI18nContext } from '../../../hooks/useI18nContext'; @@ -30,6 +31,7 @@ import { } from '../../component-library'; import { setSmartTransactionsOptInStatus } from '../../../store/actions'; import { SMART_TRANSACTIONS_LEARN_MORE_URL } from '../../../../shared/constants/smartTransactions'; +import { ADVANCED_ROUTE } from '../../../helpers/constants/routes'; export type SmartTransactionsOptInModalProps = { isOpen: boolean; @@ -73,10 +75,10 @@ const EnableSmartTransactionsButton = ({ ); }; -const NoThanksLink = ({ - handleNoThanksLinkClick, +const ManageInSettingsLink = ({ + handleManageInSettingsLinkClick, }: { - handleNoThanksLinkClick: () => void; + handleManageInSettingsLinkClick: () => void; }) => { const t = useI18nContext(); return ( @@ -85,11 +87,11 @@ const NoThanksLink = ({ type="link" variant={ButtonVariant.Link} color={TextColor.textAlternative} - onClick={handleNoThanksLinkClick} + onClick={handleManageInSettingsLinkClick} width={BlockSize.Full} className="mm-smart-transactions-opt-in-modal__no-thanks-link" > - {t('dontEnableEnhancedProtection')} + {t('manageInSettings')} ); }; @@ -164,13 +166,16 @@ export default function SmartTransactionsOptInModal({ }: SmartTransactionsOptInModalProps) { const t = useI18nContext(); const dispatch = useDispatch(); + const history = useHistory(); const handleEnableButtonClick = useCallback(() => { dispatch(setSmartTransactionsOptInStatus(true)); }, [dispatch]); - const handleNoThanksLinkClick = useCallback(() => { + const handleManageInSettingsLinkClick = useCallback(() => { + // Set the Smart Transactions opt-in status to false, so the opt-in modal is not shown again. dispatch(setSmartTransactionsOptInStatus(false)); + history.push(`${ADVANCED_ROUTE}#smart-transactions`); }, [dispatch]); useEffect(() => { @@ -210,7 +215,9 @@ export default function SmartTransactionsOptInModal({ - + diff --git a/ui/pages/home/home.component.js b/ui/pages/home/home.component.js index e6bef940b10d..af995cedf585 100644 --- a/ui/pages/home/home.component.js +++ b/ui/pages/home/home.component.js @@ -840,10 +840,8 @@ export default class Home extends PureComponent { !process.env.IN_TEST && !newNetworkAddedConfigurationId; - // TODO(dbrans): temporary fix to disable the smart transactions opt-in modal - // in 11.15 to unblock the release. Change this line in 11.16. const showSmartTransactionsOptInModal = - false && canSeeModals && isSmartTransactionsOptInModalAvailable; + canSeeModals && isSmartTransactionsOptInModalAvailable; const showWhatsNew = canSeeModals && diff --git a/ui/pages/settings/advanced-tab/__snapshots__/advanced-tab.component.test.js.snap b/ui/pages/settings/advanced-tab/__snapshots__/advanced-tab.component.test.js.snap index 4e8b864812fc..a1b16719755e 100644 --- a/ui/pages/settings/advanced-tab/__snapshots__/advanced-tab.component.test.js.snap +++ b/ui/pages/settings/advanced-tab/__snapshots__/advanced-tab.component.test.js.snap @@ -80,7 +80,7 @@ exports[`AdvancedTab Component should match snapshot 1`] = ` > - Turn on Smart Transactions for more reliable and secure transactions on ETH Mainnet. + Turn on Smart Transactions for more reliable and secure transactions on Ethereum Mainnet. - View transaction + View transaction
@@ -146,6 +146,73 @@ exports[`SmartTransactionStatusPage renders the "cancelled" STX status 1`] = ` `; +exports[`SmartTransactionStatusPage renders the "cancelled" STX status for a dapp transaction 1`] = ` +
+
+
+
+
+
+ +
+

+ Your transaction was canceled +

+
+

+ Your transaction couldn't be completed, so it was canceled to save you from paying unnecessary gas fees. +

+
+
+ +
+
+
+
+ +
+
+`; + exports[`SmartTransactionStatusPage renders the "deadline_missed" STX status 1`] = `
@@ -213,6 +280,94 @@ exports[`SmartTransactionStatusPage renders the "deadline_missed" STX status 1`]
`; +exports[`SmartTransactionStatusPage renders the "pending" STX status for a dapp transaction 1`] = ` +
+
+
+
+
+
+ +
+

+ Submitting your transaction +

+
+
+
+
+
+
+

+ + + Estimated completion in < +

+ 0:45 +

+ + + +

+
+
+ +
+
+
+
+ +
+
+`; + exports[`SmartTransactionStatusPage renders the "reverted" STX status 1`] = `
@@ -316,7 +471,7 @@ exports[`SmartTransactionStatusPage renders the "success" STX status 1`] = ` class="mm-box mm-text mm-button-base mm-button-link mm-button-link--size-auto mm-text--body-md-medium mm-box--padding-0 mm-box--padding-right-0 mm-box--padding-left-0 mm-box--display-inline-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-primary-default mm-box--background-color-transparent" type="link" > - View transaction + View transaction
@@ -338,6 +493,64 @@ exports[`SmartTransactionStatusPage renders the "success" STX status 1`] = `
`; +exports[`SmartTransactionStatusPage renders the "success" STX status for a dapp transaction 1`] = ` +
+
+
+
+
+
+ +
+

+ Your transaction is complete +

+
+ +
+
+
+
+ +
+
+`; + exports[`SmartTransactionStatusPage renders the "unknown" STX status 1`] = `
diff --git a/ui/pages/smart-transactions/smart-transaction-status-page/smart-transaction-status-page.tsx b/ui/pages/smart-transactions/smart-transaction-status-page/smart-transaction-status-page.tsx index ef9b1efcb647..06afa7898249 100644 --- a/ui/pages/smart-transactions/smart-transaction-status-page/smart-transaction-status-page.tsx +++ b/ui/pages/smart-transactions/smart-transaction-status-page/smart-transaction-status-page.tsx @@ -240,10 +240,10 @@ const PortfolioSmartTransactionStatusUrl = ({ isSmartTransactionPending: boolean; onCloseExtension: () => void; }) => { + const t = useI18nContext(); if (!portfolioSmartTransactionStatusUrl) { return null; } - const handleViewTransactionLinkClick = useCallback(() => { const isWiderThanNotificationWidth = window.innerWidth > NOTIFICATION_WIDTH; if (!isSmartTransactionPending || isWiderThanNotificationWidth) { @@ -257,8 +257,6 @@ const PortfolioSmartTransactionStatusUrl = ({ onCloseExtension, portfolioSmartTransactionStatusUrl, ]); - const t = useI18nContext(); - return ( void; }) => { + const t = useI18nContext(); if (!isDapp || isSmartTransactionPending) { return null; } - const t = useI18nContext(); - return ( { + const t = useI18nContext(); + if (!isDapp || !isSmartTransactionPending) { + return null; + } + return ( + + {t('closeWindowAnytime')} + + ); +}; + const ViewActivityButton = ({ isDapp, onViewActivity, @@ -309,11 +328,10 @@ const ViewActivityButton = ({ isDapp: boolean; onViewActivity: () => void; }) => { + const t = useI18nContext(); if (isDapp) { return null; } - const t = useI18nContext(); - return ( + { creationTime: 1519211809934, }, }; - const { getByText, container } = renderWithProvider( + const { queryByText, container } = renderWithProvider( , store, ); - expect(getByText('Sorry for the wait')).toBeInTheDocument(); + expect( + queryByText('You may close this window anytime.'), + ).not.toBeInTheDocument(); + expect(queryByText('Sorry for the wait')).toBeInTheDocument(); + expect(queryByText('View activity')).toBeInTheDocument(); expect(container).toMatchSnapshot(); }); @@ -61,6 +65,8 @@ describe('SmartTransactionStatusPage', () => { store, ); expect(getByText('Your transaction is complete')).toBeInTheDocument(); + expect(getByText('View transaction')).toBeInTheDocument(); + expect(getByText('View activity')).toBeInTheDocument(); expect(container).toMatchSnapshot(); }); @@ -78,6 +84,8 @@ describe('SmartTransactionStatusPage', () => { store, ); expect(getByText('Your transaction failed')).toBeInTheDocument(); + expect(getByText('View transaction')).toBeInTheDocument(); + expect(getByText('View activity')).toBeInTheDocument(); expect( getByText( 'Sudden market changes can cause failures. If the problem continues, reach out to MetaMask customer support.', @@ -105,6 +113,8 @@ describe('SmartTransactionStatusPage', () => { `Your transaction couldn't be completed, so it was canceled to save you from paying unnecessary gas fees.`, ), ).toBeInTheDocument(); + expect(getByText('View transaction')).toBeInTheDocument(); + expect(getByText('View activity')).toBeInTheDocument(); expect(container).toMatchSnapshot(); }); @@ -123,6 +133,8 @@ describe('SmartTransactionStatusPage', () => { store, ); expect(getByText('Your transaction was canceled')).toBeInTheDocument(); + expect(getByText('View transaction')).toBeInTheDocument(); + expect(getByText('View activity')).toBeInTheDocument(); expect(container).toMatchSnapshot(); }); @@ -140,6 +152,74 @@ describe('SmartTransactionStatusPage', () => { store, ); expect(getByText('Your transaction failed')).toBeInTheDocument(); + expect(getByText('View transaction')).toBeInTheDocument(); + expect(getByText('View activity')).toBeInTheDocument(); + expect(container).toMatchSnapshot(); + }); + + it('renders the "pending" STX status for a dapp transaction', () => { + const mockStore = createSwapsMockStore(); + const latestSmartTransaction = + mockStore.metamask.smartTransactionsState.smartTransactions[ + CHAIN_IDS.MAINNET + ][1]; + latestSmartTransaction.status = SmartTransactionStatuses.PENDING; + requestState.smartTransaction = latestSmartTransaction; + requestState.isDapp = true; + const store = configureMockStore(middleware)(mockStore); + const { queryByText, container } = renderWithProvider( + , + store, + ); + expect( + queryByText('You may close this window anytime.'), + ).toBeInTheDocument(); + expect(queryByText('View transaction')).toBeInTheDocument(); + expect(queryByText('View activity')).not.toBeInTheDocument(); + expect(container).toMatchSnapshot(); + }); + + it('renders the "success" STX status for a dapp transaction', () => { + const mockStore = createSwapsMockStore(); + const latestSmartTransaction = + mockStore.metamask.smartTransactionsState.smartTransactions[ + CHAIN_IDS.MAINNET + ][1]; + latestSmartTransaction.status = SmartTransactionStatuses.SUCCESS; + requestState.smartTransaction = latestSmartTransaction; + requestState.isDapp = true; + const store = configureMockStore(middleware)(mockStore); + const { queryByText, container } = renderWithProvider( + , + store, + ); + expect( + queryByText('You may close this window anytime.'), + ).not.toBeInTheDocument(); + expect(queryByText('View transaction')).toBeInTheDocument(); + expect(queryByText('Close extension')).toBeInTheDocument(); + expect(container).toMatchSnapshot(); + }); + + it('renders the "cancelled" STX status for a dapp transaction', () => { + const mockStore = createSwapsMockStore(); + const latestSmartTransaction = + mockStore.metamask.smartTransactionsState.smartTransactions[ + CHAIN_IDS.MAINNET + ][1]; + latestSmartTransaction.status = SmartTransactionStatuses.CANCELLED; + requestState.smartTransaction = latestSmartTransaction; + requestState.isDapp = true; + const store = configureMockStore(middleware)(mockStore); + const { queryByText, container } = renderWithProvider( + , + store, + ); + expect( + queryByText('You may close this window anytime.'), + ).not.toBeInTheDocument(); + expect(queryByText('View transaction')).toBeInTheDocument(); + expect(queryByText('Close extension')).toBeInTheDocument(); expect(container).toMatchSnapshot(); }); });