diff --git a/.js.env.example b/.js.env.example index 38daeab2a2c..0d1d2764861 100644 --- a/.js.env.example +++ b/.js.env.example @@ -66,6 +66,9 @@ export SEGMENT_FLUSH_INTERVAL="1" # example for flush when 1 event is queued export SEGMENT_FLUSH_EVENT_LIMIT="1" +# URL of the decoding API used to provide additional data from signature requests +export DECODING_API_URL: 'https://signature-insights.api.cx.metamask.io/v1' + # URL of security alerts API used to validate dApp requests. export SECURITY_ALERTS_API_URL="https://security-alerts.api.cx.metamask.io" diff --git a/app/components/UI/Ramp/Views/BuildQuote/__snapshots__/BuildQuote.test.tsx.snap b/app/components/UI/Ramp/Views/BuildQuote/__snapshots__/BuildQuote.test.tsx.snap index 52e3ff03e42..1abf3c09e47 100644 --- a/app/components/UI/Ramp/Views/BuildQuote/__snapshots__/BuildQuote.test.tsx.snap +++ b/app/components/UI/Ramp/Views/BuildQuote/__snapshots__/BuildQuote.test.tsx.snap @@ -2604,7 +2604,6 @@ exports[`BuildQuote View Crypto Currency Data renders the loading page when cryp "padding": 0, }, undefined, - undefined, ] } > @@ -2794,7 +2793,6 @@ exports[`BuildQuote View Crypto Currency Data renders the loading page when cryp "padding": 0, }, undefined, - undefined, ] } > @@ -4194,7 +4192,6 @@ exports[`BuildQuote View Fiat Currency Data renders the loading page when fiats "padding": 0, }, undefined, - undefined, ] } > @@ -4384,7 +4381,6 @@ exports[`BuildQuote View Fiat Currency Data renders the loading page when fiats "padding": 0, }, undefined, - undefined, ] } > @@ -5784,7 +5780,6 @@ exports[`BuildQuote View Payment Method Data renders the loading page when payme "padding": 0, }, undefined, - undefined, ] } > @@ -5974,7 +5969,6 @@ exports[`BuildQuote View Payment Method Data renders the loading page when payme "padding": 0, }, undefined, - undefined, ] } > @@ -7374,7 +7368,6 @@ exports[`BuildQuote View Regions data renders the loading page when regions are "padding": 0, }, undefined, - undefined, ] } > @@ -7564,7 +7557,6 @@ exports[`BuildQuote View Regions data renders the loading page when regions are "padding": 0, }, undefined, - undefined, ] } > @@ -8511,7 +8503,6 @@ exports[`BuildQuote View renders correctly 1`] = ` "padding": 0, }, undefined, - undefined, ] } > @@ -8842,7 +8833,6 @@ exports[`BuildQuote View renders correctly 1`] = ` "padding": 0, }, undefined, - undefined, ] } > @@ -9137,7 +9127,6 @@ exports[`BuildQuote View renders correctly 1`] = ` "padding": 0, }, undefined, - undefined, ] } > @@ -11620,7 +11609,6 @@ exports[`BuildQuote View renders correctly 2`] = ` "padding": 0, }, undefined, - undefined, ] } > @@ -11951,7 +11939,6 @@ exports[`BuildQuote View renders correctly 2`] = ` "padding": 0, }, undefined, - undefined, ] } > @@ -12094,7 +12081,6 @@ exports[`BuildQuote View renders correctly 2`] = ` "padding": 0, }, undefined, - undefined, ] } > diff --git a/app/components/UI/Ramp/Views/OrderDetails/__snapshots__/OrderDetails.test.tsx.snap b/app/components/UI/Ramp/Views/OrderDetails/__snapshots__/OrderDetails.test.tsx.snap index 88387a8f98c..6ab703e40da 100644 --- a/app/components/UI/Ramp/Views/OrderDetails/__snapshots__/OrderDetails.test.tsx.snap +++ b/app/components/UI/Ramp/Views/OrderDetails/__snapshots__/OrderDetails.test.tsx.snap @@ -585,7 +585,6 @@ exports[`OrderDetails renders a cancelled order 1`] = ` "padding": 0, }, undefined, - undefined, ] } > @@ -2083,7 +2082,6 @@ exports[`OrderDetails renders a completed order 1`] = ` "padding": 0, }, undefined, - undefined, ] } > @@ -3595,7 +3593,6 @@ exports[`OrderDetails renders a created order 1`] = ` "padding": 0, }, undefined, - undefined, ] } > @@ -5023,7 +5020,6 @@ exports[`OrderDetails renders a failed order 1`] = ` "padding": 0, }, undefined, - undefined, ] } > @@ -6535,7 +6531,6 @@ exports[`OrderDetails renders a pending order 1`] = ` "padding": 0, }, undefined, - undefined, ] } > @@ -9070,7 +9065,6 @@ exports[`OrderDetails renders non-transacted orders 1`] = ` "padding": 0, }, undefined, - undefined, ] } > @@ -10609,7 +10603,6 @@ exports[`OrderDetails renders the support links if the provider has them 1`] = ` "padding": 0, }, undefined, - undefined, ] } > @@ -12141,7 +12134,6 @@ exports[`OrderDetails renders transacted orders that do not have timeDescription "padding": 0, }, undefined, - undefined, ] } > @@ -13616,7 +13608,6 @@ exports[`OrderDetails renders transacted orders that have timeDescriptionPending "padding": 0, }, undefined, - undefined, ] } > diff --git a/app/components/UI/Ramp/Views/PaymentMethods/__snapshots__/PaymentMethods.test.tsx.snap b/app/components/UI/Ramp/Views/PaymentMethods/__snapshots__/PaymentMethods.test.tsx.snap index e5a9d3b334f..2e25a8d696d 100644 --- a/app/components/UI/Ramp/Views/PaymentMethods/__snapshots__/PaymentMethods.test.tsx.snap +++ b/app/components/UI/Ramp/Views/PaymentMethods/__snapshots__/PaymentMethods.test.tsx.snap @@ -495,7 +495,6 @@ exports[`PaymentMethods View renders correctly 1`] = ` "padding": 0, }, undefined, - undefined, ] } > @@ -781,7 +780,6 @@ exports[`PaymentMethods View renders correctly 1`] = ` "padding": 0, }, undefined, - undefined, ] } > @@ -1084,7 +1082,6 @@ exports[`PaymentMethods View renders correctly 1`] = ` "padding": 0, }, undefined, - undefined, ] } > @@ -1945,7 +1942,6 @@ exports[`PaymentMethods View renders correctly for sell 1`] = ` "padding": 0, }, undefined, - undefined, ] } > @@ -2231,7 +2227,6 @@ exports[`PaymentMethods View renders correctly for sell 1`] = ` "padding": 0, }, undefined, - undefined, ] } > @@ -2534,7 +2529,6 @@ exports[`PaymentMethods View renders correctly for sell 1`] = ` "padding": 0, }, undefined, - undefined, ] } > @@ -3391,7 +3385,6 @@ exports[`PaymentMethods View renders correctly while loading 1`] = ` "padding": 0, }, undefined, - undefined, ] } > @@ -3641,7 +3634,6 @@ exports[`PaymentMethods View renders correctly while loading 1`] = ` "padding": 0, }, undefined, - undefined, ] } > @@ -3893,7 +3885,6 @@ exports[`PaymentMethods View renders correctly while loading 1`] = ` "padding": 0, }, undefined, - undefined, ] } > @@ -6591,7 +6582,6 @@ exports[`PaymentMethods View renders correctly with null data 1`] = ` "padding": 0, }, undefined, - undefined, ] } > @@ -6841,7 +6831,6 @@ exports[`PaymentMethods View renders correctly with null data 1`] = ` "padding": 0, }, undefined, - undefined, ] } > @@ -7093,7 +7082,6 @@ exports[`PaymentMethods View renders correctly with null data 1`] = ` "padding": 0, }, undefined, - undefined, ] } > @@ -7823,7 +7811,6 @@ exports[`PaymentMethods View renders correctly with payment method with disclaim "padding": 0, }, undefined, - undefined, ] } > @@ -8109,7 +8096,6 @@ exports[`PaymentMethods View renders correctly with payment method with disclaim "padding": 0, }, undefined, - undefined, ] } > @@ -8412,7 +8398,6 @@ exports[`PaymentMethods View renders correctly with payment method with disclaim "padding": 0, }, undefined, - undefined, ] } > @@ -9964,7 +9949,6 @@ exports[`PaymentMethods View renders correctly with show back button false 1`] = "padding": 0, }, undefined, - undefined, ] } > @@ -10250,7 +10234,6 @@ exports[`PaymentMethods View renders correctly with show back button false 1`] = "padding": 0, }, undefined, - undefined, ] } > @@ -10553,7 +10536,6 @@ exports[`PaymentMethods View renders correctly with show back button false 1`] = "padding": 0, }, undefined, - undefined, ] } > diff --git a/app/components/UI/Ramp/Views/Quotes/__snapshots__/Quotes.test.tsx.snap b/app/components/UI/Ramp/Views/Quotes/__snapshots__/Quotes.test.tsx.snap index 97f6fd0365a..3ad1e4f304e 100644 --- a/app/components/UI/Ramp/Views/Quotes/__snapshots__/Quotes.test.tsx.snap +++ b/app/components/UI/Ramp/Views/Quotes/__snapshots__/Quotes.test.tsx.snap @@ -35,7 +35,6 @@ exports[`LoadingQuotes component renders correctly 1`] = ` "padding": 0, }, undefined, - undefined, ] } > @@ -277,7 +276,6 @@ exports[`LoadingQuotes component renders correctly 1`] = ` "padding": 0, }, undefined, - undefined, ] } > @@ -416,7 +414,6 @@ exports[`LoadingQuotes component renders correctly 1`] = ` "padding": 0, }, undefined, - undefined, ] } > @@ -3546,7 +3543,6 @@ exports[`Quotes renders correctly after animation with quotes 1`] = ` "padding": 0, }, undefined, - undefined, ] } > @@ -3819,7 +3815,6 @@ exports[`Quotes renders correctly after animation with quotes 1`] = ` "padding": 0, }, undefined, - undefined, ] } > @@ -5023,7 +5018,6 @@ exports[`Quotes renders correctly after animation with quotes and expanded 2`] = "padding": 0, }, undefined, - undefined, ] } > @@ -5296,7 +5290,6 @@ exports[`Quotes renders correctly after animation with quotes and expanded 2`] = "padding": 0, }, undefined, - undefined, ] } > @@ -5596,7 +5589,6 @@ exports[`Quotes renders correctly after animation with quotes and expanded 2`] = "padding": 0, }, undefined, - undefined, ] } > diff --git a/app/components/UI/Ramp/Views/Regions/__snapshots__/Regions.test.tsx.snap b/app/components/UI/Ramp/Views/Regions/__snapshots__/Regions.test.tsx.snap index cffaf29508d..d181f8b8542 100644 --- a/app/components/UI/Ramp/Views/Regions/__snapshots__/Regions.test.tsx.snap +++ b/app/components/UI/Ramp/Views/Regions/__snapshots__/Regions.test.tsx.snap @@ -566,7 +566,6 @@ exports[`Regions View renders correctly 1`] = ` "padding": 0, }, undefined, - undefined, ] } > @@ -1346,7 +1345,6 @@ exports[`Regions View renders correctly while loading 1`] = ` undefined, undefined, undefined, - undefined, ] } > @@ -2575,7 +2573,6 @@ exports[`Regions View renders correctly with no data 1`] = ` undefined, undefined, undefined, - undefined, ] } > @@ -3825,7 +3822,6 @@ exports[`Regions View renders correctly with selectedRegion 1`] = ` "padding": 0, }, undefined, - undefined, ] } > @@ -4624,7 +4620,6 @@ exports[`Regions View renders correctly with unsupportedRegion 1`] = ` "padding": 0, }, undefined, - undefined, ] } > @@ -4949,7 +4944,6 @@ exports[`Regions View renders correctly with unsupportedRegion 1`] = ` undefined, undefined, undefined, - undefined, { "backgroundColor": "#ffffff", "borderWidth": 0, @@ -5799,7 +5793,6 @@ exports[`Regions View renders correctly with unsupportedRegion 2`] = ` "padding": 0, }, undefined, - undefined, ] } > @@ -6124,7 +6117,6 @@ exports[`Regions View renders correctly with unsupportedRegion 2`] = ` undefined, undefined, undefined, - undefined, { "backgroundColor": "#ffffff", "borderWidth": 0, @@ -6974,7 +6966,6 @@ exports[`Regions View renders regions modal when pressing select button 1`] = ` "padding": 0, }, undefined, - undefined, ] } > diff --git a/app/components/UI/Ramp/components/Box.tsx b/app/components/UI/Ramp/components/Box.tsx index 54eb1b9cd0e..41974701101 100644 --- a/app/components/UI/Ramp/components/Box.tsx +++ b/app/components/UI/Ramp/components/Box.tsx @@ -21,9 +21,6 @@ const createStyles = (colors: Colors) => label: { marginVertical: 8, }, - noBorder: { - borderWidth: 0, - }, highlighted: { borderColor: colors.primary.default, }, @@ -41,7 +38,6 @@ interface Props { style?: StyleProp; thin?: boolean; activeOpacity?: number; - noBorder?: boolean; // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any onPress?: () => any; @@ -60,7 +56,6 @@ const Box: React.FC = ({ accessible, accessibilityLabel, compact, - noBorder, ...props }: Props) => { const { colors } = useTheme(); @@ -82,7 +77,6 @@ const Box: React.FC = ({ thin && styles.thin, highlighted && styles.highlighted, compact && styles.compact, - noBorder && styles.noBorder, style, ]} {...props} diff --git a/app/components/Views/confirmations/Confirm/Confirm.tsx b/app/components/Views/confirmations/Confirm/Confirm.tsx index a13ec947e8d..4a18a99d7d1 100644 --- a/app/components/Views/confirmations/Confirm/Confirm.tsx +++ b/app/components/Views/confirmations/Confirm/Confirm.tsx @@ -8,7 +8,7 @@ import Footer from '../components/Confirm/Footer'; import Info from '../components/Confirm/Info'; import SignatureBlockaidBanner from '../components/Confirm/SignatureBlockaidBanner'; import Title from '../components/Confirm/Title'; -import useConfirmationRedesignEnabled from '../hooks/useConfirmationRedesignEnabled'; +import { useConfirmationRedesignEnabled } from '../hooks/useConfirmationRedesignEnabled'; import styleSheet from './Confirm.styles'; const Confirm = () => { diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/Simulation.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/Simulation.tsx new file mode 100644 index 00000000000..37265d0d0cd --- /dev/null +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/Simulation.tsx @@ -0,0 +1,31 @@ +import React from 'react'; + +import { useTypedSignSimulationEnabled } from '../../../../../hooks/useTypedSignSimulationEnabled'; +import { isRecognizedPermit } from '../../../../../utils/signature'; +import { useSignatureRequest } from '../../../../../hooks/useSignatureRequest'; +import DecodedSimulation from './TypedSignDecoded'; +import PermitSimulation from './TypedSignPermit'; + +const TypedSignV3V4Simulation: React.FC = () => { + const signatureRequest = useSignatureRequest(); + const isPermit = signatureRequest && isRecognizedPermit(signatureRequest); + const isSimulationSupported = useTypedSignSimulationEnabled(); + + if (!isSimulationSupported || !signatureRequest) { + return null; + } + + const { decodingData, decodingLoading } = signatureRequest; + const hasValidDecodingData = !( + (!decodingLoading && decodingData === undefined) || + decodingData?.error + ); + + if (!hasValidDecodingData && isPermit) { + return ; + } + + return ; +}; + +export default TypedSignV3V4Simulation; diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/Static/Static.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/Static/Static.tsx new file mode 100644 index 00000000000..febb2b71115 --- /dev/null +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/Static/Static.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { StyleSheet, View } from 'react-native'; + +import { useStyles } from '../../../../../../../../../component-library/hooks'; +import InfoRow from '../../../../../UI/InfoRow'; +import InfoSection from '../../../../../UI/InfoRow/InfoSection'; +import Loader from '../../../../../../../../../component-library/components-temp/Loader'; + +const styleSheet = () => StyleSheet.create({ + base: { + display: 'flex', + justifyContent: 'space-between', + }, + loaderContainer: { + display: 'flex', + justifyContent: 'center', + }, +}); + +const StaticSimulation: React.FC<{ + title: string; + titleTooltip: string; + description?: string; + simulationElements: React.ReactNode; + isLoading?: boolean; + isCollapsed?: boolean; +}> = ({ + title, + titleTooltip, + description, + simulationElements, + isLoading, + isCollapsed = false, +}) => { + const { styles } = useStyles(styleSheet, {}); + + return( + + + + {description} + + {isLoading ? ( + + + + ) : ( + simulationElements + )} + + + ); +}; + +export default StaticSimulation; diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/Static/index.ts b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/Static/index.ts new file mode 100644 index 00000000000..58015012827 --- /dev/null +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/Static/index.ts @@ -0,0 +1 @@ +export { default } from './Static'; diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/TypedSignDecoded.test.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/TypedSignDecoded.test.tsx new file mode 100644 index 00000000000..b974d951443 --- /dev/null +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/TypedSignDecoded.test.tsx @@ -0,0 +1,180 @@ +import React from 'react'; +import cloneDeep from 'lodash/cloneDeep'; +import { + DecodingDataChangeType, + DecodingDataStateChanges, + SignatureRequest, +} from '@metamask/signature-controller'; + +import { strings } from '../../../../../../../../../../locales/i18n'; +import { typedSignV4ConfirmationState } from '../../../../../../../../../util/test/confirm-data-helpers'; +import renderWithProvider from '../../../../../../../../../util/test/renderWithProvider'; +import TypedSignDecoded, { getStateChangeToolip, getStateChangeType, StateChangeType } from './TypedSignDecoded'; + +const stateChangesApprove = [ + { + assetType: 'ERC20', + changeType: DecodingDataChangeType.Approve, + address: '0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad', + amount: '12345', + contractAddress: '0x6b175474e89094c44da98b954eedeac495271d0f', + }, +]; + +const stateChangesListingERC1155: DecodingDataStateChanges = [ + { + assetType: 'NATIVE', + changeType: DecodingDataChangeType.Receive, + address: '', + amount: '900000000000000000', + contractAddress: '', + }, + { + assetType: 'ERC1155', + changeType: DecodingDataChangeType.Listing, + address: '', + amount: '', + contractAddress: '0xafd4896984CA60d2feF66136e57f958dCe9482d5', + tokenID: '77789', + }, +]; + +const stateChangesNftListing: DecodingDataStateChanges = [ + { + assetType: 'ERC721', + changeType: DecodingDataChangeType.Listing, + address: '', + amount: '', + contractAddress: '0x922dC160f2ab743312A6bB19DD5152C1D3Ecca33', + tokenID: '22222', + }, + { + assetType: 'ERC20', + changeType: DecodingDataChangeType.Receive, + address: '', + amount: '950000000000000000', + contractAddress: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', + }, +]; + +const stateChangesNftBidding: DecodingDataStateChanges = [ + { + assetType: 'ERC20', + changeType: DecodingDataChangeType.Bidding, + address: '', + amount: '', + contractAddress: '0x922dC160f2ab743312A6bB19DD5152C1D3Ecca33', + tokenID: '189', + }, + { + assetType: 'ERC721', + changeType: DecodingDataChangeType.Receive, + address: '', + amount: '950000000000000000', + contractAddress: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', + }, +]; + +const mockState = (mockStateChanges: DecodingDataStateChanges, stubDecodingLoading: boolean = false) => { + const clonedMockState = cloneDeep(typedSignV4ConfirmationState); + const request = clonedMockState.engine.backgroundState.SignatureController.signatureRequests['fb2029e1-b0ab-11ef-9227-05a11087c334'] as SignatureRequest; + request.decodingLoading = stubDecodingLoading; + request.decodingData = { + stateChanges: mockStateChanges, + }; + + return clonedMockState; +}; + +describe('DecodedSimulation', () => { + it('renders for ERC20 approval', async () => { + const { getByText } = renderWithProvider(, { + state: mockState(stateChangesApprove), + }); + + expect(await getByText('Estimated changes')).toBeDefined(); + expect(await getByText('Spending cap')).toBeDefined(); + expect(await getByText('12,345')).toBeDefined(); + }); + + it('renders "Unlimited" for large values', async () => { + const { getByText } = renderWithProvider(, { + state: mockState([{ + ...stateChangesApprove[0], + amount: '1461501637330902918203684832716283019655932542975', + }]), + }); + + expect(await getByText('Estimated changes')).toBeDefined(); + expect(await getByText('Spending cap')).toBeDefined(); + expect(await getByText('Unlimited')).toBeDefined(); + }); + + it('renders for ERC712 token', async () => { + const { getByText } = renderWithProvider(, { + state: mockState(stateChangesNftListing), + }); + + expect(await getByText('Estimated changes')).toBeDefined(); + expect(await getByText('Listing price')).toBeDefined(); + expect(await getByText('You list')).toBeDefined(); + expect(await getByText('#22222')).toBeDefined(); + }); + + it('renders for ERC1155 token', async () => { + const { getByText } = renderWithProvider(, { + state: mockState(stateChangesListingERC1155), + }); + + expect(await getByText('Estimated changes')).toBeDefined(); + expect(await getByText('You receive')).toBeDefined(); + expect(await getByText('You list')).toBeDefined(); + expect(await getByText('#77789')).toBeDefined(); + }); + + it('renders label only once if there are multiple state changes of same changeType', async () => { + const { getAllByText } = renderWithProvider(, { + state: mockState([stateChangesApprove[0], stateChangesApprove[0], stateChangesApprove[0]]), + }); + + expect(await getAllByText('12,345')).toHaveLength(3); + expect(await getAllByText('Spending cap')).toHaveLength(1); + }); + + it('renders unavailable message if no state change is returned', async () => { + const { getByText } = renderWithProvider(, { + state: mockState([]), + }); + + expect(await getByText('Estimated changes')).toBeDefined(); + expect(await getByText('Unavailable')).toBeDefined(); + }); + + describe('getStateChangeToolip', () => { + it('return correct tooltip when permit is for listing NFT', () => { + const tooltip = getStateChangeToolip( + StateChangeType.NFTListingReceive, + ); + expect(tooltip).toBe(strings('confirm.simulation.decoded_tooltip_list_nft')); + }); + + it('return correct tooltip when permit is for bidding NFT', () => { + const tooltip = getStateChangeToolip( + StateChangeType.NFTBiddingReceive, + ); + expect(tooltip).toBe(strings('confirm.simulation.decoded_tooltip_bid_nft')); + }); + }); + + describe('getStateChangeType', () => { + it('return correct state change type for NFT listing receive', () => { + const stateChange = getStateChangeType(stateChangesNftListing, stateChangesNftListing[1]); + expect(stateChange).toBe(StateChangeType.NFTListingReceive); + }); + + it('return correct state change type for NFT bidding receive', () => { + const stateChange = getStateChangeType(stateChangesNftBidding, stateChangesNftBidding[1]); + expect(stateChange).toBe(StateChangeType.NFTBiddingReceive); + }); + }); +}); diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/TypedSignDecoded.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/TypedSignDecoded.tsx new file mode 100644 index 00000000000..2adc5d40fec --- /dev/null +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/TypedSignDecoded.tsx @@ -0,0 +1,212 @@ +import React, { useMemo } from 'react'; +import { View } from 'react-native'; +import { + DecodingDataChangeType, + DecodingDataStateChange, + DecodingDataStateChanges, +} from '@metamask/signature-controller'; +import { Hex } from '@metamask/utils'; + +import { TokenStandard } from '../../../../../../../../UI/SimulationDetails/types'; +import Text from '../../../../../../../../../component-library/components/Texts/Text'; +import { strings } from '../../../../../../../../../../locales/i18n'; +import { useSignatureRequest } from '../../../../../../hooks/useSignatureRequest'; +import InfoRow from '../../../../../UI/InfoRow'; +import NativeValueDisplay from '../components/NativeValueDisplay'; +import SimulationValueDisplay from '../components/ValueDisplay'; +import StaticSimulation from '../Static'; + +const styles = { + unavailableContainer: { + paddingHorizontal: 8, + paddingBottom: 8, + }, +}; + +export enum StateChangeType { + NFTListingReceive = 'NFTListingReceive', + NFTBiddingReceive = 'NFTBiddingReceive', +} + +export const getStateChangeType = ( + stateChangeList: DecodingDataStateChanges | null, + stateChange: DecodingDataStateChange, +): StateChangeType | undefined => { + if (stateChange.changeType === DecodingDataChangeType.Receive) { + if ( + stateChangeList?.some( + (change) => + change.changeType === DecodingDataChangeType.Listing && + change.assetType === TokenStandard.ERC721, + ) + ) { + return StateChangeType.NFTListingReceive; + } + if ( + stateChange.assetType === TokenStandard.ERC721 && + stateChangeList?.some( + (change) => change.changeType === DecodingDataChangeType.Bidding, + ) + ) { + return StateChangeType.NFTBiddingReceive; + } + } + return undefined; +}; + +export const getStateChangeToolip = ( + nftTransactionType: StateChangeType | undefined, +): string | undefined => { + if (nftTransactionType === StateChangeType.NFTListingReceive) { + return strings('confirm.simulation.decoded_tooltip_list_nft'); + } else if (nftTransactionType === StateChangeType.NFTBiddingReceive) { + return strings('confirm.simulation.decoded_tooltip_bid_nft'); + } + return undefined; +}; + +const stateChangeOrder = { + [DecodingDataChangeType.Transfer]: 1, + [DecodingDataChangeType.Listing]: 2, + [DecodingDataChangeType.Approve]: 3, + [DecodingDataChangeType.Revoke]: 4, + [DecodingDataChangeType.Bidding]: 5, + [DecodingDataChangeType.Receive]: 6, +}; + +const getStateChangeLabelMap = ( + changeType: string, + stateChangeType?: StateChangeType, +) => ({ + [DecodingDataChangeType.Transfer]: strings('confirm.simulation.label_change_type_transfer'), + [DecodingDataChangeType.Receive]: + stateChangeType === StateChangeType.NFTListingReceive + ? strings('confirm.simulation.label_change_type_nft_listing') + : strings('confirm.simulation.label_change_type_receive'), + [DecodingDataChangeType.Approve]: strings('confirm.simulation.label_change_type_permit'), + [DecodingDataChangeType.Revoke]: strings('confirm.simulation.label_change_type_permit'), + [DecodingDataChangeType.Bidding]: strings('confirm.simulation.label_change_type_bidding'), + [DecodingDataChangeType.Listing]: strings('confirm.simulation.label_change_type_listing'), + }[changeType]); + +const StateChangeRow = ({ + stateChangeList, + stateChange, + chainId, + shouldDisplayLabel, +}: { + stateChangeList: DecodingDataStateChanges | null; + stateChange: DecodingDataStateChange; + chainId: Hex; + shouldDisplayLabel: boolean; +}) => { + const { assetType, changeType, amount, contractAddress, tokenID } = + stateChange; + const nftTransactionType = getStateChangeType(stateChangeList, stateChange); + const tooltip = shouldDisplayLabel ? getStateChangeToolip(nftTransactionType) : undefined; + + const canDisplayValueAsUnlimited = + assetType === TokenStandard.ERC20 && + (changeType === DecodingDataChangeType.Approve || + changeType === DecodingDataChangeType.Revoke); + + const changeLabel = shouldDisplayLabel + ? getStateChangeLabelMap(changeType, nftTransactionType) + : ''; + + return ( + + {(assetType === TokenStandard.ERC20 || + assetType === TokenStandard.ERC721 || + assetType === TokenStandard.ERC1155) && ( + + )} + {assetType === 'NATIVE' && ( + + )} + + ); +}; + +const DecodedSimulation: React.FC = () => { + const signatureRequest = useSignatureRequest(); + + const chainId = signatureRequest?.chainId as Hex; + const { decodingLoading, decodingData } = signatureRequest ?? {}; + + const stateChangeFragment = useMemo(() => { + const orderedStateChanges = [...(decodingData?.stateChanges ?? [])].sort((c1, c2) => + stateChangeOrder[c1.changeType] > stateChangeOrder[c2.changeType] + ? 1 + : -1, + ); + const stateChangesGrouped: Record = ( + orderedStateChanges ?? [] + ).reduce>( + (result, stateChange) => { + result[stateChange.changeType] = [ + ...(result[stateChange.changeType] ?? []), + stateChange, + ]; + return result; + }, + {}, + ); + + return Object.entries(stateChangesGrouped).flatMap(([_, changeList]) => + changeList.map((change: DecodingDataStateChange, index: number) => ( + + )), + ); + }, [chainId, decodingData?.stateChanges]); + + return ( + + {strings('confirm.simulation.unavailable')} + + ) + } + isLoading={decodingLoading} + isCollapsed={decodingLoading || !stateChangeFragment.length} + /> + ); +}; + +export default DecodedSimulation; diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/index.ts b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/index.ts new file mode 100644 index 00000000000..f4bd7dcc026 --- /dev/null +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/index.ts @@ -0,0 +1 @@ +export { default } from './TypedSignDecoded'; diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/NativeValueDisplay/NativeValueDisplay.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/NativeValueDisplay/NativeValueDisplay.tsx new file mode 100644 index 00000000000..54ad3cd28e8 --- /dev/null +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/NativeValueDisplay/NativeValueDisplay.tsx @@ -0,0 +1,153 @@ +import React, { useState } from 'react'; +import { Text,TouchableOpacity, View } from 'react-native'; +import { useSelector } from 'react-redux'; +import { Hex } from '@metamask/utils'; +import { BigNumber } from 'bignumber.js'; + +import { RootState } from '../../../../../../../../../../reducers'; +import { selectConversionRateByChainId } from '../../../../../../../../../../selectors/currencyRateController'; +import { useTheme } from '../../../../../../../../../../util/theme'; + +import ButtonPill from '../../../../../../../../../../component-library/components-temp/Buttons/ButtonPill/ButtonPill'; +import { ButtonIconSizes } from '../../../../../../../../../../component-library/components/Buttons/ButtonIcon/ButtonIcon.types'; +import ButtonIcon from '../../../../../../../../../../component-library/components/Buttons/ButtonIcon/ButtonIcon'; +import { IconName , IconColor } from '../../../../../../../../../../component-library/components/Icons/Icon'; + +import AssetPill from '../../../../../../../../../UI/SimulationDetails/AssetPill/AssetPill'; +import { IndividualFiatDisplay } from '../../../../../../../../../UI/SimulationDetails/FiatDisplay/FiatDisplay'; +import { + formatAmount, + formatAmountMaxPrecision, +} from '../../../../../../../../../UI/SimulationDetails/formatAmount'; +import { AssetType } from '../../../../../../../../../UI/SimulationDetails/types'; +import { shortenString } from '../../../../../../../../../../util/notifications/methods/common'; +import { isNumberValue } from '../../../../../../../../../../util/number'; +import { calcTokenAmount } from '../../../../../../../../../../util/transactions'; +import BottomModal from '../../../../../../UI/BottomModal'; + +/** + * Reusing ValueDisplay styles for now. See issue to handle abstracting UI + * @see {@link https://github.com/MetaMask/metamask-mobile/issues/12974} + */ +import styleSheet from '../ValueDisplay/ValueDisplay.styles'; + +const NATIVE_DECIMALS = 18; + +interface PermitSimulationValueDisplayParams { + /** ID of the associated chain. */ + chainId: Hex; + + /** Change type to be displayed in value tooltip */ + labelChangeType: string; + + /** The token amount */ + value: number | string; + + /** True if value is being credited to wallet */ + credit?: boolean; + + /** True if value is being debited to wallet */ + debit?: boolean; +} + +const NativeValueDisplay: React.FC = ({ + chainId, + credit, + debit, + labelChangeType, + value, +}) => { + const [hasValueModalOpen, setHasValueModalOpen] = useState(false); + + const { colors } = useTheme(); + const styles = styleSheet(colors); + + const conversionRate = useSelector((state: RootState) => + selectConversionRateByChainId(state, chainId), + ); + + const tokenAmount = isNumberValue(value) ? calcTokenAmount(value, NATIVE_DECIMALS) : null; + const isValidTokenAmount = tokenAmount !== null && tokenAmount !== undefined && tokenAmount instanceof BigNumber; + + const fiatValue = isValidTokenAmount && conversionRate + ? tokenAmount.times(String(conversionRate)).toNumber() + : undefined; + + const tokenValue = isValidTokenAmount ? formatAmount('en-US', tokenAmount) : null; + const tokenValueMaxPrecision = isValidTokenAmount ? formatAmountMaxPrecision('en-US', tokenAmount) : null; + + function handlePressTokenValue() { + setHasValueModalOpen(true); + } + + return ( + + + + + + {credit && '+ '} + {debit && '- '} + {tokenValue !== null && + shortenString(tokenValue || '', { + truncatedCharLimit: 15, + truncatedStartChars: 15, + truncatedEndChars: 0, + skipCharacterInEnd: true, + })} + + + + + + + + + {/** + TODO - add fiat shorten prop after tooltip logic has been updated + {@see {@link https://github.com/MetaMask/metamask-mobile/issues/12656} + */} + {fiatValue !== undefined && ( + + )} + + {hasValueModalOpen && ( + /** + * TODO replace BottomModal instances with BottomSheet + * {@see {@link https://github.com/MetaMask/metamask-mobile/issues/12656}} + */ + setHasValueModalOpen(false)}> + setHasValueModalOpen(false)} + > + + + setHasValueModalOpen(false)} + iconName={IconName.ArrowLeft} + /> + + {labelChangeType} + + + + {tokenValueMaxPrecision} + + + + + )} + + ); +}; + +export default NativeValueDisplay; diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/NativeValueDisplay/index.ts b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/NativeValueDisplay/index.ts new file mode 100644 index 00000000000..1e6534cfb1c --- /dev/null +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/NativeValueDisplay/index.ts @@ -0,0 +1 @@ +export { default } from './NativeValueDisplay'; diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.styles.ts b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.styles.ts index 6414e76e0ba..9a1da0579cc 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.styles.ts +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.styles.ts @@ -12,7 +12,9 @@ const styleSheet = (colors: Theme['colors']) => borderWidth: 0, padding: 0, }, - + fiatDisplay: { + paddingEnd: 8, + }, flexRowTokenValueAndAddress: { display: 'flex', flexDirection: 'row', @@ -22,7 +24,7 @@ const styleSheet = (colors: Theme['colors']) => borderWidth: 0, padding: 0, }, - tokenAddress: { + marginStart4: { marginStart: 4, }, tokenValueTooltipContent: { @@ -35,7 +37,6 @@ const styleSheet = (colors: Theme['colors']) => valueAndAddress: { paddingVertical: 4, paddingLeft: 8, - paddingRight: 8, gap: 5, flexDirection: 'row', alignItems: 'center', diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.test.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.test.tsx index 8decf038ea1..04d399656f5 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.test.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.test.tsx @@ -23,7 +23,6 @@ const mockTrackEvent = jest.fn(); jest.mock('../../../../../../../../../hooks/useMetrics'); jest.mock('../../../../../../../hooks/useGetTokenStandardAndDetails'); - jest.mock('../../../../../../../../../../util/address', () => ({ getTokenDetails: jest.fn(), renderShortAddress: jest.requireActual('../../../../../../../../../../util/address').renderShortAddress @@ -72,11 +71,30 @@ describe('SimulationValueDisplay', () => { { state: mockInitialState }, ); - await act(async () => { - await Promise.resolve(); + expect(await findByText('0.432')).toBeDefined(); + }); + + it('renders "Unlimited" for large values when canDisplayValueAsUnlimited is true', async () => { + (useGetTokenStandardAndDetails as jest.MockedFn).mockReturnValue({ + symbol: 'TST', + decimals: '4', + balance: undefined, + standard: TokenStandard.ERC20, + decimalsNumber: 4, }); - expect(await findByText('0.432')).toBeDefined(); + const { findByText } = renderWithProvider( + , + { state: mockInitialState }, + ); + + expect(await findByText('Unlimited')).toBeDefined(); }); it('should invoke method to track missing decimal information for ERC20 tokens only once', async () => { @@ -98,10 +116,6 @@ describe('SimulationValueDisplay', () => { { state: mockInitialState }, ); - await act(async () => { - await Promise.resolve(); - }); - expect(mockTrackEvent).toHaveBeenCalledTimes(1); }); @@ -124,10 +138,6 @@ describe('SimulationValueDisplay', () => { { state: mockInitialState }, ); - await act(async () => { - await Promise.resolve(); - }); - expect(mockTrackEvent).not.toHaveBeenCalled(); }); diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.tsx index e59dcf7d43e..44622075f16 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.tsx @@ -1,8 +1,9 @@ -import React, { useMemo, useState } from 'react'; +import React, { useState } from 'react'; import { TouchableOpacity, View } from 'react-native'; import { useSelector } from 'react-redux'; import { NetworkClientId } from '@metamask/network-controller'; import { Hex } from '@metamask/utils'; +import { BigNumber } from 'bignumber.js'; import ButtonPill from '../../../../../../../../../../component-library/components-temp/Buttons/ButtonPill/ButtonPill'; import { ButtonIconSizes } from '../../../../../../../../../../component-library/components/Buttons/ButtonIcon/ButtonIcon.types'; @@ -16,22 +17,24 @@ import { formatAmountMaxPrecision, } from '../../../../../../../../../UI/SimulationDetails/formatAmount'; -import Box from '../../../../../../../../../UI/Ramp/components/Box'; import Address from '../../../../../../UI/InfoRow/InfoValue/Address/Address'; import { selectContractExchangeRates } from '../../../../../../../../../../selectors/tokenRatesController'; import Logger from '../../../../../../../../../../util/Logger'; import { shortenString } from '../../../../../../../../../../util/notifications/methods/common'; +import { isNumberValue } from '../../../../../../../../../../util/number'; import { useTheme } from '../../../../../../../../../../util/theme'; import { calcTokenAmount } from '../../../../../../../../../../util/transactions'; import useGetTokenStandardAndDetails from '../../../../../../../hooks/useGetTokenStandardAndDetails'; import useTrackERC20WithoutDecimalInformation from '../../../../../../../hooks/useTrackERC20WithoutDecimalInformation'; +import { TOKEN_VALUE_UNLIMITED_THRESHOLD } from '../../../../../../../utils/confirm'; import { TokenDetailsERC20 } from '../../../../../../../utils/token'; import BottomModal from '../../../../../../UI/BottomModal'; import styleSheet from './ValueDisplay.styles'; +import { strings } from '../../../../../../../../../../../locales/i18n'; interface SimulationValueDisplayParams { /** ID of the associated chain. */ @@ -52,6 +55,9 @@ interface SimulationValueDisplayParams { // Optional + /** Whether a large amount can be substituted by "Unlimited" */ + canDisplayValueAsUnlimited?: boolean; + /** True if value is being credited to wallet */ credit?: boolean; @@ -80,6 +86,7 @@ const SimulationValueDisplay: React.FC< value, credit, debit, + canDisplayValueAsUnlimited = false, }) => { const [hasValueModalOpen, setHasValueModalOpen] = useState(false); @@ -101,26 +108,18 @@ const SimulationValueDisplay: React.FC< tokenDetails as TokenDetailsERC20, ); - const fiatValue = useMemo(() => { - if (exchangeRate && value && !tokenId) { - const tokenAmount = calcTokenAmount(value, tokenDecimals); - return tokenAmount.multipliedBy(exchangeRate).toNumber(); - } - return undefined; - }, [exchangeRate, tokenDecimals, tokenId, value]); + const tokenAmount = isNumberValue(value) && !tokenId ? calcTokenAmount(value, tokenDecimals) : null; + const isValidTokenAmount = tokenAmount !== null && tokenAmount !== undefined && tokenAmount instanceof BigNumber; - const { tokenValue, tokenValueMaxPrecision } = useMemo(() => { - if (!value || tokenId) { - return { tokenValue: null, tokenValueMaxPrecision: null }; - } + const fiatValue = isValidTokenAmount && exchangeRate && !tokenId + ? tokenAmount.multipliedBy(exchangeRate).toNumber() + : undefined; - const tokenAmount = calcTokenAmount(value, tokenDecimals); + const tokenValue = isValidTokenAmount ? formatAmount('en-US', tokenAmount) : null; + const tokenValueMaxPrecision = isValidTokenAmount ? formatAmountMaxPrecision('en-US', tokenAmount) : null; - return { - tokenValue: formatAmount('en-US', tokenAmount), - tokenValueMaxPrecision: formatAmountMaxPrecision('en-US', tokenAmount), - }; - }, [tokenDecimals, tokenId, value]); + const shouldShowUnlimitedValue = canDisplayValueAsUnlimited && + Number(value) > TOKEN_VALUE_UNLIMITED_THRESHOLD; /** Temporary error capturing as we are building out Permit Simulations */ if (!tokenContract) { @@ -137,10 +136,11 @@ const SimulationValueDisplay: React.FC< } return ( - - + + {credit && '+ '} {debit && '- '} - {tokenValue !== null && - shortenString(tokenValue || '', { + {shouldShowUnlimitedValue + ? strings('confirm.unlimited') + : tokenValue !== null && + shortenString(tokenValue || '', { truncatedCharLimit: 15, truncatedStartChars: 15, truncatedEndChars: 0, @@ -159,18 +161,18 @@ const SimulationValueDisplay: React.FC< {tokenId && `#${tokenId}`} - +
- + - - - {/* + + + {/** TODO - add fiat shorten prop after tooltip logic has been updated {@see {@link https://github.com/MetaMask/metamask-mobile/issues/12656} */} {fiatValue && } - + {hasValueModalOpen && ( /** * TODO replace BottomModal instances with BottomSheet @@ -201,7 +203,7 @@ const SimulationValueDisplay: React.FC< )} - + ); }; diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/index.ts b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/index.ts new file mode 100644 index 00000000000..50cee91255f --- /dev/null +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/index.ts @@ -0,0 +1 @@ +export { default } from './Simulation'; diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/TypedSignV3V4.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/TypedSignV3V4.tsx index 57ba7552428..2caee661061 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/TypedSignV3V4.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/TypedSignV3V4.tsx @@ -1,31 +1,14 @@ import React from 'react'; -import { useSelector } from 'react-redux'; -import { selectUseTransactionSimulations } from '../../../../../../../selectors/preferencesController'; -import useApprovalRequest from '../../../../hooks/useApprovalRequest'; -import { isRecognizedPermit } from '../../../../utils/signature'; import InfoRowOrigin from '../Shared/InfoRowOrigin'; -import PermitSimulation from './Simulation/TypedSignPermit'; import Message from './Message'; +import TypedSignV3V4Simulation from './Simulation'; -const TypedSignV3V4 = () => { - const { approvalRequest } = useApprovalRequest(); - const useSimulation = useSelector( - selectUseTransactionSimulations, - ); - - if (!approvalRequest) { - return null; - } - - const isPermit = isRecognizedPermit(approvalRequest); - - return ( +const TypedSignV3V4 = () => ( <> - {isPermit && useSimulation && } + ); -}; export default TypedSignV3V4; diff --git a/app/components/Views/confirmations/components/SignatureRequest/Root/Root.tsx b/app/components/Views/confirmations/components/SignatureRequest/Root/Root.tsx index f610b170a57..9e8bb043755 100644 --- a/app/components/Views/confirmations/components/SignatureRequest/Root/Root.tsx +++ b/app/components/Views/confirmations/components/SignatureRequest/Root/Root.tsx @@ -6,7 +6,7 @@ import { useSelector } from 'react-redux'; import setSignatureRequestSecurityAlertResponse from '../../../../../../actions/signatureRequest'; import { store } from '../../../../../../store'; import { useTheme } from '../../../../../../util/theme'; -import useConfirmationRedesignEnabled from '../../../hooks/useConfirmationRedesignEnabled'; +import { useConfirmationRedesignEnabled } from '../../../hooks/useConfirmationRedesignEnabled'; import PersonalSign from '../../PersonalSign'; import TypedSign from '../../TypedSign'; import { MessageParams } from '../types'; diff --git a/app/components/Views/confirmations/components/UI/ExpandableSection/ExpandableSection.tsx b/app/components/Views/confirmations/components/UI/ExpandableSection/ExpandableSection.tsx index 0417a1c9ce2..4c738c73648 100644 --- a/app/components/Views/confirmations/components/UI/ExpandableSection/ExpandableSection.tsx +++ b/app/components/Views/confirmations/components/UI/ExpandableSection/ExpandableSection.tsx @@ -61,7 +61,7 @@ const ExpandableSection = ({ {expanded && ( - + setExpanded(false)} canCloseOnBackdropClick> ({ getTotalFiatAccountBalance: () => ({ tokenFiat: 10 }), diff --git a/app/components/Views/confirmations/hooks/useConfirmationRedesignEnabled.ts b/app/components/Views/confirmations/hooks/useConfirmationRedesignEnabled.ts index 140c58dcde4..9ca0e9919cc 100644 --- a/app/components/Views/confirmations/hooks/useConfirmationRedesignEnabled.ts +++ b/app/components/Views/confirmations/hooks/useConfirmationRedesignEnabled.ts @@ -7,7 +7,7 @@ import { selectRemoteFeatureFlags } from '../../../../selectors/featureFlagContr import useApprovalRequest from './useApprovalRequest'; import useQRHardwareAwareness from './useQRHardwareAwareness'; -const useConfirmationRedesignEnabled = () => { +export const useConfirmationRedesignEnabled = () => { const { approvalRequest } = useApprovalRequest(); const { isSigningQRObject, isSyncingQRHardware } = useQRHardwareAwareness(); const { confirmation_redesign } = useSelector(selectRemoteFeatureFlags); @@ -22,7 +22,6 @@ const useConfirmationRedesignEnabled = () => { const isRedesignedEnabled = useMemo( () => (confirmation_redesign as Record)?.signatures && - process.env.REDESIGNED_SIGNATURE_REQUEST === 'true' && // following condition will ensure that user is redirected to old designs is using QR scan aware hardware !isSyncingQRHardware && !isSigningQRObject && @@ -43,5 +42,3 @@ const useConfirmationRedesignEnabled = () => { return { isRedesignedEnabled }; }; - -export default useConfirmationRedesignEnabled; diff --git a/app/components/Views/confirmations/hooks/useTypedSignSimulationEnabled.ts b/app/components/Views/confirmations/hooks/useTypedSignSimulationEnabled.ts new file mode 100644 index 00000000000..0553f8fcae2 --- /dev/null +++ b/app/components/Views/confirmations/hooks/useTypedSignSimulationEnabled.ts @@ -0,0 +1,66 @@ +import { useSelector } from 'react-redux'; +import { SignTypedDataVersion } from '@metamask/eth-sig-util'; +import { MessageParamsTyped, SignatureRequest, SignatureRequestType } from '@metamask/signature-controller'; +import { selectUseTransactionSimulations } from '../../../../selectors/preferencesController'; +import { isRecognizedPermit, parseTypedDataMessage } from '../utils/signature'; +import { useSignatureRequest } from './useSignatureRequest'; + +const NON_PERMIT_SUPPORTED_TYPES_SIGNS = [ + { + domainName: 'Seaport', + primaryTypeList: ['BulkOrder'], + versionList: ['1.4', '1.5', '1.6'], + }, + { + domainName: 'Seaport', + primaryTypeList: ['OrderComponents'], + }, +]; + +const isNonPermitSupportedByDecodingAPI = ( + signatureRequest: SignatureRequest, +) => { + const data = signatureRequest.messageParams?.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 useTypedSignSimulationEnabled() { + const signatureRequest = useSignatureRequest(); + const useTransactionSimulations = useSelector( + selectUseTransactionSimulations, + ); + + if (!signatureRequest) { + return undefined; + } + + const requestType = signatureRequest.type; + const signatureMethod = (signatureRequest.messageParams as MessageParamsTyped)?.version; + + const isTypedSignV3V4 = requestType === SignatureRequestType.TypedSign && ( + signatureMethod === SignTypedDataVersion.V3 || + signatureMethod === SignTypedDataVersion.V4 + ); + const isPermit = isRecognizedPermit(signatureRequest); + + const nonPermitSupportedByDecodingAPI: boolean = + isTypedSignV3V4 && isNonPermitSupportedByDecodingAPI(signatureRequest); + + return ( + useTransactionSimulations && + isTypedSignV3V4 && + (isPermit || nonPermitSupportedByDecodingAPI) + ); +} diff --git a/app/components/Views/confirmations/utils/confirm.ts b/app/components/Views/confirmations/utils/confirm.ts index 6f9b61bdf5c..f4e414f1583 100644 --- a/app/components/Views/confirmations/utils/confirm.ts +++ b/app/components/Views/confirmations/utils/confirm.ts @@ -1,8 +1,11 @@ import { ApprovalTypes } from '../../../../core/RPCMethods/RPCMethodMiddleware'; +export const TOKEN_VALUE_UNLIMITED_THRESHOLD = 10 ** 15; + export function isSignatureRequest(requestType: string) { return [ ApprovalTypes.PERSONAL_SIGN, ApprovalTypes.ETH_SIGN_TYPED_DATA, ].includes(requestType as ApprovalTypes); } + diff --git a/app/components/Views/confirmations/utils/signature.test.ts b/app/components/Views/confirmations/utils/signature.test.ts index 13be1389591..08472ef3f27 100644 --- a/app/components/Views/confirmations/utils/signature.test.ts +++ b/app/components/Views/confirmations/utils/signature.test.ts @@ -1,6 +1,6 @@ -import { ApprovalRequest } from '@metamask/approval-controller'; import { parseTypedDataMessage, isRecognizedPermit } from './signature'; import { PRIMARY_TYPES_PERMIT } from '../constants/signatures'; +import { SignatureRequest } from '@metamask/signature-controller'; describe('Signature Utils', () => { describe('parseTypedDataMessage', () => { @@ -46,25 +46,25 @@ describe('Signature Utils', () => { describe('isRecognizedPermit', () => { it('should return true for recognized permit types', () => { - const mockRequest: ApprovalRequest<{ data: string }> = { - requestData: { + const mockRequest: SignatureRequest = { + messageParams: { data: JSON.stringify({ primaryType: PRIMARY_TYPES_PERMIT[0] }) } - } as ApprovalRequest<{ data: string }>; + } as SignatureRequest; expect(isRecognizedPermit(mockRequest)).toBe(true); }); it('should return false for unrecognized permit types', () => { - const mockRequest: ApprovalRequest<{ data: string }> = { - requestData: { + const mockRequest: SignatureRequest = { + messageParams: { data: JSON.stringify({ primaryType: 'UnrecognizedType' }) } - } as ApprovalRequest<{ data: string }>; + } as SignatureRequest; expect(isRecognizedPermit(mockRequest)).toBe(false); }); diff --git a/app/components/Views/confirmations/utils/signature.ts b/app/components/Views/confirmations/utils/signature.ts index 73c19e2b9b7..9035a2b3087 100644 --- a/app/components/Views/confirmations/utils/signature.ts +++ b/app/components/Views/confirmations/utils/signature.ts @@ -1,4 +1,4 @@ -import { ApprovalRequest } from '@metamask/approval-controller'; +import { SignatureRequest } from '@metamask/signature-controller'; import { PRIMARY_TYPES_PERMIT } from '../constants/signatures'; /** @@ -47,9 +47,15 @@ export const parseTypedDataMessage = (dataToParse: string) => { /** * Returns true if the request is a recognized Permit Typed Sign signature request * - * @param request - The confirmation request to check + * @param request - The signature request to check */ -export const isRecognizedPermit = (approvalRequest: ApprovalRequest<{ data: string }>) => { - const { primaryType } = parseTypedDataMessage(approvalRequest.requestData.data); +export const isRecognizedPermit = (request: SignatureRequest) => { + if (!request) { + return false; + } + + const data = (request as SignatureRequest).messageParams?.data as string; + + const { primaryType } = parseTypedDataMessage(data); return PRIMARY_TYPES_PERMIT.includes(primaryType); }; diff --git a/app/core/AppConstants.ts b/app/core/AppConstants.ts index 7d132663576..7f48671fa74 100644 --- a/app/core/AppConstants.ts +++ b/app/core/AppConstants.ts @@ -137,6 +137,7 @@ export default { 'https://support.metamask.io/transactions-and-gas/transactions/smart-transactions/', STAKING_RISK_DISCLOSURE: 'https://consensys.io/staking-risk-disclosures', }, + DECODING_API_URL: process.env.DECODING_API_URL || 'https://signature-insights.api.cx.metamask.io/v1', ERRORS: { INFURA_BLOCKED_MESSAGE: 'EthQuery - RPC Error - This service is not available in your country', diff --git a/app/core/Engine/Engine.ts b/app/core/Engine/Engine.ts index a0fe49693c3..740c50e7b45 100644 --- a/app/core/Engine/Engine.ts +++ b/app/core/Engine/Engine.ts @@ -1441,6 +1441,10 @@ export class Engine { }), // This casting expected due to mismatch of browser and react-native version of Sentry traceContext trace: trace as unknown as SignatureControllerOptions['trace'], + decodingApiUrl: AppConstants.DECODING_API_URL, + // TODO: check preferences useExternalServices + isDecodeSignatureRequestEnabled: () => + preferencesController.state.useTransactionSimulations, }), LoggingController: loggingController, ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) diff --git a/app/reducers/transaction/index.js b/app/reducers/transaction/index.js index 5d1ab2efb94..3b37c620150 100644 --- a/app/reducers/transaction/index.js +++ b/app/reducers/transaction/index.js @@ -110,6 +110,8 @@ const transactionReducer = (state = initialState, action) => { ...getTxData(action.transaction), }, ...txMeta, + // Retain the securityAlertResponses from the old state + securityAlertResponses: state.securityAlertResponses, }; } case 'SET_TOKENS_TRANSACTION': { diff --git a/app/selectors/currencyRateController.test.ts b/app/selectors/currencyRateController.test.ts index e48f6b957a4..61a29d38137 100644 --- a/app/selectors/currencyRateController.test.ts +++ b/app/selectors/currencyRateController.test.ts @@ -2,6 +2,7 @@ import { selectConversionRate, selectCurrentCurrency, selectCurrencyRates, + selectConversionRateByChainId, } from './currencyRateController'; import { isTestNet } from '../../app/util/networks'; import { CurrencyRateState } from '@metamask/assets-controllers'; @@ -64,6 +65,41 @@ describe('CurrencyRateController Selectors', () => { }); }); + describe('selectConversionRateByChainId', () => { + const mockChainId = '1'; + const mockNativeCurrency = 'ETH'; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('returns undefined if on a testnet and fiat is disabled', () => { + (isTestNet as jest.Mock).mockReturnValue(true); + + const result = selectConversionRateByChainId.resultFunc( + mockCurrencyRateState.currencyRates as unknown as CurrencyRateState['currencyRates'], + mockChainId as `0x${string}`, + false, + mockNativeCurrency, + ); + + expect(result).toBeUndefined(); + }); + + it('returns the conversion rate for the native currency of the chain id', () => { + (isTestNet as jest.Mock).mockReturnValue(false); + + const result = selectConversionRateByChainId.resultFunc( + mockCurrencyRateState.currencyRates as unknown as CurrencyRateState['currencyRates'], + mockChainId as `0x${string}`, + true, + mockNativeCurrency, + ); + + expect(result).toBe(3000); + }); + }); + describe('selectCurrentCurrency', () => { it('returns the current currency from the state', () => { const result = selectCurrentCurrency.resultFunc( diff --git a/app/selectors/currencyRateController.ts b/app/selectors/currencyRateController.ts index 715ebeb4e8b..296cf3eedd5 100644 --- a/app/selectors/currencyRateController.ts +++ b/app/selectors/currencyRateController.ts @@ -1,7 +1,11 @@ import { createSelector } from 'reselect'; import { CurrencyRateState } from '@metamask/assets-controllers'; import { RootState } from '../reducers'; -import { selectChainId, selectTicker } from './networkController'; +import { + selectChainId, + selectNativeCurrencyByChainId, + selectTicker, +} from './networkController'; import { isTestNet } from '../../app/util/networks'; const selectCurrencyRateControllerState = (state: RootState) => @@ -56,3 +60,22 @@ export const selectConversionRateFoAllChains = createSelector( (currencyRateControllerState: CurrencyRateState) => currencyRateControllerState?.currencyRates, ); + +export const selectConversionRateByChainId = createSelector( + selectConversionRateFoAllChains, + (_state: RootState, chainId: string) => chainId, + (state: RootState) => state.settings.showFiatOnTestnets, + selectNativeCurrencyByChainId, + ( + currencyRates: CurrencyRateState['currencyRates'], + chainId, + showFiatOnTestnets, + nativeCurrency, + ) => { + if (isTestNet(chainId) && !showFiatOnTestnets) { + return undefined; + } + + return currencyRates?.[nativeCurrency]?.conversionRate; + }, +); diff --git a/app/util/number/index.js b/app/util/number/index.js index a6bd1e1aaed..f84cc95c189 100644 --- a/app/util/number/index.js +++ b/app/util/number/index.js @@ -352,7 +352,7 @@ export function isBN(value) { /** * Determines if a string is a valid decimal * - * @param {string} value - String to check + * @param {number | string} value - String to check * @returns {boolean} - True if the string is a valid decimal */ export function isDecimal(value) { @@ -383,6 +383,22 @@ export function isNumber(str) { return regex.number.test(str); } +/** + * Determines if a value is a number + * + * @param {number | string | null | undefined} value - Value to check + * @returns {boolean} - True if the value is a valid number + */ +export function isNumberValue(value) { + if (value === null || value === undefined) { return false; } + + if (typeof value === 'number') { + return !Number.isNaN(value) && Number.isFinite(value); + } + + return isDecimal(value); +} + export const dotAndCommaDecimalFormatter = (value) => { const valueStr = String(value); diff --git a/app/util/number/index.test.ts b/app/util/number/index.test.ts index 436f18c0c29..5849713f223 100644 --- a/app/util/number/index.test.ts +++ b/app/util/number/index.test.ts @@ -20,6 +20,7 @@ import { isBN, isDecimal, isNumber, + isNumberValue, isNumberScientificNotationWhenString, isZeroValue, limitToMaximumDecimalPlaces, @@ -925,6 +926,42 @@ describe('Number utils :: isNumber', () => { }); }); +describe('Number utils :: isNumberValue', () => { + it('should return true for valid number types', () => { + expect(isNumberValue(1650.7)).toBe(true); + expect(isNumberValue(1000)).toBe(true); + expect(isNumberValue(0.0001)).toBe(true); + expect(isNumberValue(-0.0001)).toBe(true); + expect(isNumberValue(1)).toBe(true); + expect(isNumberValue(1e-10)).toBe(true); + }); + + it('should be a valid number string types', () => { + expect(isNumberValue('1650.7')).toBe(true); + expect(isNumberValue('1000')).toBe(true); + expect(isNumberValue('.01')).toBe(true); + expect(isNumberValue('0.0001')).toBe(true); + expect(isNumberValue('0001')).toBe(true); + expect(isNumberValue('-0.0001')).toBe(true); + expect(isNumberValue('1')).toBe(true); + expect(isNumberValue('1e-10')).toBe(true); + }); + + it('should not be a valid number ', () => { + expect(isNumberValue('..7')).toBe(false); + expect(isNumberValue('1..1')).toBe(false); + expect(isNumberValue('0..')).toBe(false); + expect(isNumberValue('a.0001')).toBe(false); + expect(isNumberValue('00a01')).toBe(false); + expect(isNumberValue('1,.')).toBe(false); + expect(isNumberValue('1,')).toBe(false); + expect(isNumberValue('.')).toBe(false); + expect(isNumberValue('a¡1')).toBe(false); + expect(isNumberValue(undefined)).toBe(false); + expect(isNumberValue(null)).toBe(false); + }); +}); + describe('Number utils :: dotAndCommaDecimalFormatter', () => { it('should return the number if it does not contain a dot or comma', () => { expect(dotAndCommaDecimalFormatter('1650')).toBe('1650'); diff --git a/e2e/api-mocking/mock-config/mock-events.js b/e2e/api-mocking/mock-config/mock-events.js index 0e3423416b1..755fe4d39da 100644 --- a/e2e/api-mocking/mock-config/mock-events.js +++ b/e2e/api-mocking/mock-config/mock-events.js @@ -38,21 +38,37 @@ export const mockEvents = { securityAlertApiSupportedChains: { urlEndpoint: 'https://security-alerts.api.cx.metamask.io/supportedChains', response: [ - '0xa4b1', - '0xa86a', - '0x2105', - '0x138d5', - '0x38', - '0xe708', - '0x1', - '0x1b6e6', - '0xcc', - '0xa', - '0x89', - '0x82750', - '0xaa36a7', - '0x144' - ], + '0xa4b1', + '0xa86a', + '0x2105', + '0x138d5', + '0x38', + '0xe708', + '0x1', + '0x1b6e6', + '0xcc', + '0xa', + '0x89', + '0x82750', + '0xaa36a7', + '0x144', + ], + responseCode: 200, + }, + + remoteFeatureFlags: { + urlEndpoint: + 'https://client-config.api.cx.metamask.io/v1/flags?client=mobile&distribution=main&environment=dev', + response: [ + { + mobileMinimumVersions: { + appMinimumBuild: 1243, + appleMinimumOS: 6, + androidMinimumAPIVersion: 21, + }, + }, + { confirmation_redesign: { signatures: false } }, + ], responseCode: 200, }, }, @@ -77,7 +93,8 @@ export const mockEvents = { }, securityAlertApiValidate: { - urlEndpoint: 'https://security-alerts.api.cx.metamask.io/validate/0xaa36a7', + urlEndpoint: + 'https://security-alerts.api.cx.metamask.io/validate/0xaa36a7', response: { block: 20733513, result_type: 'Benign', @@ -93,9 +110,9 @@ export const mockEvents = { { from: '0x76cf1cdd1fcc252442b50d6e97207228aa4aefc3', to: '0x50587e46c5b96a3f6f9792922ec647f13e6efae4', - value: '0x0' - } - ] + value: '0x0', + }, + ], }, responseCode: 201, }, diff --git a/e2e/api-specs/json-rpc-coverage.js b/e2e/api-specs/json-rpc-coverage.js index ca5b30f828a..bcc75bc049c 100644 --- a/e2e/api-specs/json-rpc-coverage.js +++ b/e2e/api-specs/json-rpc-coverage.js @@ -22,6 +22,7 @@ import ConfirmationsRejectRule from './ConfirmationsRejectionRule'; import { createDriverTransport } from './helpers'; import { BrowserViewSelectorsIDs } from '../selectors/Browser/BrowserView.selectors'; import { getGanachePort } from '../fixtures/utils'; +import { mockEvents } from '../api-mocking/mock-config/mock-events'; const port = getGanachePort(8545, process.pid); const chainId = 1337; @@ -156,6 +157,10 @@ const main = async () => { const server = mockServer(port, openrpcDocument); server.start(); + const testSpecificMock = { + GET: [mockEvents.GET.remoteFeatureFlags], + }; + await withFixtures( { dapp: true, @@ -163,6 +168,7 @@ const main = async () => { ganacheOptions: defaultGanacheOptions, disableGanache: true, restartDevice: true, + testSpecificMock, }, async () => { await loginToApp(); diff --git a/e2e/specs/confirmations/signatures/ethereum-sign.spec.js b/e2e/specs/confirmations/signatures/ethereum-sign.spec.js index 4be24f172ca..b51be2c81b6 100644 --- a/e2e/specs/confirmations/signatures/ethereum-sign.spec.js +++ b/e2e/specs/confirmations/signatures/ethereum-sign.spec.js @@ -12,6 +12,7 @@ import { import { SmokeConfirmations } from '../../../tags'; import TestHelpers from '../../../helpers'; import Assertions from '../../../utils/Assertions'; +import { mockEvents } from '../../../api-mocking/mock-config/mock-events'; describe(SmokeConfirmations('Ethereum Sign'), () => { beforeAll(async () => { @@ -20,6 +21,10 @@ describe(SmokeConfirmations('Ethereum Sign'), () => { }); it('Sign in with Ethereum', async () => { + const testSpecificMock = { + GET: [mockEvents.GET.remoteFeatureFlags], + }; + await withFixtures( { dapp: true, @@ -29,6 +34,7 @@ describe(SmokeConfirmations('Ethereum Sign'), () => { .build(), restartDevice: true, ganacheOptions: defaultGanacheOptions, + testSpecificMock, }, async () => { await loginToApp(); diff --git a/e2e/specs/confirmations/signatures/personal-sign.spec.js b/e2e/specs/confirmations/signatures/personal-sign.spec.js index 0e3acf0579c..4fc37496141 100644 --- a/e2e/specs/confirmations/signatures/personal-sign.spec.js +++ b/e2e/specs/confirmations/signatures/personal-sign.spec.js @@ -12,8 +12,13 @@ import { import { SmokeConfirmations } from '../../../tags'; import TestHelpers from '../../../helpers'; import Assertions from '../../../utils/Assertions'; +import { mockEvents } from '../../../api-mocking/mock-config/mock-events'; describe(SmokeConfirmations('Personal Sign'), () => { + const testSpecificMock = { + GET: [mockEvents.GET.remoteFeatureFlags], + }; + beforeAll(async () => { jest.setTimeout(2500000); await TestHelpers.reverseServerPort(); @@ -29,6 +34,7 @@ describe(SmokeConfirmations('Personal Sign'), () => { .build(), restartDevice: true, ganacheOptions: defaultGanacheOptions, + testSpecificMock, }, async () => { await loginToApp(); diff --git a/e2e/specs/confirmations/signatures/security-alert-signatures.mock.spec.js b/e2e/specs/confirmations/signatures/security-alert-signatures.mock.spec.js index ff4a19e0137..77f0f3b3721 100644 --- a/e2e/specs/confirmations/signatures/security-alert-signatures.mock.spec.js +++ b/e2e/specs/confirmations/signatures/security-alert-signatures.mock.spec.js @@ -60,7 +60,10 @@ describe(SmokeConfirmations('Security Alert API - Signature'), () => { it('should sign typed message', async () => { const testSpecificMock = { - GET: [mockEvents.GET.securityAlertApiSupportedChains], + GET: [ + mockEvents.GET.securityAlertApiSupportedChains, + mockEvents.GET.remoteFeatureFlags, + ], POST: [ { ...mockEvents.POST.securityAlertApiValidate, @@ -83,7 +86,10 @@ describe(SmokeConfirmations('Security Alert API - Signature'), () => { it('should show security alert for malicious request', async () => { const testSpecificMock = { - GET: [mockEvents.GET.securityAlertApiSupportedChains], + GET: [ + mockEvents.GET.securityAlertApiSupportedChains, + mockEvents.GET.remoteFeatureFlags, + ], POST: [ { ...mockEvents.POST.securityAlertApiValidate, @@ -108,6 +114,7 @@ describe(SmokeConfirmations('Security Alert API - Signature'), () => { const testSpecificMock = { GET: [ mockEvents.GET.securityAlertApiSupportedChains, + mockEvents.GET.remoteFeatureFlags, { urlEndpoint: 'https://static.cx.metamask.io/api/v1/confirmations/ppom/ppom_version.json', diff --git a/e2e/specs/confirmations/signatures/typed-sign-v3.spec.js b/e2e/specs/confirmations/signatures/typed-sign-v3.spec.js index 86d958aa561..26cf84334ae 100644 --- a/e2e/specs/confirmations/signatures/typed-sign-v3.spec.js +++ b/e2e/specs/confirmations/signatures/typed-sign-v3.spec.js @@ -12,8 +12,13 @@ import { import { SmokeConfirmations } from '../../../tags'; import TestHelpers from '../../../helpers'; import Assertions from '../../../utils/Assertions'; +import { mockEvents } from '../../../api-mocking/mock-config/mock-events'; describe(SmokeConfirmations('Typed Sign V3'), () => { + const testSpecificMock = { + GET: [mockEvents.GET.remoteFeatureFlags], + }; + beforeAll(async () => { jest.setTimeout(2500000); await TestHelpers.reverseServerPort(); @@ -29,6 +34,7 @@ describe(SmokeConfirmations('Typed Sign V3'), () => { .build(), restartDevice: true, ganacheOptions: defaultGanacheOptions, + testSpecificMock, }, async () => { await loginToApp(); diff --git a/e2e/specs/confirmations/signatures/typed-sign-v4.spec.js b/e2e/specs/confirmations/signatures/typed-sign-v4.spec.js index 4de3dd9b006..c82b6e7a17b 100644 --- a/e2e/specs/confirmations/signatures/typed-sign-v4.spec.js +++ b/e2e/specs/confirmations/signatures/typed-sign-v4.spec.js @@ -12,8 +12,13 @@ import { import { SmokeConfirmations } from '../../../tags'; import TestHelpers from '../../../helpers'; import Assertions from '../../../utils/Assertions'; +import { mockEvents } from '../../../api-mocking/mock-config/mock-events'; describe(SmokeConfirmations('Typed Sign V4'), () => { + const testSpecificMock = { + GET: [mockEvents.GET.remoteFeatureFlags], + }; + beforeAll(async () => { jest.setTimeout(2500000); await TestHelpers.reverseServerPort(); @@ -29,6 +34,7 @@ describe(SmokeConfirmations('Typed Sign V4'), () => { .build(), restartDevice: true, ganacheOptions: defaultGanacheOptions, + testSpecificMock, }, async () => { await loginToApp(); diff --git a/e2e/specs/confirmations/signatures/typed-sign.spec.js b/e2e/specs/confirmations/signatures/typed-sign.spec.js index bee5b2a5e8a..c6b9cfed353 100644 --- a/e2e/specs/confirmations/signatures/typed-sign.spec.js +++ b/e2e/specs/confirmations/signatures/typed-sign.spec.js @@ -12,8 +12,13 @@ import { import { SmokeConfirmations } from '../../../tags'; import TestHelpers from '../../../helpers'; import Assertions from '../../../utils/Assertions'; +import { mockEvents } from '../../../api-mocking/mock-config/mock-events'; describe(SmokeConfirmations('Typed Sign'), () => { + const testSpecificMock = { + GET: [mockEvents.GET.remoteFeatureFlags], + }; + beforeAll(async () => { jest.setTimeout(2500000); await TestHelpers.reverseServerPort(); @@ -29,6 +34,7 @@ describe(SmokeConfirmations('Typed Sign'), () => { .build(), restartDevice: true, ganacheOptions: defaultGanacheOptions, + testSpecificMock, }, async () => { await loginToApp(); diff --git a/jest.config.js b/jest.config.js index bbc092b33f5..d54cc45b1f8 100644 --- a/jest.config.js +++ b/jest.config.js @@ -9,8 +9,6 @@ process.env.MM_SECURITY_ALERTS_API_ENABLED = 'true'; process.env.PORTFOLIO_VIEW = 'true'; process.env.SECURITY_ALERTS_API_URL = 'https://example.com'; -process.env.REDESIGNED_SIGNATURE_REQUEST = 'true'; - process.env.LAUNCH_DARKLY_URL = 'https://client-config.dev-api.cx.metamask.io/v1'; diff --git a/locales/languages/en.json b/locales/languages/en.json index 861594b3a49..666c154faf3 100644 --- a/locales/languages/en.json +++ b/locales/languages/en.json @@ -3607,13 +3607,23 @@ "balance": "Balance", "network": "Network", "simulation": { + "decoded_tooltip_bid_nft": "The NFT will be reflected in your wallet, when the bid is accepted.", + "decoded_tooltip_list_nft": "Expect changes only if someone buys your NFTs.", "info_permit": "You’re giving the spender permission to spend this many tokens from your account.", + "label_change_type_bidding": "You bid", + "label_change_type_listing": "You list", + "label_change_type_nft_listing": "Listing price", "label_change_type_permit": "Spending cap", "label_change_type_permit_nft": "Withdraw", + "label_change_type_receive": "You receive", + "label_change_type_revoke": "Revoke", + "label_change_type_transfer": "You send", "personal_sign_info": "You’re signing into a site and there are no predicted changes to your account.", "title": "Estimated changes", - "tooltip": "Estimated changes are what might happen if you go through with this transaction. This is just a prediction, not a guarantee." - } + "tooltip": "Estimated changes are what might happen if you go through with this transaction. This is just a prediction, not a guarantee.", + "unavailable": "Unavailable" + }, + "unlimited": "Unlimited" }, "change_in_simulation_modal": { "title": "Results have changed",