From 18f97b80376dccef42a30c2091f4a9e703e3a137 Mon Sep 17 00:00:00 2001 From: Daniel <80175477+dan437@users.noreply.github.com> Date: Thu, 7 Nov 2024 14:03:10 +0100 Subject: [PATCH] cherry pick: fix: smart transactions in redesigned confirmations (#28353) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Cherry pick of: https://github.com/MetaMask/metamask-extension/pull/28273 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28273?quickstart=1) ## **Related issues** ## **Manual testing steps** 1. Install fresh extension. 2. Create transaction using redesigned confirmation. 3. Ensure smart transaction is performed. ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. ## **Description** [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28353?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. Co-authored-by: Matthew Walsh --- test/data/confirmations/helper.ts | 1 + .../components/confirm/info/info.tsx | 4 + .../useSmartTransactionFeatureFlags.test.ts | 119 ++++++++++++++++++ .../hooks/useSmartTransactionFeatureFlags.ts | 53 ++++++++ 4 files changed, 177 insertions(+) create mode 100644 ui/pages/confirmations/hooks/useSmartTransactionFeatureFlags.test.ts create mode 100644 ui/pages/confirmations/hooks/useSmartTransactionFeatureFlags.ts diff --git a/test/data/confirmations/helper.ts b/test/data/confirmations/helper.ts index 6669c043d0ea..b8bd8a634588 100644 --- a/test/data/confirmations/helper.ts +++ b/test/data/confirmations/helper.ts @@ -133,6 +133,7 @@ export const getMockConfirmState = (args: RootState = { metamask: {} }) => ({ ...args.metamask, preferences: { ...mockState.metamask.preferences, + ...(args.metamask?.preferences as Record), redesignedTransactionsEnabled: true, redesignedConfirmationsEnabled: true, isRedesignedConfirmationsDeveloperEnabled: true, diff --git a/ui/pages/confirmations/components/confirm/info/info.tsx b/ui/pages/confirmations/components/confirm/info/info.tsx index f283cbfc2e61..642d1d70c017 100644 --- a/ui/pages/confirmations/components/confirm/info/info.tsx +++ b/ui/pages/confirmations/components/confirm/info/info.tsx @@ -2,6 +2,7 @@ import { TransactionType } from '@metamask/transaction-controller'; import React, { useMemo } from 'react'; import { useConfirmContext } from '../../../context/confirm'; import { SignatureRequestType } from '../../../types/confirm'; +import { useSmartTransactionFeatureFlags } from '../../../hooks/useSmartTransactionFeatureFlags'; import ApproveInfo from './approve/approve'; import BaseTransactionInfo from './base-transaction-info/base-transaction-info'; import NativeTransferInfo from './native-transfer/native-transfer'; @@ -15,6 +16,9 @@ import TypedSignInfo from './typed-sign/typed-sign'; const Info = () => { const { currentConfirmation } = useConfirmContext(); + // TODO: Create TransactionInfo and SignatureInfo components. + useSmartTransactionFeatureFlags(); + const ConfirmationInfoComponentMap = useMemo( () => ({ [TransactionType.contractInteraction]: () => BaseTransactionInfo, diff --git a/ui/pages/confirmations/hooks/useSmartTransactionFeatureFlags.test.ts b/ui/pages/confirmations/hooks/useSmartTransactionFeatureFlags.test.ts new file mode 100644 index 000000000000..708a78b5b1eb --- /dev/null +++ b/ui/pages/confirmations/hooks/useSmartTransactionFeatureFlags.test.ts @@ -0,0 +1,119 @@ +import { act } from 'react-dom/test-utils'; +import { useDispatch } from 'react-redux'; +import { CHAIN_IDS, TransactionMeta } from '@metamask/transaction-controller'; +import { Hex } from '@metamask/utils'; +import { + fetchSmartTransactionsLiveness, + setSwapsFeatureFlags, +} from '../../../store/actions'; +import { renderHookWithConfirmContextProvider } from '../../../../test/lib/confirmations/render-helpers'; +import { genUnapprovedContractInteractionConfirmation } from '../../../../test/data/confirmations/contract-interaction'; +import { getMockConfirmStateForTransaction } from '../../../../test/data/confirmations/helper'; +import { mockNetworkState } from '../../../../test/stub/networks'; +import { fetchSwapsFeatureFlags } from '../../swaps/swaps.util'; +import { useSmartTransactionFeatureFlags } from './useSmartTransactionFeatureFlags'; + +jest.mock('react-redux', () => ({ + ...jest.requireActual('react-redux'), + useDispatch: jest.fn(), +})); + +jest.mock('../../../store/actions', () => ({ + ...jest.requireActual('../../../store/actions'), + setSwapsFeatureFlags: jest.fn(), + fetchSmartTransactionsLiveness: jest.fn(), +})); + +jest.mock('../../swaps/swaps.util', () => ({ + ...jest.requireActual('../../swaps/swaps.util'), + fetchSwapsFeatureFlags: jest.fn(), +})); + +async function runHook({ + smartTransactionsOptInStatus, + chainId, + confirmation, +}: { + smartTransactionsOptInStatus: boolean; + chainId: Hex; + confirmation?: Partial; +}) { + const transaction = + (confirmation as TransactionMeta) ?? + genUnapprovedContractInteractionConfirmation({ + chainId, + }); + + const state = getMockConfirmStateForTransaction(transaction, { + metamask: { + ...mockNetworkState({ chainId, id: 'Test' }), + selectedNetworkClientId: 'Test', + preferences: { + smartTransactionsOptInStatus, + }, + }, + }); + + renderHookWithConfirmContextProvider( + () => useSmartTransactionFeatureFlags(), + state, + ); + + await act(async () => { + // Intentionally empty + }); +} + +describe('useSmartTransactionFeatureFlags', () => { + const setSwapsFeatureFlagsMock = jest.mocked(setSwapsFeatureFlags); + const fetchSwapsFeatureFlagsMock = jest.mocked(fetchSwapsFeatureFlags); + const fetchSmartTransactionsLivenessMock = jest.mocked( + fetchSmartTransactionsLiveness, + ); + const useDispatchMock = jest.mocked(useDispatch); + + beforeEach(() => { + jest.resetAllMocks(); + useDispatchMock.mockReturnValue(jest.fn()); + fetchSwapsFeatureFlagsMock.mockResolvedValue({}); + fetchSmartTransactionsLivenessMock.mockReturnValue(() => Promise.resolve()); + }); + + it('updates feature flags', async () => { + await runHook({ + smartTransactionsOptInStatus: true, + chainId: CHAIN_IDS.MAINNET, + }); + + expect(setSwapsFeatureFlagsMock).toHaveBeenCalledTimes(1); + expect(setSwapsFeatureFlagsMock).toHaveBeenCalledWith({}); + }); + + it('does not update feature flags if smart transactions disabled', async () => { + await runHook({ + smartTransactionsOptInStatus: false, + chainId: CHAIN_IDS.MAINNET, + }); + + expect(setSwapsFeatureFlagsMock).not.toHaveBeenCalled(); + }); + + it('does not update feature flags if chain not supported', async () => { + await runHook({ + smartTransactionsOptInStatus: true, + chainId: CHAIN_IDS.ARBITRUM, + }); + + expect(setSwapsFeatureFlagsMock).not.toHaveBeenCalled(); + }); + + it('does not update feature flags if confirmation is not transaction', async () => { + await runHook({ + smartTransactionsOptInStatus: true, + chainId: CHAIN_IDS.MAINNET, + confirmation: {}, + }); + + expect(setSwapsFeatureFlagsMock).not.toHaveBeenCalled(); + }); +}); diff --git a/ui/pages/confirmations/hooks/useSmartTransactionFeatureFlags.ts b/ui/pages/confirmations/hooks/useSmartTransactionFeatureFlags.ts new file mode 100644 index 000000000000..099327eb211f --- /dev/null +++ b/ui/pages/confirmations/hooks/useSmartTransactionFeatureFlags.ts @@ -0,0 +1,53 @@ +import { useDispatch, useSelector } from 'react-redux'; +import { useEffect } from 'react'; +import { TransactionMeta } from '@metamask/transaction-controller'; +import log from 'loglevel'; +import { + getCurrentChainSupportsSmartTransactions, + getSmartTransactionsPreferenceEnabled, +} from '../../../../shared/modules/selectors'; +import { fetchSwapsFeatureFlags } from '../../swaps/swaps.util'; +import { + fetchSmartTransactionsLiveness, + setSwapsFeatureFlags, +} from '../../../store/actions'; +import { useConfirmContext } from '../context/confirm'; + +export function useSmartTransactionFeatureFlags() { + const dispatch = useDispatch(); + const { currentConfirmation } = useConfirmContext(); + const { id: transactionId, txParams } = currentConfirmation ?? {}; + const isTransaction = Boolean(txParams); + + const smartTransactionsPreferenceEnabled = useSelector( + getSmartTransactionsPreferenceEnabled, + ); + + const currentChainSupportsSmartTransactions = useSelector( + getCurrentChainSupportsSmartTransactions, + ); + + useEffect(() => { + if ( + !isTransaction || + !transactionId || + !smartTransactionsPreferenceEnabled || + !currentChainSupportsSmartTransactions + ) { + return; + } + + Promise.all([fetchSwapsFeatureFlags(), fetchSmartTransactionsLiveness()()]) + .then(([swapsFeatureFlags]) => { + dispatch(setSwapsFeatureFlags(swapsFeatureFlags)); + }) + .catch((error) => { + log.debug('Error updating smart transaction feature flags', error); + }); + }, [ + isTransaction, + transactionId, + smartTransactionsPreferenceEnabled, + currentChainSupportsSmartTransactions, + ]); +}