diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index dadfc1b5990e..992302983baf 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -2055,8 +2055,7 @@ export default class MetamaskController extends EventEmitter { decodingApiUrl: process.env.DECODING_API_URL, isDecodeSignatureRequestEnabled: () => this.preferencesController.state.useExternalServices === true && - this.preferencesController.state.useTransactionSimulations && - process.env.ENABLE_SIGNATURE_DECODING === true, + this.preferencesController.state.useTransactionSimulations, }); this.signatureController.hub.on( diff --git a/builds.yml b/builds.yml index c27d79a9a034..3963aeda93e5 100644 --- a/builds.yml +++ b/builds.yml @@ -235,10 +235,8 @@ env: # Used to enable confirmation redesigned pages - ENABLE_CONFIRMATION_REDESIGN: '' - # Used to enable signature decoding - - ENABLE_SIGNATURE_DECODING: '' # URL of the decoding API used to provide additional data from signature requests - - DECODING_API_URL: null + - DECODING_API_URL: 'https://qtgdj2huxh.execute-api.us-east-2.amazonaws.com/uat/v1' # Determines if feature flagged Settings Page - Developer Options should be used - ENABLE_SETTINGS_PAGE_DEV_OPTIONS: false # Used for debugging changes to the phishing warning page. diff --git a/privacy-snapshot.json b/privacy-snapshot.json index 36249b132bca..5620903a5c73 100644 --- a/privacy-snapshot.json +++ b/privacy-snapshot.json @@ -48,6 +48,7 @@ "price.api.cx.metamask.io", "proxy.api.cx.metamask.io", "proxy.dev-api.cx.metamask.io", + "qtgdj2huxh.execute-api.us-east-2.amazonaws.com", "raw.githubusercontent.com", "registry.npmjs.org", "responsive-rpc.test", diff --git a/test/e2e/tests/confirmations/helpers.ts b/test/e2e/tests/confirmations/helpers.ts index d1c665241f5a..3ea2a2927729 100644 --- a/test/e2e/tests/confirmations/helpers.ts +++ b/test/e2e/tests/confirmations/helpers.ts @@ -10,6 +10,9 @@ import { SMART_CONTRACTS } from '../../seeder/smart-contracts'; import { Driver } from '../../webdriver/driver'; import Confirmation from '../../page-objects/pages/confirmations/redesign/confirmation'; +export const DECODING_E2E_API_URL = + 'https://qtgdj2huxh.execute-api.us-east-2.amazonaws.com/uat/v1'; + export async function scrollAndConfirmAndAssertConfirm(driver: Driver) { const confirmation = new Confirmation(driver); await confirmation.clickScrollToBottomButton(); @@ -61,6 +64,33 @@ async function createMockSegmentEvent(mockServer: Mockttp, eventName: string) { })); } +async function createMockSignatureDecodingEvent(mockServer: Mockttp) { + return await mockServer + .forPost(`${DECODING_E2E_API_URL}/signature`) + .thenCallback(() => ({ + statusCode: 200, + json: { + stateChanges: [ + { + assetType: 'NATIVE', + changeType: 'RECEIVE', + address: '', + amount: '900000000000000000', + contractAddress: '', + }, + { + assetType: 'ERC721', + changeType: 'LISTING', + address: '', + amount: '', + contractAddress: '0xafd4896984CA60d2feF66136e57f958dCe9482d5', + tokenID: '2101', + }, + ], + }, + })); +} + export async function mockSignatureApproved( mockServer: Mockttp, withAnonEvents = false, @@ -77,6 +107,7 @@ export async function mockSignatureApproved( await createMockSegmentEvent(mockServer, 'Account Details Opened'), ...anonEvents, await createMockSegmentEvent(mockServer, 'Signature Approved'), + await createMockSignatureDecodingEvent(mockServer), ]; } @@ -94,6 +125,11 @@ export async function mockSignatureRejected( return [ await createMockSegmentEvent(mockServer, 'Signature Requested'), await createMockSegmentEvent(mockServer, 'Signature Rejected'), + await createMockSignatureDecodingEvent(mockServer), ...anonEvents, ]; } + +export async function mockPermitDecoding(mockServer: Mockttp) { + return [await createMockSignatureDecodingEvent(mockServer)]; +} diff --git a/test/e2e/tests/confirmations/signatures/nft-permit.spec.ts b/test/e2e/tests/confirmations/signatures/nft-permit.spec.ts index 8f67fb7607ce..09c1e414eeee 100644 --- a/test/e2e/tests/confirmations/signatures/nft-permit.spec.ts +++ b/test/e2e/tests/confirmations/signatures/nft-permit.spec.ts @@ -72,6 +72,8 @@ describe('Confirmation Signature - NFT Permit @no-mmi', function (this: Suite) { signatureType: 'eth_signTypedData_v4', primaryType: 'Permit', uiCustomizations: ['redesigned_confirmation', 'permit'], + decodingChangeTypes: ['RECEIVE', 'LISTING'], + decodingResponse: 'CHANGE', }); await assertVerifiedResults(driver, publicAddress); @@ -113,6 +115,8 @@ describe('Confirmation Signature - NFT Permit @no-mmi', function (this: Suite) { primaryType: 'Permit', uiCustomizations: ['redesigned_confirmation', 'permit'], location: 'confirmation', + decodingChangeTypes: ['RECEIVE', 'LISTING'], + decodingResponse: 'CHANGE', }); }, mockSignatureRejected, diff --git a/test/e2e/tests/confirmations/signatures/permit.spec.ts b/test/e2e/tests/confirmations/signatures/permit.spec.ts index 0385f0a23af5..f4c55582cb0a 100644 --- a/test/e2e/tests/confirmations/signatures/permit.spec.ts +++ b/test/e2e/tests/confirmations/signatures/permit.spec.ts @@ -1,3 +1,4 @@ +import { strict as assert } from 'assert'; import { TransactionEnvelopeType } from '@metamask/transaction-controller'; import { Suite } from 'mocha'; import { MockedEndpoint } from 'mockttp'; @@ -5,6 +6,7 @@ import { openDapp, unlockWallet, WINDOW_TITLES } from '../../../helpers'; import { Ganache } from '../../../seeder/ganache'; import { Driver } from '../../../webdriver/driver'; import { + mockPermitDecoding, mockSignatureApproved, mockSignatureRejected, scrollAndConfirmAndAssertConfirm, @@ -67,6 +69,8 @@ describe('Confirmation Signature - Permit @no-mmi', function (this: Suite) { signatureType: 'eth_signTypedData_v4', primaryType: 'Permit', uiCustomizations: ['redesigned_confirmation', 'permit'], + decodingChangeTypes: ['RECEIVE', 'LISTING'], + decodingResponse: 'CHANGE', }); await assertVerifiedResults(driver, publicAddress); @@ -103,11 +107,39 @@ describe('Confirmation Signature - Permit @no-mmi', function (this: Suite) { primaryType: 'Permit', uiCustomizations: ['redesigned_confirmation', 'permit'], location: 'confirmation', + decodingChangeTypes: ['RECEIVE', 'LISTING'], + decodingResponse: 'CHANGE', }); }, mockSignatureRejected, ); }); + + it('display decoding information if available', async function () { + await withTransactionEnvelopeTypeFixtures( + this.test?.fullTitle(), + TransactionEnvelopeType.legacy, + async ({ driver }: TestSuiteArguments) => { + await initializePages(driver); + await openDappAndTriggerSignature(driver, SignatureType.Permit); + + const simulationSection = driver.findElement({ + text: 'Estimated changes', + }); + const receiveChange = driver.findElement({ text: 'You receive' }); + const listChange = driver.findElement({ text: 'You list' }); + const listChangeValue = driver.findElement({ text: '#2101' }); + + assert.ok(await simulationSection, 'Estimated changes'); + assert.ok(await receiveChange, 'You receive'); + assert.ok(await listChange, 'You list'); + assert.ok(await listChangeValue, '#2101'); + + await driver.delay(10000); + }, + mockPermitDecoding, + ); + }); }); async function assertInfoValues(driver: Driver) { diff --git a/test/e2e/tests/confirmations/signatures/signature-helpers.ts b/test/e2e/tests/confirmations/signatures/signature-helpers.ts index fd07c35fab19..050b2fc19eb7 100644 --- a/test/e2e/tests/confirmations/signatures/signature-helpers.ts +++ b/test/e2e/tests/confirmations/signatures/signature-helpers.ts @@ -253,6 +253,8 @@ function assertEventPropertiesMatch( const actualProperties = { ...event.properties }; const expectedProps = { ...expectedProperties }; + compareDecodingAPIResponse(actualProperties, expectedProps, eventName); + compareSecurityAlertResponse(actualProperties, expectedProps, eventName); assert(event, `${eventName} event not found`); @@ -287,6 +289,33 @@ function compareSecurityAlertResponse( } } +function compareDecodingAPIResponse( + actualProperties: Record, + expectedProperties: Record, + eventName: string, +) { + if ( + eventName === 'Signature Rejected' || + eventName === 'Signature Approved' + ) { + assert.deepStrictEqual( + actualProperties.decoding_change_types, + expectedProperties.decoding_change_types, + `${eventName} event properties do not match: decoding_change_types is ${actualProperties.decoding_change_types}`, + ); + assert.equal( + actualProperties.decoding_response, + expectedProperties.decoding_response, + `${eventName} event properties do not match: decoding_response is ${actualProperties.decoding_response}`, + ); + } + // Remove the property from both objects to avoid comparison + delete expectedProperties.decoding_change_types; + delete expectedProperties.decoding_response; + delete actualProperties.decoding_change_types; + delete actualProperties.decoding_response; +} + export async function clickHeaderInfoBtn(driver: Driver) { const confirmation = new Confirmation(driver); await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); diff --git a/test/integration/confirmations/signatures/permit-seaport.test.tsx b/test/integration/confirmations/signatures/permit-seaport.test.tsx index be4f6b6064d2..7829a2714b57 100644 --- a/test/integration/confirmations/signatures/permit-seaport.test.tsx +++ b/test/integration/confirmations/signatures/permit-seaport.test.tsx @@ -1,4 +1,4 @@ -import { act, screen } from '@testing-library/react'; +import { act, fireEvent, screen } from '@testing-library/react'; import nock from 'nock'; import mockMetaMaskState from '../../data/integration-init-state.json'; import { integrationTestRender } from '../../../lib/render-helpers'; @@ -92,6 +92,8 @@ describe('Permit Seaport Tests', () => { it('renders message details section', async () => { await renderSeaportSignature(); + fireEvent.click(screen.getByTestId('sectionCollapseButton')); + const messageDetailsSection = await screen.findByTestId( 'confirmation_message-section', ); @@ -112,6 +114,8 @@ describe('Permit Seaport Tests', () => { it('renders offer and consideration details', async () => { await renderSeaportSignature(); + fireEvent.click(screen.getByTestId('sectionCollapseButton')); + const offers = await screen.findByTestId('confirmation_data-offer-index-2'); const offerDetails0 = offers.querySelector( '[data-testid="confirmation_data-0-index-0"]', diff --git a/ui/pages/confirmations/components/confirm/info/__snapshots__/info.test.tsx.snap b/ui/pages/confirmations/components/confirm/info/__snapshots__/info.test.tsx.snap index 5bd747959256..bdcc3e7db452 100644 --- a/ui/pages/confirmations/components/confirm/info/__snapshots__/info.test.tsx.snap +++ b/ui/pages/confirmations/components/confirm/info/__snapshots__/info.test.tsx.snap @@ -1386,30 +1386,19 @@ exports[`Info renders info section for typed sign request 1`] = ` data-testid="confirmation_message-section" >
-
diff --git a/ui/pages/confirmations/components/confirm/info/typed-sign/__snapshots__/typed-sign.test.tsx.snap b/ui/pages/confirmations/components/confirm/info/typed-sign/__snapshots__/typed-sign.test.tsx.snap index edb33ff6fea3..20998032a049 100644 --- a/ui/pages/confirmations/components/confirm/info/typed-sign/__snapshots__/typed-sign.test.tsx.snap +++ b/ui/pages/confirmations/components/confirm/info/typed-sign/__snapshots__/typed-sign.test.tsx.snap @@ -1102,30 +1102,19 @@ exports[`TypedSignInfo renders origin for typed sign data request 1`] = ` data-testid="confirmation_message-section" >
-
@@ -2047,30 +2036,19 @@ exports[`TypedSignInfo should render message for typed sign v3 request 1`] = ` data-testid="confirmation_message-section" >
-
@@ -2619,30 +2597,19 @@ exports[`TypedSignInfo should render message for typed sign v4 request 1`] = ` data-testid="confirmation_message-section" >
-
diff --git a/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign-v4-simulation/decoded-simulation/decoded-simulation.tsx b/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign-v4-simulation/decoded-simulation/decoded-simulation.tsx index 53295852c566..55197e689600 100644 --- a/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign-v4-simulation/decoded-simulation/decoded-simulation.tsx +++ b/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign-v4-simulation/decoded-simulation/decoded-simulation.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import { DecodingDataChangeType, DecodingDataStateChange, @@ -103,14 +103,18 @@ const DecodedSimulation: React.FC = () => { const chainId = currentConfirmation.chainId as Hex; const { decodingLoading, decodingData } = currentConfirmation; - const stateChangeFragment = (decodingData?.stateChanges ?? []).map( - (change: DecodingDataStateChange) => ( - - ), + const stateChangeFragment = useMemo( + () => + (decodingData?.stateChanges ?? []).map( + (change: DecodingDataStateChange) => ( + + ), + ), + [decodingData?.stateChanges], ); return ( diff --git a/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign-v4-simulation/typed-sign-v4-simulation.test.tsx b/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign-v4-simulation/typed-sign-v4-simulation.test.tsx index 36d20e30fc66..756d54f57a9b 100644 --- a/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign-v4-simulation/typed-sign-v4-simulation.test.tsx +++ b/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign-v4-simulation/typed-sign-v4-simulation.test.tsx @@ -21,6 +21,7 @@ jest.mock('../../../../../../../store/actions', () => { getTokenStandardAndDetails: jest .fn() .mockResolvedValue({ decimals: 2, standard: 'ERC20' }), + updateEventFragment: jest.fn(), }; }); @@ -45,11 +46,16 @@ describe('PermitSimulation', () => { }); it('should render default simulation if decoding api does not return result', async () => { - const state = getMockTypedSignConfirmStateForRequest({ - ...permitSignatureMsg, - decodingLoading: false, - decodingData: undefined, - }); + const state = getMockTypedSignConfirmStateForRequest( + { + ...permitSignatureMsg, + decodingLoading: false, + decodingData: undefined, + }, + { + metamask: { useTransactionSimulations: true }, + }, + ); const mockStore = configureMockStore([])(state); await act(async () => { @@ -69,17 +75,22 @@ describe('PermitSimulation', () => { }); it('should render default simulation if decoding api returns error', async () => { - const state = getMockTypedSignConfirmStateForRequest({ - ...permitSignatureMsg, - decodingLoading: false, - decodingData: { - stateChanges: null, - error: { - message: 'some error', - type: 'SOME_ERROR', + const state = getMockTypedSignConfirmStateForRequest( + { + ...permitSignatureMsg, + decodingLoading: false, + decodingData: { + stateChanges: null, + error: { + message: 'some error', + type: 'SOME_ERROR', + }, }, }, - }); + { + metamask: { useTransactionSimulations: true }, + }, + ); const mockStore = configureMockStore([])(state); await act(async () => { @@ -113,7 +124,6 @@ describe('PermitSimulation', () => { await waitFor(() => { expect(queryByTestId('30')).not.toBeInTheDocument(); - expect(queryByTestId('Estimated changes')).toBeInTheDocument(); expect( queryByTestId( "You're giving the spender permission to spend this many tokens from your account.", @@ -124,11 +134,14 @@ describe('PermitSimulation', () => { }); it('should render decoding simulation for permits', async () => { - const state = getMockTypedSignConfirmStateForRequest({ - ...permitSignatureMsg, - decodingLoading: false, - decodingData, - }); + const state = getMockTypedSignConfirmStateForRequest( + { + ...permitSignatureMsg, + decodingLoading: false, + decodingData, + }, + { metamask: { useTransactionSimulations: true } }, + ); const mockStore = configureMockStore([])(state); await act(async () => { @@ -139,12 +152,13 @@ describe('PermitSimulation', () => { expect(await findByText('Estimated changes')).toBeInTheDocument(); expect(await findByText('Spending cap')).toBeInTheDocument(); - expect(await findByText('1,461,501,637,3...')).toBeInTheDocument(); }); }); - it.only('should render decoding simulation for seaport request', async () => { - const state = getMockTypedSignConfirmStateForRequest(seaportSignatureMsg); + it('should render decoding simulation for seaport request', async () => { + const state = getMockTypedSignConfirmStateForRequest(seaportSignatureMsg, { + metamask: { useTransactionSimulations: true }, + }); const mockStore = configureMockStore([])(state); await act(async () => { diff --git a/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign-v4-simulation/typed-sign-v4-simulation.tsx b/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign-v4-simulation/typed-sign-v4-simulation.tsx index ebea09e18f15..ce2c8b54c04a 100644 --- a/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign-v4-simulation/typed-sign-v4-simulation.tsx +++ b/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign-v4-simulation/typed-sign-v4-simulation.tsx @@ -1,50 +1,20 @@ import React from 'react'; -import { PRIMARY_TYPES_PERMIT } from '../../../../../../../../shared/constants/signatures'; -import { parseTypedDataMessage } from '../../../../../../../../shared/modules/transaction.utils'; import { SignatureRequestType } from '../../../../../types/confirm'; import { isPermitSignatureRequest } from '../../../../../utils'; import { useConfirmContext } from '../../../../../context/confirm'; import { useDecodedSignatureMetrics } from '../../../../../hooks/useDecodedSignatureMetrics'; +import { useTypesSignSimulationEnabledInfo } from '../../../../../hooks/useTypesSignSimulationEnabledInfo'; import { DecodedSimulation } from './decoded-simulation'; import { PermitSimulation } from './permit-simulation'; -const NON_PERMIT_SUPPORTED_TYPES_SIGNS = [ - { - domainName: 'Seaport', - primaryTypeList: ['BulkOrder'], - versionList: ['1.4', '1.5', '1.6'], - }, - { - domainName: 'Seaport', - primaryTypeList: ['OrderComponents'], - }, -]; - -const isSupportedByDecodingAPI = (signatureRequest: SignatureRequestType) => { - const { - domain: { name, version }, - primaryType, - } = parseTypedDataMessage( - (signatureRequest as SignatureRequestType).msgParams?.data as string, - ); - const isPermit = PRIMARY_TYPES_PERMIT.includes(primaryType); - const nonPermitSupportedTypes = NON_PERMIT_SUPPORTED_TYPES_SIGNS.some( - ({ domainName, primaryTypeList, versionList }) => - name === domainName && - primaryTypeList.includes(primaryType) && - (!versionList || versionList.includes(version)), - ); - return isPermit || nonPermitSupportedTypes; -}; - const TypedSignV4Simulation: React.FC = () => { const { currentConfirmation } = useConfirmContext(); const isPermit = isPermitSignatureRequest(currentConfirmation); - const supportedByDecodingAPI = isSupportedByDecodingAPI(currentConfirmation); - useDecodedSignatureMetrics(); + const isSimulationSupported = useTypesSignSimulationEnabledInfo(); + useDecodedSignatureMetrics(isSimulationSupported === true); - if (!supportedByDecodingAPI) { + if (!isSimulationSupported) { return null; } diff --git a/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign.test.tsx b/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign.test.tsx index 68f3c011f338..56421561ccd2 100644 --- a/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign.test.tsx +++ b/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign.test.tsx @@ -14,6 +14,7 @@ import { permitSignatureMsg, permitSignatureMsgWithNoDeadline, unapprovedTypedSignMsgV3, + unapprovedTypedSignMsgV4, } from '../../../../../../../test/data/confirmations/typed_sign'; import { renderWithConfirmContextProvider } from '../../../../../../../test/lib/confirmations/render-helpers'; import * as snapUtils from '../../../../../../helpers/utils/snaps'; @@ -147,6 +148,7 @@ describe('TypedSignInfo', () => { it('displays "requestFromInfoSnap" tooltip when origin is a snap', async () => { const mockState = getMockTypedSignConfirmStateForRequest({ + ...unapprovedTypedSignMsgV4, id: '123', type: TransactionType.signTypedData, chainId: '0x5', @@ -170,6 +172,7 @@ describe('TypedSignInfo', () => { it('displays "requestFromInfo" tooltip when origin is not a snap', async () => { const mockState = getMockTypedSignConfirmStateForRequest({ + ...unapprovedTypedSignMsgV4, id: '123', type: TransactionType.signTypedData, chainId: '0x5', diff --git a/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign.tsx b/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign.tsx index 0937e0dc1117..d21ed2b1fca9 100644 --- a/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign.tsx +++ b/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign.tsx @@ -1,9 +1,7 @@ import React from 'react'; -import { useSelector } from 'react-redux'; import { isValidAddress } from 'ethereumjs-util'; import { ConfirmInfoAlertRow } from '../../../../../../components/app/confirm/info/row/alert-row/alert-row'; -import { MESSAGE_TYPE } from '../../../../../../../shared/constants/app'; import { parseTypedDataMessage } from '../../../../../../../shared/modules/transaction.utils'; import { RowAlertKey } from '../../../../../../components/app/confirm/info/row/constants'; import { @@ -21,7 +19,7 @@ import { isPermitSignatureRequest, } from '../../../../utils'; import { useConfirmContext } from '../../../../context/confirm'; -import { selectUseTransactionSimulations } from '../../../../selectors/preferences'; +import { useTypesSignSimulationEnabledInfo } from '../../../../hooks/useTypesSignSimulationEnabledInfo'; import { ConfirmInfoRowTypedSignData } from '../../row/typed-sign-data/typedSignData'; import { isSnapId } from '../../../../../../helpers/utils/snaps'; import { SigningInWithRow } from '../shared/sign-in-with-row/sign-in-with-row'; @@ -30,9 +28,7 @@ import { TypedSignV4Simulation } from './typed-sign-v4-simulation'; const TypedSignInfo: React.FC = () => { const t = useI18nContext(); const { currentConfirmation } = useConfirmContext(); - const useTransactionSimulations = useSelector( - selectUseTransactionSimulations, - ); + const isSimulationSupported = useTypesSignSimulationEnabledInfo(); if (!currentConfirmation?.msgParams) { return null; @@ -44,9 +40,6 @@ const TypedSignInfo: React.FC = () => { } = parseTypedDataMessage(currentConfirmation.msgParams.data as string); const isPermit = isPermitSignatureRequest(currentConfirmation); - const isTypedSignV4 = - currentConfirmation.msgParams.signatureMethod === - MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4; const isOrder = isOrderSignatureRequest(currentConfirmation); const tokenContract = isPermit || isOrder ? verifyingContract : undefined; const { decimalsNumber } = useGetTokenStandardAndDetails(tokenContract); @@ -60,7 +53,7 @@ const TypedSignInfo: React.FC = () => { return ( <> - {isTypedSignV4 && useTransactionSimulations && } + {isSimulationSupported && } {isPermit && ( <> @@ -91,7 +84,7 @@ const TypedSignInfo: React.FC = () => { diff --git a/ui/pages/confirmations/confirm/__snapshots__/confirm.test.tsx.snap b/ui/pages/confirmations/confirm/__snapshots__/confirm.test.tsx.snap index ee656d5ebb53..e7e2e499dfe9 100644 --- a/ui/pages/confirmations/confirm/__snapshots__/confirm.test.tsx.snap +++ b/ui/pages/confirmations/confirm/__snapshots__/confirm.test.tsx.snap @@ -1927,30 +1927,19 @@ exports[`Confirm should match snapshot for signature - typed sign - V4 1`] = ` data-testid="confirmation_message-section" >
-
diff --git a/ui/pages/confirmations/hooks/useDecodedSignatureMetrics.test.ts b/ui/pages/confirmations/hooks/useDecodedSignatureMetrics.test.ts index c0023104ba8b..b378343bc274 100644 --- a/ui/pages/confirmations/hooks/useDecodedSignatureMetrics.test.ts +++ b/ui/pages/confirmations/hooks/useDecodedSignatureMetrics.test.ts @@ -22,7 +22,27 @@ const decodingData: DecodingData = { }; describe('useDecodedSignatureMetrics', () => { - process.env.ENABLE_SIGNATURE_DECODING = 'true'; + it('should not call updateSignatureEventFragment if supportedByDecodingAPI is false', async () => { + const state = getMockTypedSignConfirmStateForRequest({ + ...permitSignatureMsg, + decodingLoading: false, + }); + + const mockUpdateSignatureEventFragment = jest.fn(); + jest + .spyOn(SignatureEventFragment, 'useSignatureEventFragment') + .mockImplementation(() => ({ + updateSignatureEventFragment: mockUpdateSignatureEventFragment, + })); + + renderHookWithConfirmContextProvider( + () => useDecodedSignatureMetrics(false), + state, + ); + + expect(mockUpdateSignatureEventFragment).toHaveBeenCalledTimes(0); + }); + it('should not call updateSignatureEventFragment if decodingLoading is true', async () => { const state = getMockTypedSignConfirmStateForRequest({ ...permitSignatureMsg, @@ -37,7 +57,7 @@ describe('useDecodedSignatureMetrics', () => { })); renderHookWithConfirmContextProvider( - () => useDecodedSignatureMetrics(), + () => useDecodedSignatureMetrics(true), state, ); @@ -58,7 +78,7 @@ describe('useDecodedSignatureMetrics', () => { })); renderHookWithConfirmContextProvider( - () => useDecodedSignatureMetrics(), + () => useDecodedSignatureMetrics(true), state, ); @@ -86,7 +106,7 @@ describe('useDecodedSignatureMetrics', () => { })); renderHookWithConfirmContextProvider( - () => useDecodedSignatureMetrics(), + () => useDecodedSignatureMetrics(true), state, ); @@ -120,7 +140,7 @@ describe('useDecodedSignatureMetrics', () => { })); renderHookWithConfirmContextProvider( - () => useDecodedSignatureMetrics(), + () => useDecodedSignatureMetrics(true), state, ); diff --git a/ui/pages/confirmations/hooks/useDecodedSignatureMetrics.ts b/ui/pages/confirmations/hooks/useDecodedSignatureMetrics.ts index 1bf508bce655..98fd07984a9d 100644 --- a/ui/pages/confirmations/hooks/useDecodedSignatureMetrics.ts +++ b/ui/pages/confirmations/hooks/useDecodedSignatureMetrics.ts @@ -10,7 +10,7 @@ enum DecodingResponseType { NoChange = 'NO_CHANGE', } -export function useDecodedSignatureMetrics() { +export function useDecodedSignatureMetrics(supportedByDecodingAPI: boolean) { const { updateSignatureEventFragment } = useSignatureEventFragment(); const { currentConfirmation } = useConfirmContext(); const { decodingLoading, decodingData } = currentConfirmation; @@ -26,7 +26,7 @@ export function useDecodedSignatureMetrics() { : DecodingResponseType.NoChange); useEffect(() => { - if (decodingLoading || !process.env.ENABLE_SIGNATURE_DECODING) { + if (decodingLoading || !supportedByDecodingAPI) { return; } diff --git a/ui/pages/confirmations/hooks/useTypesSignSimulationEnabledInfo.test.ts b/ui/pages/confirmations/hooks/useTypesSignSimulationEnabledInfo.test.ts new file mode 100644 index 000000000000..ce98f58782d7 --- /dev/null +++ b/ui/pages/confirmations/hooks/useTypesSignSimulationEnabledInfo.test.ts @@ -0,0 +1,71 @@ +import { getMockTypedSignConfirmStateForRequest } from '../../../../test/data/confirmations/helper'; +import { renderHookWithConfirmContextProvider } from '../../../../test/lib/confirmations/render-helpers'; +import { + permitSingleSignatureMsg, + seaportSignatureMsg, + unapprovedTypedSignMsgV1, +} from '../../../../test/data/confirmations/typed_sign'; +import { useTypesSignSimulationEnabledInfo } from './useTypesSignSimulationEnabledInfo'; + +describe('useTypesSignSimulationEnabledInfo', () => { + it('return false if user has disabled simulations', async () => { + const state = getMockTypedSignConfirmStateForRequest( + permitSingleSignatureMsg, + { + metamask: { useTransactionSimulations: false }, + }, + ); + + const { result } = renderHookWithConfirmContextProvider( + () => useTypesSignSimulationEnabledInfo(), + state, + ); + + expect(result.current).toBe(false); + }); + + it('return false if request is not types sign v3 or V4', async () => { + const state = getMockTypedSignConfirmStateForRequest( + unapprovedTypedSignMsgV1, + { + metamask: { useTransactionSimulations: true }, + }, + ); + + const { result } = renderHookWithConfirmContextProvider( + () => useTypesSignSimulationEnabledInfo(), + state, + ); + + expect(result.current).toBe(false); + }); + + it('return true for typed sign v4 permit request', async () => { + const state = getMockTypedSignConfirmStateForRequest( + permitSingleSignatureMsg, + { + metamask: { useTransactionSimulations: true }, + }, + ); + + const { result } = renderHookWithConfirmContextProvider( + () => useTypesSignSimulationEnabledInfo(), + state, + ); + + expect(result.current).toBe(true); + }); + + it('return true for typed sign v4 seaport request', async () => { + const state = getMockTypedSignConfirmStateForRequest(seaportSignatureMsg, { + metamask: { useTransactionSimulations: true }, + }); + + const { result } = renderHookWithConfirmContextProvider( + () => useTypesSignSimulationEnabledInfo(), + state, + ); + + expect(result.current).toBe(true); + }); +}); diff --git a/ui/pages/confirmations/hooks/useTypesSignSimulationEnabledInfo.ts b/ui/pages/confirmations/hooks/useTypesSignSimulationEnabledInfo.ts new file mode 100644 index 000000000000..96703bffc0fb --- /dev/null +++ b/ui/pages/confirmations/hooks/useTypesSignSimulationEnabledInfo.ts @@ -0,0 +1,65 @@ +import { useSelector } from 'react-redux'; + +import { MESSAGE_TYPE } from '../../../../shared/constants/app'; +import { parseTypedDataMessage } from '../../../../shared/modules/transaction.utils'; +import { SignatureRequestType } from '../types/confirm'; +import { isPermitSignatureRequest } from '../utils'; +import { selectUseTransactionSimulations } from '../selectors/preferences'; +import { useConfirmContext } from '../context/confirm'; + +const NON_PERMIT_SUPPORTED_TYPES_SIGNS = [ + { + domainName: 'Seaport', + primaryTypeList: ['BulkOrder'], + versionList: ['1.4', '1.5', '1.6'], + }, + { + domainName: 'Seaport', + primaryTypeList: ['OrderComponents'], + }, +]; + +const isNonPermitSupportedByDecodingAPI = ( + signatureRequest: SignatureRequestType, +) => { + const data = (signatureRequest as SignatureRequestType).msgParams + ?.data as string; + if (!data) { + return false; + } + const { + domain: { name, version }, + primaryType, + } = parseTypedDataMessage(data); + return NON_PERMIT_SUPPORTED_TYPES_SIGNS.some( + ({ domainName, primaryTypeList, versionList }) => + name === domainName && + primaryTypeList.includes(primaryType) && + (!versionList || versionList.includes(version)), + ); +}; + +export function useTypesSignSimulationEnabledInfo() { + const { currentConfirmation } = useConfirmContext(); + const useTransactionSimulations = useSelector( + selectUseTransactionSimulations, + ); + + const signatureMethod = currentConfirmation?.msgParams?.signatureMethod; + const isTypedSignV3V4 = + signatureMethod === MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4 || + signatureMethod === MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V3; + const isPermit = isPermitSignatureRequest(currentConfirmation); + const nonPermitSupportedByDecodingAPI = + isTypedSignV3V4 && isNonPermitSupportedByDecodingAPI(currentConfirmation); + + if (!currentConfirmation) { + return undefined; + } + + return ( + useTransactionSimulations && + isTypedSignV3V4 && + (isPermit || nonPermitSupportedByDecodingAPI) + ); +}