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, + ]); +}