From 87b746ee417908e254a7c8aaed448ae13077b9eb Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Fri, 6 Dec 2024 21:53:48 +0700 Subject: [PATCH 01/77] feat: Permit Simulation todo: - metrics - precision tooltip - tests - styles: - right align row values - hide --- app/components/UI/Name/Name.tsx | 12 +- app/components/UI/Name/Name.types.ts | 3 +- app/components/UI/Ramp/components/Box.tsx | 6 + app/components/UI/SimulationDetails/types.ts | 11 ++ .../PermitSimulation/PermitSimulation.tsx | 115 ++++++++++++++ .../ValueDisplay/ValueDisplay.styles.ts | 55 +++++++ .../ValueDisplay/ValueDisplay.tsx | 148 ++++++++++++++++++ .../Info/TypedSign/PermitSimulation/index.ts | 1 + .../UI/InfoRow/InfoValue/Address/Address.tsx | 11 +- .../confirmations/constants/signatures.ts | 36 +++++ .../hooks/useGetTokenStandardAndDetails.ts | 45 ++++++ .../Views/confirmations/utils/signature.ts | 43 +++++ .../Views/confirmations/utils/token.ts | 89 +++++++++++ app/util/notifications/methods/common.ts | 15 +- locales/languages/en.json | 5 +- 15 files changed, 581 insertions(+), 14 deletions(-) create mode 100644 app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/PermitSimulation.tsx create mode 100644 app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.styles.ts create mode 100644 app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.tsx create mode 100644 app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/index.ts create mode 100644 app/components/Views/confirmations/constants/signatures.ts create mode 100644 app/components/Views/confirmations/hooks/useGetTokenStandardAndDetails.ts create mode 100644 app/components/Views/confirmations/utils/signature.ts create mode 100644 app/components/Views/confirmations/utils/token.ts diff --git a/app/components/UI/Name/Name.tsx b/app/components/UI/Name/Name.tsx index 9c554d8759a..bddd22806d4 100644 --- a/app/components/UI/Name/Name.tsx +++ b/app/components/UI/Name/Name.tsx @@ -1,6 +1,6 @@ /* eslint-disable react/prop-types */ import React from 'react'; -import { TextProps, View } from 'react-native'; +import { TextProps, View, ViewStyle } from 'react-native'; import { useStyles } from '../../../component-library/hooks'; import Text, { @@ -34,11 +34,12 @@ const NameLabel: React.FC<{ ); }; -const UnknownEthereumAddress: React.FC<{ address: string }> = ({ address }) => { +const UnknownEthereumAddress: React.FC<{ address: string, style?: ViewStyle }> = ({ address, style }) => { const displayNameVariant = DisplayNameVariant.Unknown; const { styles } = useStyles(styleSheet, { displayNameVariant }); + return ( - + {renderShortAddress(address, 5)} @@ -52,6 +53,7 @@ const Name: React.FC = ({ type, value, variation, + style, }) => { if (type !== NameType.EthereumAddress) { throw new Error('Unsupported NameType: ' + type); @@ -69,11 +71,11 @@ const Name: React.FC = ({ }); if (variant === DisplayNameVariant.Unknown) { - return ; + return ; } return ( - + label: { marginVertical: 8, }, + noBorder: { + borderWidth: 0, + }, highlighted: { borderColor: colors.primary.default, }, @@ -38,6 +41,7 @@ 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; @@ -56,6 +60,7 @@ const Box: React.FC = ({ accessible, accessibilityLabel, compact, + noBorder, ...props }: Props) => { const { colors } = useTheme(); @@ -77,6 +82,7 @@ const Box: React.FC = ({ thin && styles.thin, highlighted && styles.highlighted, compact && styles.compact, + noBorder && styles.noBorder, style, ]} {...props} diff --git a/app/components/UI/SimulationDetails/types.ts b/app/components/UI/SimulationDetails/types.ts index 9b87ad84af3..af921e25f8b 100644 --- a/app/components/UI/SimulationDetails/types.ts +++ b/app/components/UI/SimulationDetails/types.ts @@ -41,6 +41,17 @@ export type AssetIdentifier = Readonly< NativeAssetIdentifier | TokenAssetIdentifier >; +export enum TokenStandard { + /** A token that conforms to the ERC20 standard. */ + ERC20 = 'ERC20', + /** A token that conforms to the ERC721 standard. */ + ERC721 = 'ERC721', + /** A token that conforms to the ERC1155 standard. */ + ERC1155 = 'ERC1155', + /** Not a token, but rather the base asset of the selected chain. */ + none = 'NONE', +} + /** * Describes a change in an asset's balance to a user's wallet. */ diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/PermitSimulation.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/PermitSimulation.tsx new file mode 100644 index 00000000000..17057902e71 --- /dev/null +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/PermitSimulation.tsx @@ -0,0 +1,115 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; +import { StyleSheet, View } from 'react-native'; + +import { strings } from '../../../../../../../../../locales/i18n'; +import { useStyles } from '../../../../../../../../component-library/hooks'; +import { selectChainId } from '../../../../../../../../selectors/networkController'; +import { safeToChecksumAddress } from '../../../../../../../../util/address'; +import { PrimaryType } from '../../../../../constants/signatures'; +import useApprovalRequest from '../../../../../hooks/useApprovalRequest'; +import { parseTypedDataMessage } from '../../../../../utils/signature'; +import InfoRow from '../../../../UI/InfoRow'; +import InfoSection from '../../../../UI/InfoRow/InfoSection'; +import PermitSimulationValueDisplay from './ValueDisplay/ValueDisplay'; + +const styleSheet = () => + StyleSheet.create({ + permitValues: { + display: 'flex', + flexDirection: 'column', + gap: 2, + }, + }); + +function extractTokenDetailsByPrimaryType( + message: Record, + primaryType: PrimaryType, +): object[] | unknown { + let tokenDetails; + + switch (primaryType) { + case PrimaryType.PermitBatch: + case PrimaryType.PermitSingle: + tokenDetails = message?.details; + break; + case PrimaryType.PermitBatchTransferFrom: + case PrimaryType.PermitTransferFrom: + tokenDetails = message?.permitted; + break; + default: + break; + } + + const isNonArrayObject = tokenDetails && !Array.isArray(tokenDetails); + return isNonArrayObject ? [tokenDetails] : tokenDetails; +} + +// Permit Simulation +const Simulation = () => { + const { styles } = useStyles(styleSheet, {}); + + const { approvalRequest } = useApprovalRequest(); + + // TODO: confirm we want selected chainID, not one associated through confirmation itself + const chainId = useSelector(selectChainId); + + const msgData = approvalRequest?.requestData?.data; + + if (!msgData) { + return null; + } + + const { + domain: { verifyingContract }, + message, + message: { tokenId }, + primaryType, + } = parseTypedDataMessage(msgData as string); + + const tokenDetails = extractTokenDetailsByPrimaryType(message, primaryType); + + const isNFT = tokenId !== undefined; + const labelChangeType = isNFT ? strings('confirm.simulation.subtitle_permit') : strings('confirm.simulation.subtitle_permit_nft'); + + return ( + + + {strings('confirm.simulation.info_permit')} + + + + {Array.isArray(tokenDetails) ? ( + + {tokenDetails.map( + ( + { token, amount }: { token: string; amount: string }, + i: number, + ) => ( + + ), + )} + + ) : ( + + )} + + + ); +}; + +export default Simulation; diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.styles.ts b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.styles.ts new file mode 100644 index 00000000000..75822f0a69b --- /dev/null +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.styles.ts @@ -0,0 +1,55 @@ +import { StyleSheet } from 'react-native'; +import { Theme } from '@metamask/design-tokens'; +import { colors as importedColors } from '../../../../../../../../../styles/common'; + +const styleSheet = (colors: Theme['colors']) => + StyleSheet.create({ + wrapper: { + marginLeft: 'auto', + maxWidth: '100%', + alignSelf: 'flex-end', + justifyContent: 'flex-end', + borderWidth: 0, + padding: 0, + }, + flexRowTokenValueAndAddress: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'flex-end', + borderColor: importedColors.transparent, + borderWidth: 0, + padding: 0, + }, + tokenAddress: { + marginStart: 4, + }, + tokenValueTooltipContent: { + borderRadius: 12, + paddingHorizontal: 8, + paddingTop: 4, + paddingBottom: 4, + textAlign: 'center', + }, + valueAndAddress: { + backgroundColor: colors.background.alternative, + color: colors.text.default, + borderRadius: 99, + paddingVertical: 4, + paddingLeft: 8, + paddingRight: 8, + gap: 5, + flexDirection: 'row', + alignItems: 'center', + alignSelf: 'center', + }, + valueIsCredit: { + backgroundColor: colors.success.muted, + color: colors.success.default, + }, + valueIsDebit: { + backgroundColor: colors.error.muted, + color: colors.error.default, + }, + }); + +export default styleSheet; diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.tsx new file mode 100644 index 00000000000..06877bd71d5 --- /dev/null +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.tsx @@ -0,0 +1,148 @@ +import React, { useMemo } from 'react'; +import { View } from 'react-native'; +import { useSelector } from 'react-redux'; +import { Hex } from '@metamask/utils'; + +// TODO: add metrics +// import { MetaMetricsEventLocation } from '../../../../../../../../../shared/constants/metametrics'; +import { IndividualFiatDisplay } from '../../../../../../../../UI/SimulationDetails/FiatDisplay/FiatDisplay'; +import { + formatAmount, + formatAmountMaxPrecision, +} from '../../../../../../../../UI/SimulationDetails/formatAmount'; +import { useGetTokenStandardAndDetails } from '../../../../../../hooks/useGetTokenStandardAndDetails'; +// import useTrackERC20WithoutDecimalInformation from '../../../../../../hooks/useTrackERC20WithoutDecimalInformation'; + +import Box from '../../../../../../../../UI/Ramp/components/Box'; +import Address from '../../../../../UI/InfoRow/InfoValue/Address/Address'; +import Text from '../../../../../../../../../component-library/components/Texts/Text'; + +import { selectContractExchangeRates } from '../../../../../../../../../selectors/tokenRatesController'; + +import Logger from '../../../../../../../../../util/Logger'; +import { shortenString } from '../../../../../../../../../util/notifications/methods/common'; +import { calcTokenAmount } from '../../../../../../../../../util/transactions'; + +import { useTheme } from '../../../../../../../../../util/theme'; +import styleSheet from './ValueDisplay.styles'; + +interface PermitSimulationValueDisplayParams { + /** ID of the associated chain. */ + chainId: Hex; + + /** + * The ethereum token contract address. It is expected to be in hex format. + * We currently accept strings since we have a patch that accepts a custom string + * {@see .yarn/patches/@metamask-eth-json-rpc-middleware-npm-14.0.1-b6c2ccbe8c.patch} + */ + tokenContract: Hex | string | undefined; + + // Optional + + /** True if value is being credited to wallet */ + credit?: boolean; + + /** True if value is being debited to wallet */ + debit?: boolean; + + /** The primaryType of the typed sign message */ + primaryType?: string; + + /** The tokenId for NFT */ + tokenId?: string; + + /** The token amount */ + value?: number | string; +} + + +const PermitSimulationValueDisplay: React.FC< + PermitSimulationValueDisplayParams +> = ({ + chainId, + primaryType, + tokenContract, + tokenId, + value, + credit, + debit, +}) => { + const { colors } = useTheme(); + const styles = styleSheet(colors); + + const contractExchangeRates = useSelector(selectContractExchangeRates); + const exchangeRate = + tokenContract && contractExchangeRates + ? contractExchangeRates[tokenContract as `0x${string}`]?.price + : undefined; + + const tokenDetails = useGetTokenStandardAndDetails(tokenContract); + // TODO + // useTrackERC20WithoutDecimalInformation( + // chainId, + // tokenContract, + // tokenDetails as TokenDetailsERC20, + // MetaMetricsEventLocation.SignatureConfirmation, + // ); + const { decimalsNumber: tokenDecimals } = tokenDetails; + + const fiatValue = useMemo(() => { + if (exchangeRate && value && !tokenId) { + const tokenAmount = calcTokenAmount(value, tokenDecimals); + return tokenAmount.multipliedBy(exchangeRate).toNumber(); + } + return undefined; + }, [exchangeRate, tokenDecimals, tokenId, value]); + + const { tokenValue, tokenValueMaxPrecision } = useMemo(() => { + if (!value || tokenId) { + return { tokenValue: null, tokenValueMaxPrecision: null }; + } + + const tokenAmount = calcTokenAmount(value, tokenDecimals); + + return { + tokenValue: formatAmount('en-US', tokenAmount), + tokenValueMaxPrecision: formatAmountMaxPrecision('en-US', tokenAmount), + }; + }, [tokenDecimals, tokenId, value]); + + /** Temporary error capturing as we are building out Permit Simulations */ + if (!tokenContract) { + Logger.error( + new Error( + `PermitSimulationValueDisplay: Token contract address is missing where primaryType === ${primaryType}`, + ), + ); + return null; + } + + return ( + + + {/* TODO: add tooltip + precision */} + + + {credit && '+ '} + {debit && '- '} + {tokenValue !== null && + shortenString(tokenValue || '', { + truncatedCharLimit: 15, + truncatedStartChars: 15, + truncatedEndChars: 0, + skipCharacterInEnd: true, + })} + {tokenId && `#${tokenId}`} + + +
+ + + {/* TODO - add fiat shorten prop */} + {fiatValue && } + + + ); +}; + +export default PermitSimulationValueDisplay; diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/index.ts b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/index.ts new file mode 100644 index 00000000000..43538766e64 --- /dev/null +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/index.ts @@ -0,0 +1 @@ +export { default } from './PermitSimulation'; diff --git a/app/components/Views/confirmations/components/UI/InfoRow/InfoValue/Address/Address.tsx b/app/components/Views/confirmations/components/UI/InfoRow/InfoValue/Address/Address.tsx index 218e2b0dcf5..47e096a247c 100644 --- a/app/components/Views/confirmations/components/UI/InfoRow/InfoValue/Address/Address.tsx +++ b/app/components/Views/confirmations/components/UI/InfoRow/InfoValue/Address/Address.tsx @@ -2,14 +2,21 @@ import React from 'react'; import Name from '../../../../../../../UI/Name'; import { NameType } from '../../../../../../../UI/Name/Name.types'; +import { ViewStyle } from 'react-native'; interface AddressProps { address: string; chainId: string; + style?: ViewStyle; } -const Address = ({ address, chainId }: AddressProps) => ( - +const Address = ({ address, chainId, style }: AddressProps) => ( + ); export default Address; diff --git a/app/components/Views/confirmations/constants/signatures.ts b/app/components/Views/confirmations/constants/signatures.ts new file mode 100644 index 00000000000..79a6dfdf184 --- /dev/null +++ b/app/components/Views/confirmations/constants/signatures.ts @@ -0,0 +1,36 @@ +/** + * The contents of this file have been taken verbatim from + * metamask-extension/shared/constants/signatures.ts + * + * If updating, please be mindful of this or delete this comment. + */ + +export enum PrimaryTypeOrder { + Order = 'Order', + OrderComponents = 'OrderComponents', +} + +export enum PrimaryTypePermit { + Permit = 'Permit', + PermitBatch = 'PermitBatch', + PermitBatchTransferFrom = 'PermitBatchTransferFrom', + PermitSingle = 'PermitSingle', + PermitTransferFrom = 'PermitTransferFrom', +} + +/** + * EIP-712 Permit PrimaryTypes + */ +export const PrimaryType = { + ...PrimaryTypeOrder, + ...PrimaryTypePermit, +} as const; + +// Create a type from the const object +export type PrimaryType = (typeof PrimaryType)[keyof typeof PrimaryType]; + +export const PRIMARY_TYPES_ORDER: PrimaryTypeOrder[] = + Object.values(PrimaryTypeOrder); +export const PRIMARY_TYPES_PERMIT: PrimaryTypePermit[] = + Object.values(PrimaryTypePermit); +export const PRIMARY_TYPES: PrimaryType[] = Object.values(PrimaryType); diff --git a/app/components/Views/confirmations/hooks/useGetTokenStandardAndDetails.ts b/app/components/Views/confirmations/hooks/useGetTokenStandardAndDetails.ts new file mode 100644 index 00000000000..f3ed356e283 --- /dev/null +++ b/app/components/Views/confirmations/hooks/useGetTokenStandardAndDetails.ts @@ -0,0 +1,45 @@ +import { Hex } from '@metamask/utils'; + +import { TokenStandard } from '../../../UI/SimulationDetails/types'; +import { useAsyncResult } from '../../../hooks/useAsyncResult'; +import { + ERC20_DEFAULT_DECIMALS, + parseTokenDetailDecimals, + memoizedGetTokenStandardAndDetails, + TokenDetailsERC20, +} from '../utils/token'; + +/** + * Returns token details for a given token contract + * + * @param tokenAddress + * @returns + */ +export const useGetTokenStandardAndDetails = ( + tokenAddress?: Hex | string | undefined, +) => { + const { value: details } = + useAsyncResult(async () => { + if (!tokenAddress) { + return Promise.resolve(null); + } + + return (await memoizedGetTokenStandardAndDetails( + tokenAddress, + )) as TokenDetailsERC20; + }, [tokenAddress]); + + if (!details) { + return { decimalsNumber: undefined }; + } + + const { decimals, standard } = details || {}; + + if (standard === TokenStandard.ERC20) { + const parsedDecimals = + parseTokenDetailDecimals(decimals) ?? ERC20_DEFAULT_DECIMALS; + details.decimalsNumber = parsedDecimals; + } + + return details; +}; diff --git a/app/components/Views/confirmations/utils/signature.ts b/app/components/Views/confirmations/utils/signature.ts new file mode 100644 index 00000000000..ef323273113 --- /dev/null +++ b/app/components/Views/confirmations/utils/signature.ts @@ -0,0 +1,43 @@ +/** + * The contents of this file have been taken verbatim from + * metamask-extension/shared/modules/transaction.utils.ts + * + * If updating, please be mindful of this or delete this comment. + */ + +const REGEX_MESSAGE_VALUE_LARGE = /"message"\s*:\s*\{[^}]*"value"\s*:\s*(\d{15,})/u; + +function extractLargeMessageValue(dataToParse: string): string | undefined { + if (typeof dataToParse !== 'string') { + return undefined; + } + return dataToParse.match(REGEX_MESSAGE_VALUE_LARGE)?.[1]; +} + +/** + * JSON.parse has a limitation which coerces values to scientific notation if numbers are greater than + * Number.MAX_SAFE_INTEGER. This can cause a loss in precision. + * + * Aside from precision concerns, if the value returned was a large number greater than 15 digits, + * e.g. 3.000123123123121e+26, passing the value to BigNumber will throw the error: + * Error: new BigNumber() number type has more than 15 significant digits + * + * Note that using JSON.parse reviver cannot help since the value will be coerced by the time it + * reaches the reviver function. + * + * This function has a workaround to extract the large value from the message and replace + * the message value with the string value. + * + * @param dataToParse + * @returns + */ +export const parseTypedDataMessage = (dataToParse: string) => { + const result = JSON.parse(dataToParse); + + const messageValue = extractLargeMessageValue(dataToParse); + if (result.message?.value) { + result.message.value = messageValue || String(result.message.value); + } + + return result; +}; diff --git a/app/components/Views/confirmations/utils/token.ts b/app/components/Views/confirmations/utils/token.ts new file mode 100644 index 00000000000..49fc1335fbb --- /dev/null +++ b/app/components/Views/confirmations/utils/token.ts @@ -0,0 +1,89 @@ +import { memoize } from 'lodash'; +import { Hex } from '@metamask/utils'; +import { AssetsContractController } from '@metamask/assets-controllers'; +import { getTokenDetails } from '../../../../util/address'; + +export type TokenDetailsERC20 = Awaited< + ReturnType< + ReturnType['getDetails'] + > +> & { decimalsNumber: number }; + +export type TokenDetailsERC721 = Awaited< + ReturnType< + ReturnType['getDetails'] + > +>; + +export type TokenDetailsERC1155 = Awaited< + ReturnType< + ReturnType['getDetails'] + > +>; + +export type TokenDetails = + | TokenDetailsERC20 + | TokenDetailsERC721 + | TokenDetailsERC1155; + +export const ERC20_DEFAULT_DECIMALS = 18; + +export const parseTokenDetailDecimals = ( + decStr?: string, +): number | undefined => { + if (!decStr) { + return undefined; + } + + for (const radix of [10, 16]) { + const parsedDec = parseInt(decStr, radix); + if (isFinite(parsedDec)) { + return parsedDec; + } + } + return undefined; +}; + +export const memoizedGetTokenStandardAndDetails = memoize( + async ( + tokenAddress?: Hex | string, + userAddress?: string, + tokenId?: string, + ): Promise> => { + try { + if (!tokenAddress) { + return {}; + } + + return (await getTokenDetails( + tokenAddress, + userAddress, + tokenId, + )) as TokenDetails; + } catch { + return {}; + } + }, +); + +/** + * Fetches the decimals for the given token address. + * + * @param address - The ethereum token contract address. It is expected to be in hex format. + * We currently accept strings since we have a patch that accepts a custom string + * {@see .yarn/patches/@metamask-eth-json-rpc-middleware-npm-14.0.1-b6c2ccbe8c.patch} + */ +export const fetchErc20Decimals = async ( + address: Hex | string, +): Promise => { + try { + const { decimals: decStr } = (await memoizedGetTokenStandardAndDetails( + address, + )) as TokenDetailsERC20; + const decimals = parseTokenDetailDecimals(decStr); + + return decimals ?? ERC20_DEFAULT_DECIMALS; + } catch { + return ERC20_DEFAULT_DECIMALS; + } +}; diff --git a/app/util/notifications/methods/common.ts b/app/util/notifications/methods/common.ts index fcbab88a089..8b05c90cd2d 100644 --- a/app/util/notifications/methods/common.ts +++ b/app/util/notifications/methods/common.ts @@ -300,20 +300,25 @@ export const TRUNCATED_ADDRESS_END_CHARS = 5; */ export function shortenString( stringToShorten = '', - { truncatedCharLimit, truncatedStartChars, truncatedEndChars } = { + { + truncatedCharLimit, + truncatedStartChars, + truncatedEndChars, + skipCharacterInEnd, + } = { truncatedCharLimit: TRUNCATED_NAME_CHAR_LIMIT, truncatedStartChars: TRUNCATED_ADDRESS_START_CHARS, truncatedEndChars: TRUNCATED_ADDRESS_END_CHARS, + skipCharacterInEnd: false, }, ) { if (stringToShorten.length < truncatedCharLimit) { return stringToShorten; } - return `${stringToShorten.slice( - 0, - truncatedStartChars, - )}...${stringToShorten.slice(-truncatedEndChars)}`; + return `${stringToShorten.slice(0, truncatedStartChars)}...${ + skipCharacterInEnd ? '' : stringToShorten.slice(-truncatedEndChars) + }`; } export const sortNotifications = ( diff --git a/locales/languages/en.json b/locales/languages/en.json index b92a0d71dcf..6ac67997f74 100644 --- a/locales/languages/en.json +++ b/locales/languages/en.json @@ -3602,8 +3602,11 @@ "network": "Network", "rpc_url": "RPC URL", "simulation": { - "title": "Estimated changes", + "info_permit": "You’re giving the spender permission to spend this many tokens from your account.", + "label_change_permit": "Spending cap", + "label_change_permit_nft": "Withdraw", "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." } }, From 60086e10b729246ee03a981540114ad7265da9c1 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Fri, 6 Dec 2024 21:54:48 +0700 Subject: [PATCH 02/77] refactor: PermitSimulation const name --- .../Info/TypedSign/PermitSimulation/PermitSimulation.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/PermitSimulation.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/PermitSimulation.tsx index 17057902e71..94edb8021c1 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/PermitSimulation.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/PermitSimulation.tsx @@ -45,8 +45,7 @@ function extractTokenDetailsByPrimaryType( return isNonArrayObject ? [tokenDetails] : tokenDetails; } -// Permit Simulation -const Simulation = () => { +const PermitSimulation = () => { const { styles } = useStyles(styleSheet, {}); const { approvalRequest } = useApprovalRequest(); @@ -112,4 +111,4 @@ const Simulation = () => { ); }; -export default Simulation; +export default PermitSimulation; From ae2a2cb99488838fdba981328485c5fb431a4ea7 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Mon, 9 Dec 2024 17:55:23 +0700 Subject: [PATCH 03/77] fix: label_change_type i18n --- .../PermitSimulation/PermitSimulation.tsx | 52 ++++++++++--------- locales/languages/en.json | 4 +- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/PermitSimulation.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/PermitSimulation.tsx index 94edb8021c1..3fc1a0b40fb 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/PermitSimulation.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/PermitSimulation.tsx @@ -69,7 +69,9 @@ const PermitSimulation = () => { const tokenDetails = extractTokenDetailsByPrimaryType(message, primaryType); const isNFT = tokenId !== undefined; - const labelChangeType = isNFT ? strings('confirm.simulation.subtitle_permit') : strings('confirm.simulation.subtitle_permit_nft'); + const labelChangeType = isNFT + ? strings('confirm.simulation.label_change_type_permit') + : strings('confirm.simulation.label_change_type_permit_nft'); return ( @@ -82,30 +84,30 @@ const PermitSimulation = () => { {Array.isArray(tokenDetails) ? ( - - {tokenDetails.map( - ( - { token, amount }: { token: string; amount: string }, - i: number, - ) => ( - - ), - )} - - ) : ( - - )} + + {tokenDetails.map( + ( + { token, amount }: { token: string; amount: string }, + i: number, + ) => ( + + ), + )} + + ) : ( + + )} ); diff --git a/locales/languages/en.json b/locales/languages/en.json index 6ac67997f74..8888291a5b4 100644 --- a/locales/languages/en.json +++ b/locales/languages/en.json @@ -3603,8 +3603,8 @@ "rpc_url": "RPC URL", "simulation": { "info_permit": "You’re giving the spender permission to spend this many tokens from your account.", - "label_change_permit": "Spending cap", - "label_change_permit_nft": "Withdraw", + "label_change_type_permit": "Spending cap", + "label_change_type_permit_nft": "Withdraw", "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." From 5b5d09421f8c5093f16a142b055e987daff6c9f8 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Mon, 9 Dec 2024 17:58:48 +0700 Subject: [PATCH 04/77] feat: add PermitSimulation metrics --- .../ValueDisplay/ValueDisplay.tsx | 19 ++++---- .../useTrackERC20WithoutDecimalInformation.ts | 48 +++++++++++++++++++ 2 files changed, 56 insertions(+), 11 deletions(-) create mode 100644 app/components/Views/confirmations/hooks/useTrackERC20WithoutDecimalInformation.ts diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.tsx index 06877bd71d5..c612aa2c55a 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.tsx @@ -3,15 +3,14 @@ import { View } from 'react-native'; import { useSelector } from 'react-redux'; import { Hex } from '@metamask/utils'; -// TODO: add metrics -// import { MetaMetricsEventLocation } from '../../../../../../../../../shared/constants/metametrics'; import { IndividualFiatDisplay } from '../../../../../../../../UI/SimulationDetails/FiatDisplay/FiatDisplay'; import { formatAmount, formatAmountMaxPrecision, } from '../../../../../../../../UI/SimulationDetails/formatAmount'; import { useGetTokenStandardAndDetails } from '../../../../../../hooks/useGetTokenStandardAndDetails'; -// import useTrackERC20WithoutDecimalInformation from '../../../../../../hooks/useTrackERC20WithoutDecimalInformation'; +import useTrackERC20WithoutDecimalInformation from '../../../../../../hooks/useTrackERC20WithoutDecimalInformation'; +import { TokenDetailsERC20 } from '../../../../../../utils/token'; import Box from '../../../../../../../../UI/Ramp/components/Box'; import Address from '../../../../../UI/InfoRow/InfoValue/Address/Address'; @@ -55,7 +54,6 @@ interface PermitSimulationValueDisplayParams { value?: number | string; } - const PermitSimulationValueDisplay: React.FC< PermitSimulationValueDisplayParams > = ({ @@ -77,15 +75,14 @@ const PermitSimulationValueDisplay: React.FC< : undefined; const tokenDetails = useGetTokenStandardAndDetails(tokenContract); - // TODO - // useTrackERC20WithoutDecimalInformation( - // chainId, - // tokenContract, - // tokenDetails as TokenDetailsERC20, - // MetaMetricsEventLocation.SignatureConfirmation, - // ); const { decimalsNumber: tokenDecimals } = tokenDetails; + useTrackERC20WithoutDecimalInformation( + chainId, + tokenContract, + tokenDetails as TokenDetailsERC20, + ); + const fiatValue = useMemo(() => { if (exchangeRate && value && !tokenId) { const tokenAmount = calcTokenAmount(value, tokenDecimals); diff --git a/app/components/Views/confirmations/hooks/useTrackERC20WithoutDecimalInformation.ts b/app/components/Views/confirmations/hooks/useTrackERC20WithoutDecimalInformation.ts new file mode 100644 index 00000000000..e941dd9e726 --- /dev/null +++ b/app/components/Views/confirmations/hooks/useTrackERC20WithoutDecimalInformation.ts @@ -0,0 +1,48 @@ +import { useEffect } from 'react'; +import { Hex } from '@metamask/utils'; + +import { MetaMetricsEvents } from '../../../../core/Analytics'; +import { TokenStandard } from '../../../../components/UI/SimulationDetails/types'; +import { useMetrics } from '../../../../components/hooks/useMetrics'; +import { parseTokenDetailDecimals, TokenDetailsERC20 } from '../utils/token'; + +/** + * Track event that number of decimals in ERC20 is not obtained + * + * @param chainId + * @param tokenAddress + * @param tokenDetails + * @param metricLocation + */ +const useTrackERC20WithoutDecimalInformation = ( + chainId: Hex, + tokenAddress: Hex | string | undefined, + tokenDetails?: TokenDetailsERC20, + metricLocation = 'signature_confirmation', +) => { + const { trackEvent, createEventBuilder } = useMetrics(); + + useEffect(() => { + if (chainId === undefined || tokenDetails === undefined) { + return; + } + const { decimals, standard } = tokenDetails || {}; + if (standard === TokenStandard.ERC20) { + const parsedDecimals = parseTokenDetailDecimals(decimals); + if (parsedDecimals === undefined) { + trackEvent(createEventBuilder(MetaMetricsEvents.INCOMPLETE_ASSET_DISPLAYED) + .addProperties({ + token_decimals_available: false, + asset_address: tokenAddress, + asset_type: TokenStandard.ERC20, + chain_id: chainId, + location: metricLocation, + ui_customizations: ['redesigned_confirmation'], + }) + .build()); + } + } + }, [tokenDetails, chainId, tokenAddress, trackEvent, createEventBuilder, metricLocation]); +}; + +export default useTrackERC20WithoutDecimalInformation; From dfc086f57ed1608b6243f4100de4c8f9e400df75 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Tue, 10 Dec 2024 20:49:28 +0700 Subject: [PATCH 05/77] feat: create ButtonPill component --- .../Buttons/ButtonPill/ButtonPill.styles.ts | 49 +++++++++++++ .../Buttons/ButtonPill/ButtonPill.test.tsx | 15 ++++ .../Buttons/ButtonPill/ButtonPill.tsx | 69 +++++++++++++++++++ .../__snapshots__/ButtonPill.test.tsx.snap | 24 +++++++ .../components/Buttons/ButtonPill/index.ts | 1 + 5 files changed, 158 insertions(+) create mode 100644 app/component-library/components/Buttons/ButtonPill/ButtonPill.styles.ts create mode 100644 app/component-library/components/Buttons/ButtonPill/ButtonPill.test.tsx create mode 100644 app/component-library/components/Buttons/ButtonPill/ButtonPill.tsx create mode 100644 app/component-library/components/Buttons/ButtonPill/__snapshots__/ButtonPill.test.tsx.snap create mode 100644 app/component-library/components/Buttons/ButtonPill/index.ts diff --git a/app/component-library/components/Buttons/ButtonPill/ButtonPill.styles.ts b/app/component-library/components/Buttons/ButtonPill/ButtonPill.styles.ts new file mode 100644 index 00000000000..c7ca8c1e8c9 --- /dev/null +++ b/app/component-library/components/Buttons/ButtonPill/ButtonPill.styles.ts @@ -0,0 +1,49 @@ +// Third party dependencies. +import { StyleSheet } from 'react-native'; + +// External dependencies. +import { Theme } from '../../../../util/theme/models'; + +/** + * Style sheet input parameters. + */ +export interface ButtonPillStyleSheetVars { + pressed: boolean; + isDisabled: boolean; +} + +/** + * Style sheet function for ButtonIcon component + * + * @param params Style sheet params + * @param params.theme Theme object + * @param params.vars Inputs that the style sheet depends on + * @returns StyleSheet object + */ +const styleSheet = (params: { + theme: Theme; + vars: ButtonPillStyleSheetVars; +}) => { + const { + theme: { colors }, + vars: { pressed, isDisabled } + } = params; + + return StyleSheet.create({ + base: { + backgroundColor: colors.background.alternative, + color: colors.text.default, + alignItems: 'center', + justifyContent: 'center', + paddingHorizontal: 8, + paddingVertical: 4, + borderRadius: 99, + opacity: isDisabled ? 0.5 : 1, + ...(pressed && { + backgroundColor: colors.background.alternativePressed, + }), + }, + }); +}; + +export default styleSheet; diff --git a/app/component-library/components/Buttons/ButtonPill/ButtonPill.test.tsx b/app/component-library/components/Buttons/ButtonPill/ButtonPill.test.tsx new file mode 100644 index 00000000000..41933943f63 --- /dev/null +++ b/app/component-library/components/Buttons/ButtonPill/ButtonPill.test.tsx @@ -0,0 +1,15 @@ +// Third party dependencies. +import React from 'react'; +import { render } from '@testing-library/react-native'; + +// Internal dependencies. +import ButtonPill from './ButtonPill'; + +describe('ButtonPill', () => { + it('should render correctly', () => { + const { toJSON } = render( + , + ); + expect(toJSON()).toMatchSnapshot(); + }); +}); diff --git a/app/component-library/components/Buttons/ButtonPill/ButtonPill.tsx b/app/component-library/components/Buttons/ButtonPill/ButtonPill.tsx new file mode 100644 index 00000000000..23af13ea938 --- /dev/null +++ b/app/component-library/components/Buttons/ButtonPill/ButtonPill.tsx @@ -0,0 +1,69 @@ +// Third party dependencies. +import React, { useCallback, useState } from 'react'; +import { GestureResponderEvent, TouchableOpacity, TouchableOpacityProps } from 'react-native'; + +// External dependencies. +import { useStyles } from '../../../hooks'; + +// Internal dependencies. +import stylesheet from './ButtonPill.styles'; + +/** + * ButtonPill component props. + */ +export interface ButtonPillProps extends TouchableOpacityProps { + /** + * Optional param to disable the button. + */ + isDisabled?: boolean; +} + +const ButtonPill = ({ + onPress, + onPressIn, + onPressOut, + style, + isDisabled = false, + children, + ...props +}: ButtonPillProps) => { + const [pressed, setPressed] = useState(false); + const { styles } = useStyles(stylesheet, { + style, + pressed, + isDisabled, + }); + + const triggerOnPressedIn = useCallback( + (e: GestureResponderEvent) => { + setPressed(true); + onPressIn?.(e); + }, + [setPressed, onPressIn], + ); + + const triggerOnPressedOut = useCallback( + (e: GestureResponderEvent) => { + setPressed(false); + onPressOut?.(e); + }, + [setPressed, onPressOut], + ); + + return ( + + {children} + + ); +}; + +export default ButtonPill; diff --git a/app/component-library/components/Buttons/ButtonPill/__snapshots__/ButtonPill.test.tsx.snap b/app/component-library/components/Buttons/ButtonPill/__snapshots__/ButtonPill.test.tsx.snap new file mode 100644 index 00000000000..c3e3b781a54 --- /dev/null +++ b/app/component-library/components/Buttons/ButtonPill/__snapshots__/ButtonPill.test.tsx.snap @@ -0,0 +1,24 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ButtonPill should render correctly 1`] = ` + +`; diff --git a/app/component-library/components/Buttons/ButtonPill/index.ts b/app/component-library/components/Buttons/ButtonPill/index.ts new file mode 100644 index 00000000000..d983c349bc5 --- /dev/null +++ b/app/component-library/components/Buttons/ButtonPill/index.ts @@ -0,0 +1 @@ +export { default } from './ButtonPill'; From d96f9aaec01f1e6c6254661cef9bd3efa28f2f53 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Thu, 12 Dec 2024 18:21:04 +0700 Subject: [PATCH 06/77] feat: PermitSimulation token value BottomModal --- .../Views/confirmations/Confirm/Confirm.tsx | 2 +- .../PermitSimulation/PermitSimulation.tsx | 4 +- .../ValueDisplay/ValueDisplay.styles.ts | 40 +++- .../ValueDisplay/ValueDisplay.tsx | 207 +++++++++++------- .../components/UI/BottomModal/BottomModal.tsx | 8 +- 5 files changed, 177 insertions(+), 84 deletions(-) diff --git a/app/components/Views/confirmations/Confirm/Confirm.tsx b/app/components/Views/confirmations/Confirm/Confirm.tsx index e1a06fb45b2..0ff0794bfc7 100644 --- a/app/components/Views/confirmations/Confirm/Confirm.tsx +++ b/app/components/Views/confirmations/Confirm/Confirm.tsx @@ -19,7 +19,7 @@ const Confirm = () => { } return ( - + diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/PermitSimulation.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/PermitSimulation.tsx index 3fc1a0b40fb..67ac491c3b6 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/PermitSimulation.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/PermitSimulation.tsx @@ -50,7 +50,7 @@ const PermitSimulation = () => { const { approvalRequest } = useApprovalRequest(); - // TODO: confirm we want selected chainID, not one associated through confirmation itself + // TODO: update logic to use chainId from approvalRequest. SignatureController needs to be updated prior const chainId = useSelector(selectChainId); const msgData = approvalRequest?.requestData?.data; @@ -92,6 +92,7 @@ const PermitSimulation = () => { ) => ( <PermitSimulationValueDisplay key={`${token}-${i}`} + labelChangeType={labelChangeType} primaryType={primaryType} tokenContract={safeToChecksumAddress(token)} value={amount} @@ -102,6 +103,7 @@ const PermitSimulation = () => { </View> ) : ( <PermitSimulationValueDisplay + labelChangeType={labelChangeType} tokenContract={verifyingContract} value={message.value} tokenId={message.tokenId} diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.styles.ts b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.styles.ts index 75822f0a69b..0ef2906ce48 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.styles.ts +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.styles.ts @@ -1,6 +1,6 @@ import { StyleSheet } from 'react-native'; import { Theme } from '@metamask/design-tokens'; -import { colors as importedColors } from '../../../../../../../../../styles/common'; +import { fontStyles, colors as importedColors } from '../../../../../../../../../styles/common'; const styleSheet = (colors: Theme['colors']) => StyleSheet.create({ @@ -31,9 +31,6 @@ const styleSheet = (colors: Theme['colors']) => textAlign: 'center', }, valueAndAddress: { - backgroundColor: colors.background.alternative, - color: colors.text.default, - borderRadius: 99, paddingVertical: 4, paddingLeft: 8, paddingRight: 8, @@ -50,6 +47,41 @@ const styleSheet = (colors: Theme['colors']) => backgroundColor: colors.error.muted, color: colors.error.default, }, + valueModal: { + backgroundColor: colors.background.alternative, + paddingTop: 24, + paddingBottom: 34, + paddingHorizontal: 16, + borderTopLeftRadius: 8, + borderTopRightRadius: 8, + }, + valueModalHeader: { + alignItems: 'center', + display: 'flex', + flexDirection: 'row', + paddingBottom: 16, + position: 'relative', + textAlign: 'center', + width: '100%', + }, + valueModalHeaderIcon: { + position: 'absolute', + top: 0, + left: 0, + }, + valueModalHeaderText: { + color: colors.text.default, + ...fontStyles.bold, + fontSize: 14, + fontWeight: '700', + textAlign: 'center', + width: '100%', + // height of header icon + minHeight: 24, + }, + valueModalText: { + textAlign: 'center', + }, }); export default styleSheet; diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.tsx index c612aa2c55a..26f9773d8ed 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.tsx @@ -1,34 +1,44 @@ -import React, { useMemo } from 'react'; -import { View } from 'react-native'; +import React, { useMemo, useState } from 'react'; +import { TouchableOpacity, View } from 'react-native'; import { useSelector } from 'react-redux'; import { Hex } from '@metamask/utils'; +import ButtonPill from '../../../../../../../../../component-library/components/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 Text from '../../../../../../../../../component-library/components/Texts/Text'; + import { IndividualFiatDisplay } from '../../../../../../../../UI/SimulationDetails/FiatDisplay/FiatDisplay'; import { formatAmount, formatAmountMaxPrecision, } from '../../../../../../../../UI/SimulationDetails/formatAmount'; -import { useGetTokenStandardAndDetails } from '../../../../../../hooks/useGetTokenStandardAndDetails'; -import useTrackERC20WithoutDecimalInformation from '../../../../../../hooks/useTrackERC20WithoutDecimalInformation'; -import { TokenDetailsERC20 } from '../../../../../../utils/token'; import Box from '../../../../../../../../UI/Ramp/components/Box'; import Address from '../../../../../UI/InfoRow/InfoValue/Address/Address'; -import Text from '../../../../../../../../../component-library/components/Texts/Text'; import { selectContractExchangeRates } from '../../../../../../../../../selectors/tokenRatesController'; import Logger from '../../../../../../../../../util/Logger'; import { shortenString } from '../../../../../../../../../util/notifications/methods/common'; +import { useTheme } from '../../../../../../../../../util/theme'; import { calcTokenAmount } from '../../../../../../../../../util/transactions'; -import { useTheme } from '../../../../../../../../../util/theme'; +import { useGetTokenStandardAndDetails } from '../../../../../../hooks/useGetTokenStandardAndDetails'; +import useTrackERC20WithoutDecimalInformation from '../../../../../../hooks/useTrackERC20WithoutDecimalInformation'; +import { TokenDetailsERC20 } from '../../../../../../utils/token'; +import BottomModal from '../../../../../UI/BottomModal'; + import styleSheet from './ValueDisplay.styles'; interface PermitSimulationValueDisplayParams { /** ID of the associated chain. */ chainId: Hex; + /** Change type to be displayed in value tooltip */ + labelChangeType: string; + /** * The ethereum token contract address. It is expected to be in hex format. * We currently accept strings since we have a patch that accepts a custom string @@ -58,6 +68,7 @@ const PermitSimulationValueDisplay: React.FC< PermitSimulationValueDisplayParams > = ({ chainId, + labelChangeType, primaryType, tokenContract, tokenId, @@ -65,81 +76,123 @@ const PermitSimulationValueDisplay: React.FC< credit, debit, }) => { - const { colors } = useTheme(); - const styles = styleSheet(colors); - - const contractExchangeRates = useSelector(selectContractExchangeRates); - const exchangeRate = - tokenContract && contractExchangeRates - ? contractExchangeRates[tokenContract as `0x${string}`]?.price - : undefined; - - const tokenDetails = useGetTokenStandardAndDetails(tokenContract); - const { decimalsNumber: tokenDecimals } = tokenDetails; - - useTrackERC20WithoutDecimalInformation( - chainId, - tokenContract, - 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 [hasValueModalOpen, setHasValueModalOpen] = useState(false); - const { tokenValue, tokenValueMaxPrecision } = useMemo(() => { - if (!value || tokenId) { - return { tokenValue: null, tokenValueMaxPrecision: null }; - } + const { colors } = useTheme(); + const styles = styleSheet(colors); - const tokenAmount = calcTokenAmount(value, tokenDecimals); + const contractExchangeRates = useSelector(selectContractExchangeRates); + const exchangeRate = + tokenContract && contractExchangeRates + ? contractExchangeRates[tokenContract as `0x${string}`]?.price + : undefined; - return { - tokenValue: formatAmount('en-US', tokenAmount), - tokenValueMaxPrecision: formatAmountMaxPrecision('en-US', tokenAmount), - }; - }, [tokenDecimals, tokenId, value]); + const tokenDetails = useGetTokenStandardAndDetails(tokenContract); + const { decimalsNumber: tokenDecimals } = tokenDetails; - /** Temporary error capturing as we are building out Permit Simulations */ - if (!tokenContract) { - Logger.error( - new Error( - `PermitSimulationValueDisplay: Token contract address is missing where primaryType === ${primaryType}`, - ), + useTrackERC20WithoutDecimalInformation( + chainId, + tokenContract, + tokenDetails as TokenDetailsERC20, ); - return null; - } - - return ( - <Box style={styles.wrapper}> - <Box style={styles.flexRowTokenValueAndAddress}> - {/* TODO: add tooltip + precision */} - <View style={styles.valueAndAddress}> - <Text style={[credit && styles.valueIsCredit, debit && styles.valueIsDebit]}> - {credit && '+ '} - {debit && '- '} - {tokenValue !== null && - shortenString(tokenValue || '', { - truncatedCharLimit: 15, - truncatedStartChars: 15, - truncatedEndChars: 0, - skipCharacterInEnd: true, - })} - {tokenId && `#${tokenId}`} - </Text> - </View> - <Address address={tokenContract} chainId={chainId} style={styles.tokenAddress} /> - </Box> - <Box compact noBorder> - {/* TODO - add fiat shorten prop */} - {fiatValue && <IndividualFiatDisplay fiatAmount={fiatValue} /* shorten*/ />} + + const fiatValue = useMemo(() => { + if (exchangeRate && value && !tokenId) { + const tokenAmount = calcTokenAmount(value, tokenDecimals); + return tokenAmount.multipliedBy(exchangeRate).toNumber(); + } + return undefined; + }, [exchangeRate, tokenDecimals, tokenId, value]); + + const { tokenValue, tokenValueMaxPrecision } = useMemo(() => { + if (!value || tokenId) { + return { tokenValue: null, tokenValueMaxPrecision: null }; + } + + const tokenAmount = calcTokenAmount(value, tokenDecimals); + + return { + tokenValue: formatAmount('en-US', tokenAmount), + tokenValueMaxPrecision: formatAmountMaxPrecision('en-US', tokenAmount), + }; + }, [tokenDecimals, tokenId, value]); + + /** Temporary error capturing as we are building out Permit Simulations */ + if (!tokenContract) { + Logger.error( + new Error( + `PermitSimulationValueDisplay: Token contract address is missing where primaryType === ${primaryType}`, + ), + ); + return null; + } + + function onPressTokenValue() { + setHasValueModalOpen(true); + } + + return ( + <Box style={styles.wrapper}> + <Box style={styles.flexRowTokenValueAndAddress}> + <View style={styles.valueAndAddress}> + <ButtonPill + onPress={onPressTokenValue} + onPressIn={onPressTokenValue} + onPressOut={onPressTokenValue} + style={[credit && styles.valueIsCredit, debit && styles.valueIsDebit]} + > + <Text> + {credit && '+ '} + {debit && '- '} + {tokenValue !== null && + shortenString(tokenValue || '', { + truncatedCharLimit: 15, + truncatedStartChars: 15, + truncatedEndChars: 0, + skipCharacterInEnd: true, + })} + {tokenId && `#${tokenId}`} + </Text> + </ButtonPill> + <Address address={tokenContract} chainId={chainId} style={styles.tokenAddress} /> + </View> + </Box> + <Box compact noBorder> + {/* TODO - add fiat shorten prop */} + {fiatValue && <IndividualFiatDisplay fiatAmount={fiatValue} /* shorten*/ />} + </Box> + {hasValueModalOpen && ( + /** + * TODO replace BottomModal instances with BottomSheet + * {@see {@link https://github.com/MetaMask/metamask-mobile/issues/12656}} + */ + <BottomModal onClose={() => setHasValueModalOpen(false)}> + <TouchableOpacity + activeOpacity={1} + onPress={() => setHasValueModalOpen(false)} + > + <View style={styles.valueModal} > + <View style={styles.valueModalHeader}> + <ButtonIcon + iconColor={IconColor.Default} + size={ButtonIconSizes.Sm} + style={styles.valueModalHeaderIcon} + onPress={() => setHasValueModalOpen(false)} + iconName={IconName.ArrowLeft} + /> + <Text style={styles.valueModalHeaderText}> + {labelChangeType} + </Text> + </View> + <Text style={styles.valueModalText}> + {tokenValueMaxPrecision} + </Text> + </View> + </TouchableOpacity> + </BottomModal> + )} </Box> - </Box> - ); -}; + ); + }; export default PermitSimulationValueDisplay; diff --git a/app/components/Views/confirmations/components/UI/BottomModal/BottomModal.tsx b/app/components/Views/confirmations/components/UI/BottomModal/BottomModal.tsx index c7b5e0ab7ab..c96b1ab0742 100644 --- a/app/components/Views/confirmations/components/UI/BottomModal/BottomModal.tsx +++ b/app/components/Views/confirmations/components/UI/BottomModal/BottomModal.tsx @@ -6,6 +6,7 @@ import { useTheme } from '../../../../../../util/theme'; const OPAQUE_GRAY = '#414141'; interface BottomModalProps { + canCloseOnBackdropClick?: boolean; children: ReactChild; onClose?: () => void; hideBackground?: boolean; @@ -18,7 +19,11 @@ const styles = StyleSheet.create({ }, }); -const BottomModal = ({ children, hideBackground, onClose }: BottomModalProps) => { +/** + * TODO replace BottomModal instances with BottomSheet + * {@see {@link https://github.com/MetaMask/metamask-mobile/issues/12656}} + */ +const BottomModal = ({ canCloseOnBackdropClick = true, children, hideBackground, onClose }: BottomModalProps) => { const { colors } = useTheme(); return ( @@ -32,6 +37,7 @@ const BottomModal = ({ children, hideBackground, onClose }: BottomModalProps) => animationInTiming={600} animationOutTiming={600} onBackButtonPress={onClose} + onBackdropPress={canCloseOnBackdropClick ? onClose : undefined} onSwipeComplete={onClose} swipeDirection={'down'} propagateSwipe From 518e51d5c1e1ab518941792f755550d9f4da40b1 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Mon, 16 Dec 2024 19:39:27 +0700 Subject: [PATCH 07/77] feat: Add Permit Simulation to TypedSignV3V4 redesign --- .../Confirm/Info/TypedSignV3V4/TypedSignV3V4.tsx | 12 ++++++++++-- .../Views/confirmations/utils/signature.ts | 14 +++++++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) 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 1fd23c2fbeb..89a953d71e5 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/TypedSignV3V4.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/TypedSignV3V4.tsx @@ -1,19 +1,27 @@ 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 '../TypedSign/PermitSimulation/PermitSimulation'; import Message from './Message'; const TypedSignV3V4 = () => { const { approvalRequest } = useApprovalRequest(); + const useSimulation = useSelector( + selectUseTransactionSimulations, + ); if (!approvalRequest) { return null; } + const isPermit = isRecognizedPermit(approvalRequest); + return ( <> - {/* SIMULATION TO BE ADDED */} + {isPermit && useSimulation && <PermitSimulation />} <InfoRowOrigin /> <Message /> </> diff --git a/app/components/Views/confirmations/utils/signature.ts b/app/components/Views/confirmations/utils/signature.ts index ef323273113..ca84d70d427 100644 --- a/app/components/Views/confirmations/utils/signature.ts +++ b/app/components/Views/confirmations/utils/signature.ts @@ -1,3 +1,6 @@ +import { ApprovalRequest } from '@metamask/approval-controller'; +import { PRIMARY_TYPES_PERMIT } from '../constants/signatures'; + /** * The contents of this file have been taken verbatim from * metamask-extension/shared/modules/transaction.utils.ts @@ -38,6 +41,15 @@ export const parseTypedDataMessage = (dataToParse: string) => { if (result.message?.value) { result.message.value = messageValue || String(result.message.value); } - return result; }; + +/** + * Returns true if the request is a recognized Permit Typed Sign signature request + * + * @param request - The confirmation request to check + */ +export const isRecognizedPermit = (approvalRequest: ApprovalRequest<{ data: string } & Record<string, any>>) => { + const { primaryType } = parseTypedDataMessage(approvalRequest?.requestData?.data); + return PRIMARY_TYPES_PERMIT.includes(primaryType); +}; From f069e37c990998cedb0c8042111dc64d06d5f99c Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Tue, 17 Dec 2024 15:52:57 +0700 Subject: [PATCH 08/77] refactor: cleanup --- .../components/Buttons/ButtonPill/ButtonPill.styles.ts | 4 ++-- .../Info/TypedSign/PermitSimulation/PermitSimulation.tsx | 2 +- .../PermitSimulation/ValueDisplay/ValueDisplay.styles.ts | 1 + .../Info/TypedSign/PermitSimulation/ValueDisplay/index.ts | 1 + 4 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/index.ts diff --git a/app/component-library/components/Buttons/ButtonPill/ButtonPill.styles.ts b/app/component-library/components/Buttons/ButtonPill/ButtonPill.styles.ts index c7ca8c1e8c9..8e55b9a27e3 100644 --- a/app/component-library/components/Buttons/ButtonPill/ButtonPill.styles.ts +++ b/app/component-library/components/Buttons/ButtonPill/ButtonPill.styles.ts @@ -13,11 +13,11 @@ export interface ButtonPillStyleSheetVars { } /** - * Style sheet function for ButtonIcon component + * Style sheet function for ButtonPill component * * @param params Style sheet params * @param params.theme Theme object - * @param params.vars Inputs that the style sheet depends on + * @param params.vars Arbitrary inputs this style sheet depends on * @returns StyleSheet object */ const styleSheet = (params: { diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/PermitSimulation.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/PermitSimulation.tsx index 67ac491c3b6..2f4a5c5d1c1 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/PermitSimulation.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/PermitSimulation.tsx @@ -11,7 +11,7 @@ import useApprovalRequest from '../../../../../hooks/useApprovalRequest'; import { parseTypedDataMessage } from '../../../../../utils/signature'; import InfoRow from '../../../../UI/InfoRow'; import InfoSection from '../../../../UI/InfoRow/InfoSection'; -import PermitSimulationValueDisplay from './ValueDisplay/ValueDisplay'; +import PermitSimulationValueDisplay from './ValueDisplay'; const styleSheet = () => StyleSheet.create({ diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.styles.ts b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.styles.ts index 0ef2906ce48..6ffd38f4ffc 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.styles.ts +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.styles.ts @@ -12,6 +12,7 @@ const styleSheet = (colors: Theme['colors']) => borderWidth: 0, padding: 0, }, + flexRowTokenValueAndAddress: { display: 'flex', flexDirection: 'row', diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/index.ts b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/index.ts new file mode 100644 index 00000000000..33cde05cb62 --- /dev/null +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/index.ts @@ -0,0 +1 @@ +export { default } from './ValueDisplay'; From fe15f12ada086dbc6f68f547f01fe3424bfcbcc4 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Tue, 17 Dec 2024 17:12:49 +0700 Subject: [PATCH 09/77] chore: update todo ValueDisplay shorten comment --- .../TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.tsx index 26f9773d8ed..72e6ad14801 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.tsx @@ -158,7 +158,10 @@ const PermitSimulationValueDisplay: React.FC< </View> </Box> <Box compact noBorder> - {/* TODO - add fiat shorten prop */} + {/* + TODO - add fiat shorten prop after tooltip logic has been updated + {@see {@link https://github.com/MetaMask/metamask-mobile/issues/12656} + */} {fiatValue && <IndividualFiatDisplay fiatAmount={fiatValue} /* shorten*/ />} </Box> {hasValueModalOpen && ( From 677731900f392c5cdfdf1f4ffa89daba91dd7092 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 18 Dec 2024 18:53:25 +0700 Subject: [PATCH 10/77] refactor: update useGetTokenStandardAndDetails import --- .../TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.tsx | 2 +- .../confirmations/hooks/useGetTokenStandardAndDetails.ts | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.tsx index 72e6ad14801..5b49fb5006f 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.tsx @@ -25,7 +25,7 @@ import { shortenString } from '../../../../../../../../../util/notifications/met import { useTheme } from '../../../../../../../../../util/theme'; import { calcTokenAmount } from '../../../../../../../../../util/transactions'; -import { useGetTokenStandardAndDetails } from '../../../../../../hooks/useGetTokenStandardAndDetails'; +import useGetTokenStandardAndDetails from '../../../../../../hooks/useGetTokenStandardAndDetails'; import useTrackERC20WithoutDecimalInformation from '../../../../../../hooks/useTrackERC20WithoutDecimalInformation'; import { TokenDetailsERC20 } from '../../../../../../utils/token'; import BottomModal from '../../../../../UI/BottomModal'; diff --git a/app/components/Views/confirmations/hooks/useGetTokenStandardAndDetails.ts b/app/components/Views/confirmations/hooks/useGetTokenStandardAndDetails.ts index f3ed356e283..212865ebcda 100644 --- a/app/components/Views/confirmations/hooks/useGetTokenStandardAndDetails.ts +++ b/app/components/Views/confirmations/hooks/useGetTokenStandardAndDetails.ts @@ -15,7 +15,7 @@ import { * @param tokenAddress * @returns */ -export const useGetTokenStandardAndDetails = ( +const useGetTokenStandardAndDetails = ( tokenAddress?: Hex | string | undefined, ) => { const { value: details } = @@ -43,3 +43,5 @@ export const useGetTokenStandardAndDetails = ( return details; }; + +export default useGetTokenStandardAndDetails; From 4bc90e02fb9223aeeec49622e6e281806037d023 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 18 Dec 2024 18:53:58 +0700 Subject: [PATCH 11/77] fix: PermitSimulation changeType --- .../Info/TypedSign/PermitSimulation/PermitSimulation.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/PermitSimulation.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/PermitSimulation.tsx index 2f4a5c5d1c1..cc80cae0f26 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/PermitSimulation.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/PermitSimulation.tsx @@ -70,8 +70,8 @@ const PermitSimulation = () => { const isNFT = tokenId !== undefined; const labelChangeType = isNFT - ? strings('confirm.simulation.label_change_type_permit') - : strings('confirm.simulation.label_change_type_permit_nft'); + ? strings('confirm.simulation.label_change_type_permit_nft') + : strings('confirm.simulation.label_change_type_permit'); return ( <InfoSection> From b65adf359baecfdb4760611dfe1d3a27586366dd Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 18 Dec 2024 18:54:54 +0700 Subject: [PATCH 12/77] test: PermitSimulation --- .../PermitSimulation.test.tsx | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/PermitSimulation.test.tsx diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/PermitSimulation.test.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/PermitSimulation.test.tsx new file mode 100644 index 00000000000..5b7157bd1d2 --- /dev/null +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/PermitSimulation.test.tsx @@ -0,0 +1,20 @@ + +import React from 'react'; + +import renderWithProvider from '../../../../../../../../util/test/renderWithProvider'; +import { typedSignV4ConfirmationState } from '../../../../../../../../util/test/confirm-data-helpers'; +import PermitSimulation from './PermitSimulation'; + +describe('PermitSimulation', () => { + it('should render correctly for personal sign', async () => { + const { getByText } = renderWithProvider(<PermitSimulation />, { + state: typedSignV4ConfirmationState, + }); + + expect(getByText('Estimated changes')).toBeDefined(); + expect(getByText('You’re giving the spender permission to spend this many tokens from your account.')).toBeDefined(); + expect(getByText('Spending cap')).toBeDefined(); + expect(getByText('3,000')).toBeDefined(); + expect(getByText('0xCcCCc...ccccC')).toBeDefined(); + }); +}); From a79f2cd3764b87e13293b0e6426b182e7fe8434d Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 18 Dec 2024 18:56:32 +0700 Subject: [PATCH 13/77] fix: useTrackERC20WithoutDecimalInformation should run once --- .../useTrackERC20WithoutDecimalInformation.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/app/components/Views/confirmations/hooks/useTrackERC20WithoutDecimalInformation.ts b/app/components/Views/confirmations/hooks/useTrackERC20WithoutDecimalInformation.ts index e941dd9e726..678d65eccd9 100644 --- a/app/components/Views/confirmations/hooks/useTrackERC20WithoutDecimalInformation.ts +++ b/app/components/Views/confirmations/hooks/useTrackERC20WithoutDecimalInformation.ts @@ -31,18 +31,19 @@ const useTrackERC20WithoutDecimalInformation = ( const parsedDecimals = parseTokenDetailDecimals(decimals); if (parsedDecimals === undefined) { trackEvent(createEventBuilder(MetaMetricsEvents.INCOMPLETE_ASSET_DISPLAYED) - .addProperties({ - token_decimals_available: false, - asset_address: tokenAddress, - asset_type: TokenStandard.ERC20, - chain_id: chainId, - location: metricLocation, - ui_customizations: ['redesigned_confirmation'], - }) + .addProperties({ + token_decimals_available: false, + asset_address: tokenAddress, + asset_type: TokenStandard.ERC20, + chain_id: chainId, + location: metricLocation, + ui_customizations: ['redesigned_confirmation'], + }) .build()); } } - }, [tokenDetails, chainId, tokenAddress, trackEvent, createEventBuilder, metricLocation]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); }; export default useTrackERC20WithoutDecimalInformation; From a5d96ec7b8fe0e6132c8a8b436582de0d170a4f9 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 18 Dec 2024 19:41:04 +0700 Subject: [PATCH 14/77] fix: include typedSignV4ConfirmationState for PermitSimulation test --- app/util/test/confirm-data-helpers.ts | 37 +++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/app/util/test/confirm-data-helpers.ts b/app/util/test/confirm-data-helpers.ts index 09fd96a1285..d2d05549084 100644 --- a/app/util/test/confirm-data-helpers.ts +++ b/app/util/test/confirm-data-helpers.ts @@ -108,3 +108,40 @@ export const typedSignV3ConfirmationState = { }, }, }; + +export const typedSignV4ConfirmationState = { + engine: { + backgroundState: { + ...backgroundState, + ApprovalController: { + pendingApprovals: { + 'fb2029e1-b0ab-11ef-9227-05a11087c334': { + id: 'fb2029e1-b0ab-11ef-9227-05a11087c334', + origin: 'metamask.github.io', + type: 'eth_signTypedData', + time: 1733143817088, + requestData: { + data: '{"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"Permit":[{"name":"owner","type":"address"},{"name":"spender","type":"address"},{"name":"value","type":"uint256"},{"name":"nonce","type":"uint256"},{"name":"deadline","type":"uint256"}]},"primaryType":"Permit","domain":{"name":"MyToken","version":"1","verifyingContract":"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC","chainId":1},"message":{"owner":"0x935e73edb9ff52e23bac7f7e043a1ecd06d05477","spender":"0x5B38Da6a701c568545dCfcB03FcB875f56beddC4","value":3000,"nonce":0,"deadline":50000000000}}', + from: '0x935e73edb9ff52e23bac7f7e043a1ecd06d05477', + version: 'V4', + requestId: 14, + signatureMethod: 'eth_signTypedData_v4', + origin: 'https://metamask.github.io', + metamaskId: 'fb2029e0-b0ab-11ef-9227-05a11087c334', + meta: { + url: 'https://metamask.github.io/test-dapp/', + title: 'E2E Test Dapp', + icon: { uri: 'https://metamask.github.io/metamask-fox.svg' }, + analytics: { request_source: 'In-App-Browser' }, + }, + }, + requestState: null, + expectsResult: true, + }, + }, + pendingApprovalCount: 1, + approvalFlows: [], + }, + }, + }, +}; From 05669d3181302bc8f72aa7ec277477eb85ecff62 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 18 Dec 2024 19:57:11 +0700 Subject: [PATCH 15/77] test: create PermitSimulationValueDisplay test todo: - fix rerender issue - fix removeEventListener detached console error --- .../ValueDisplay/ValueDisplay.test.tsx | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.test.tsx diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.test.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.test.tsx new file mode 100644 index 00000000000..ce7076a5db6 --- /dev/null +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.test.tsx @@ -0,0 +1,162 @@ + +import React from 'react'; +import { act } from '@testing-library/react-native'; +import PermitSimulationValueDisplay from './ValueDisplay'; + +import { memoizedGetTokenStandardAndDetails } from '../../../../../../utils/token'; +import useGetTokenStandardAndDetails from '../../../../../../hooks/useGetTokenStandardAndDetails'; +import { TokenStandard } from '../../../../../../../../UI/SimulationDetails/types'; +import { getTokenDetails } from '../../../../../../../../../util/address'; +import { backgroundState } from '../../../../../../../../../util/test/initial-root-state'; +import renderWithProvider from '../../../../../../../../../util/test/renderWithProvider'; +import { useMetrics } from '../../../../../../../../../components/hooks/useMetrics'; +import { MetricsEventBuilder } from '../../../../../../../../../core/Analytics/MetricsEventBuilder'; + +const mockInitialState = { + engine: { + backgroundState, + }, +}; + +const mockTrackEvent = jest.fn(); + +jest.mock('../../../../../../../../../components/hooks/useMetrics'); +jest.mock('../../../../../../hooks/useGetTokenStandardAndDetails'); + + +jest.mock('../../../../../../../../../util/address', () => ({ + getTokenDetails: jest.fn(), + renderShortAddress: jest.requireActual('../../../../../../../../../util/address').renderShortAddress +})); + +describe('PermitSimulationValueDisplay', () => { + beforeEach(() => { + (useMetrics as jest.MockedFn<typeof useMetrics>).mockReturnValue({ + trackEvent: mockTrackEvent, + createEventBuilder: MetricsEventBuilder.createEventBuilder, + enable: jest.fn(), + addTraitsToUser: jest.fn(), + createDataDeletionTask: jest.fn(), + checkDataDeleteStatus: jest.fn(), + getDeleteRegulationCreationDate: jest.fn(), + getDeleteRegulationId: jest.fn(), + isDataRecorded: jest.fn(), + isEnabled: jest.fn(), + getMetaMetricsId: jest.fn(), + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + + /** Reset memoized function using getTokenStandardAndDetails for each test */ + memoizedGetTokenStandardAndDetails?.cache?.clear?.(); + }); + + it('renders component correctly', async () => { + (useGetTokenStandardAndDetails as jest.MockedFn<typeof useGetTokenStandardAndDetails>).mockReturnValue({ + symbol: 'TST', + decimals: '4', + balance: undefined, + standard: TokenStandard.ERC20, + decimalsNumber: 4, + }); + + const { findByText } = renderWithProvider( + <PermitSimulationValueDisplay + labelChangeType={'Spending Cap'} + tokenContract={'0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'} + value={'4321'} + chainId={'0x1'} + />, + { state: mockInitialState }, + ); + + await act(async () => { + await Promise.resolve(); + }); + + expect(await findByText('0.432')).toBeDefined(); + }); + + it('should invoke method to track missing decimal information for ERC20 tokens', async () => { + (useGetTokenStandardAndDetails as jest.MockedFn<typeof useGetTokenStandardAndDetails>).mockReturnValue({ + symbol: 'TST', + decimals: undefined, + balance: undefined, + standard: TokenStandard.ERC20, + decimalsNumber: 4, + }); + + renderWithProvider( + <PermitSimulationValueDisplay + labelChangeType={'Spending Cap'} + tokenContract={'0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'} + value={'4321'} + chainId={'0x1'} + />, + { state: mockInitialState }, + ); + + await act(async () => { + await Promise.resolve(); + }); + + expect(mockTrackEvent).toHaveBeenCalled(); + }); + + it('should not invoke method to track missing decimal information for ERC20 tokens', async () => { + (useGetTokenStandardAndDetails as jest.MockedFn<typeof useGetTokenStandardAndDetails>).mockReturnValue({ + symbol: 'TST', + decimals: '4', + balance: undefined, + standard: TokenStandard.ERC20, + decimalsNumber: 4, + }); + + renderWithProvider( + <PermitSimulationValueDisplay + labelChangeType={'Spending Cap'} + tokenContract={'0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'} + value={'4321'} + chainId={'0x1'} + />, + { state: mockInitialState }, + ); + + await act(async () => { + await Promise.resolve(); + }); + + expect(mockTrackEvent).not.toHaveBeenCalled(); + }); + + describe('when token is an ERC721 token', () => { + beforeEach(() => { + jest.mocked(getTokenDetails).mockResolvedValue({ + name: 'TST', + symbol: 'TST', + standard: TokenStandard.ERC721, + }); + }); + + it('should not invoke method to track missing decimal information', async () => { + renderWithProvider( + <PermitSimulationValueDisplay + labelChangeType={'Withdraw'} + tokenContract={'0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'} + tokenId={'1234'} + value={'4321'} + chainId={'0x1'} + />, + { state: mockInitialState }, + ); + + await act(async () => { + await Promise.resolve(); + }); + + expect(mockTrackEvent).not.toHaveBeenCalled(); + }); + }); +}); From bcccb06cf5a416fab269a1b06878824fbc66a269 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Thu, 19 Dec 2024 23:27:10 +0700 Subject: [PATCH 16/77] fix: TS lint signature util --- app/components/Views/confirmations/utils/signature.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/components/Views/confirmations/utils/signature.ts b/app/components/Views/confirmations/utils/signature.ts index ca84d70d427..73c19e2b9b7 100644 --- a/app/components/Views/confirmations/utils/signature.ts +++ b/app/components/Views/confirmations/utils/signature.ts @@ -49,7 +49,7 @@ export const parseTypedDataMessage = (dataToParse: string) => { * * @param request - The confirmation request to check */ -export const isRecognizedPermit = (approvalRequest: ApprovalRequest<{ data: string } & Record<string, any>>) => { - const { primaryType } = parseTypedDataMessage(approvalRequest?.requestData?.data); +export const isRecognizedPermit = (approvalRequest: ApprovalRequest<{ data: string }>) => { + const { primaryType } = parseTypedDataMessage(approvalRequest.requestData.data); return PRIMARY_TYPES_PERMIT.includes(primaryType); }; From 567fc667f0e8ae514d5c4148935fccf8f2992e17 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Mon, 6 Jan 2025 21:05:39 -0800 Subject: [PATCH 17/77] fix: readd dep to useTrackERC20WithoutDecimalInformation --- .../useTrackERC20WithoutDecimalInformation.ts | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/app/components/Views/confirmations/hooks/useTrackERC20WithoutDecimalInformation.ts b/app/components/Views/confirmations/hooks/useTrackERC20WithoutDecimalInformation.ts index 678d65eccd9..2297bb043bf 100644 --- a/app/components/Views/confirmations/hooks/useTrackERC20WithoutDecimalInformation.ts +++ b/app/components/Views/confirmations/hooks/useTrackERC20WithoutDecimalInformation.ts @@ -27,23 +27,24 @@ const useTrackERC20WithoutDecimalInformation = ( return; } const { decimals, standard } = tokenDetails || {}; - if (standard === TokenStandard.ERC20) { - const parsedDecimals = parseTokenDetailDecimals(decimals); - if (parsedDecimals === undefined) { - trackEvent(createEventBuilder(MetaMetricsEvents.INCOMPLETE_ASSET_DISPLAYED) - .addProperties({ - token_decimals_available: false, - asset_address: tokenAddress, - asset_type: TokenStandard.ERC20, - chain_id: chainId, - location: metricLocation, - ui_customizations: ['redesigned_confirmation'], - }) - .build()); - } + + if (standard !== TokenStandard.ERC20) { return; } + + const parsedDecimals = parseTokenDetailDecimals(decimals); + + if (parsedDecimals === undefined) { + trackEvent(createEventBuilder(MetaMetricsEvents.INCOMPLETE_ASSET_DISPLAYED) + .addProperties({ + token_decimals_available: false, + asset_address: tokenAddress, + asset_type: TokenStandard.ERC20, + chain_id: chainId, + location: metricLocation, + ui_customizations: ['redesigned_confirmation'], + }) + .build()); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [chainId, tokenAddress, tokenDetails, metricLocation, trackEvent, createEventBuilder]); }; export default useTrackERC20WithoutDecimalInformation; From 0c215c43d6978ee84f7b0de987ee0ce22d3445c7 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Mon, 6 Jan 2025 21:09:40 -0800 Subject: [PATCH 18/77] chore: update useTrackERC20WithoutDecimalInformation TS metricLocation --- .../hooks/useTrackERC20WithoutDecimalInformation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/Views/confirmations/hooks/useTrackERC20WithoutDecimalInformation.ts b/app/components/Views/confirmations/hooks/useTrackERC20WithoutDecimalInformation.ts index 2297bb043bf..6bf37651a5c 100644 --- a/app/components/Views/confirmations/hooks/useTrackERC20WithoutDecimalInformation.ts +++ b/app/components/Views/confirmations/hooks/useTrackERC20WithoutDecimalInformation.ts @@ -18,7 +18,7 @@ const useTrackERC20WithoutDecimalInformation = ( chainId: Hex, tokenAddress: Hex | string | undefined, tokenDetails?: TokenDetailsERC20, - metricLocation = 'signature_confirmation', + metricLocation: string = 'signature_confirmation', ) => { const { trackEvent, createEventBuilder } = useMetrics(); From 16831239eb8b75097973ec4832ce529f3919b4ad Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Mon, 6 Jan 2025 21:12:19 -0800 Subject: [PATCH 19/77] test: ValueDisplay test --- .../PermitSimulation/ValueDisplay/ValueDisplay.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.test.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.test.tsx index ce7076a5db6..7f570674a9d 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.test.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.test.tsx @@ -79,7 +79,7 @@ describe('PermitSimulationValueDisplay', () => { expect(await findByText('0.432')).toBeDefined(); }); - it('should invoke method to track missing decimal information for ERC20 tokens', async () => { + it('should invoke method to track missing decimal information for ERC20 tokens only once', async () => { (useGetTokenStandardAndDetails as jest.MockedFn<typeof useGetTokenStandardAndDetails>).mockReturnValue({ symbol: 'TST', decimals: undefined, @@ -102,7 +102,7 @@ describe('PermitSimulationValueDisplay', () => { await Promise.resolve(); }); - expect(mockTrackEvent).toHaveBeenCalled(); + expect(mockTrackEvent).toHaveBeenCalledTimes(1); }); it('should not invoke method to track missing decimal information for ERC20 tokens', async () => { From 79a4003f251435000713a060be92afdd410f95b2 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Mon, 6 Jan 2025 21:57:43 -0800 Subject: [PATCH 20/77] fix: test allow style param InfoAddress --- .../__snapshots__/Address.test.tsx.snap | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/app/components/Views/confirmations/components/UI/InfoRow/InfoValue/Address/__snapshots__/Address.test.tsx.snap b/app/components/Views/confirmations/components/UI/InfoRow/InfoValue/Address/__snapshots__/Address.test.tsx.snap index c0a8749fcc1..6d6fbd986dc 100644 --- a/app/components/Views/confirmations/components/UI/InfoRow/InfoValue/Address/__snapshots__/Address.test.tsx.snap +++ b/app/components/Views/confirmations/components/UI/InfoRow/InfoValue/Address/__snapshots__/Address.test.tsx.snap @@ -3,17 +3,20 @@ exports[`InfoAddress should match snapshot 1`] = ` <View style={ - { - "alignItems": "center", - "alignSelf": "center", - "backgroundColor": "#f2f4f6", - "borderRadius": 99, - "flexDirection": "row", - "gap": 5, - "paddingLeft": 8, - "paddingRight": 8, - "paddingVertical": 4, - } + [ + { + "alignItems": "center", + "alignSelf": "center", + "backgroundColor": "#f2f4f6", + "borderRadius": 99, + "flexDirection": "row", + "gap": 5, + "paddingLeft": 8, + "paddingRight": 8, + "paddingVertical": 4, + }, + undefined, + ] } > <SvgMock From aa43eb9b818888392554c87cbc711c0c611636eb Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Mon, 6 Jan 2025 22:05:50 -0800 Subject: [PATCH 21/77] test:fix: OrderDetails allow undefined style param --- .../__snapshots__/OrderDetails.test.tsx.snap | 9 +++++++++ 1 file changed, 9 insertions(+) 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 f25e42498a0..b630bd9b7d0 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 @@ -591,6 +591,7 @@ exports[`OrderDetails renders a cancelled order 1`] = ` "padding": 0, }, undefined, + undefined, ] } > @@ -2088,6 +2089,7 @@ exports[`OrderDetails renders a completed order 1`] = ` "padding": 0, }, undefined, + undefined, ] } > @@ -3599,6 +3601,7 @@ exports[`OrderDetails renders a created order 1`] = ` "padding": 0, }, undefined, + undefined, ] } > @@ -5032,6 +5035,7 @@ exports[`OrderDetails renders a failed order 1`] = ` "padding": 0, }, undefined, + undefined, ] } > @@ -6543,6 +6547,7 @@ exports[`OrderDetails renders a pending order 1`] = ` "padding": 0, }, undefined, + undefined, ] } > @@ -9077,6 +9082,7 @@ exports[`OrderDetails renders non-transacted orders 1`] = ` "padding": 0, }, undefined, + undefined, ] } > @@ -10615,6 +10621,7 @@ exports[`OrderDetails renders the support links if the provider has them 1`] = ` "padding": 0, }, undefined, + undefined, ] } > @@ -12146,6 +12153,7 @@ exports[`OrderDetails renders transacted orders that do not have timeDescription "padding": 0, }, undefined, + undefined, ] } > @@ -13620,6 +13628,7 @@ exports[`OrderDetails renders transacted orders that have timeDescriptionPending "padding": 0, }, undefined, + undefined, ] } > From 8577591c260a653aebc68f467cd815b76ede9a9d Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Mon, 6 Jan 2025 22:21:26 -0800 Subject: [PATCH 22/77] test:fix: include InfoAddress undefined style param --- .../UI/Name/__snapshots__/Name.test.tsx.snap | 75 +++++++++++-------- .../__snapshots__/BuildQuote.test.tsx.snap | 14 ++++ .../PaymentMethods.test.tsx.snap | 18 +++++ .../Quotes/__snapshots__/Quotes.test.tsx.snap | 8 ++ .../__snapshots__/Regions.test.tsx.snap | 9 +++ 5 files changed, 91 insertions(+), 33 deletions(-) diff --git a/app/components/UI/Name/__snapshots__/Name.test.tsx.snap b/app/components/UI/Name/__snapshots__/Name.test.tsx.snap index df088e79c43..fe079e83141 100644 --- a/app/components/UI/Name/__snapshots__/Name.test.tsx.snap +++ b/app/components/UI/Name/__snapshots__/Name.test.tsx.snap @@ -3,17 +3,20 @@ exports[`Name recognized address should render image 1`] = ` <View style={ - { - "alignItems": "center", - "alignSelf": "center", - "backgroundColor": "#f2f4f6", - "borderRadius": 99, - "flexDirection": "row", - "gap": 5, - "paddingLeft": 8, - "paddingRight": 8, - "paddingVertical": 4, - } + [ + { + "alignItems": "center", + "alignSelf": "center", + "backgroundColor": "#f2f4f6", + "borderRadius": 99, + "flexDirection": "row", + "gap": 5, + "paddingLeft": 8, + "paddingRight": 8, + "paddingVertical": 4, + }, + undefined, + ] } > <View @@ -102,17 +105,20 @@ exports[`Name recognized address should render image 1`] = ` exports[`Name recognized address should return name 1`] = ` <View style={ - { - "alignItems": "center", - "alignSelf": "center", - "backgroundColor": "#f2f4f6", - "borderRadius": 99, - "flexDirection": "row", - "gap": 5, - "paddingLeft": 8, - "paddingRight": 8, - "paddingVertical": 4, - } + [ + { + "alignItems": "center", + "alignSelf": "center", + "backgroundColor": "#f2f4f6", + "borderRadius": 99, + "flexDirection": "row", + "gap": 5, + "paddingLeft": 8, + "paddingRight": 8, + "paddingVertical": 4, + }, + undefined, + ] } > <View @@ -201,17 +207,20 @@ exports[`Name recognized address should return name 1`] = ` exports[`Name unknown address displays checksummed address 1`] = ` <View style={ - { - "alignItems": "center", - "alignSelf": "center", - "backgroundColor": "#f2f4f6", - "borderRadius": 99, - "flexDirection": "row", - "gap": 5, - "paddingLeft": 8, - "paddingRight": 8, - "paddingVertical": 4, - } + [ + { + "alignItems": "center", + "alignSelf": "center", + "backgroundColor": "#f2f4f6", + "borderRadius": 99, + "flexDirection": "row", + "gap": 5, + "paddingLeft": 8, + "paddingRight": 8, + "paddingVertical": 4, + }, + undefined, + ] } > <SvgMock 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 1abf3c09e47..52e3ff03e42 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,6 +2604,7 @@ exports[`BuildQuote View Crypto Currency Data renders the loading page when cryp "padding": 0, }, undefined, + undefined, ] } > @@ -2793,6 +2794,7 @@ exports[`BuildQuote View Crypto Currency Data renders the loading page when cryp "padding": 0, }, undefined, + undefined, ] } > @@ -4192,6 +4194,7 @@ exports[`BuildQuote View Fiat Currency Data renders the loading page when fiats "padding": 0, }, undefined, + undefined, ] } > @@ -4381,6 +4384,7 @@ exports[`BuildQuote View Fiat Currency Data renders the loading page when fiats "padding": 0, }, undefined, + undefined, ] } > @@ -5780,6 +5784,7 @@ exports[`BuildQuote View Payment Method Data renders the loading page when payme "padding": 0, }, undefined, + undefined, ] } > @@ -5969,6 +5974,7 @@ exports[`BuildQuote View Payment Method Data renders the loading page when payme "padding": 0, }, undefined, + undefined, ] } > @@ -7368,6 +7374,7 @@ exports[`BuildQuote View Regions data renders the loading page when regions are "padding": 0, }, undefined, + undefined, ] } > @@ -7557,6 +7564,7 @@ exports[`BuildQuote View Regions data renders the loading page when regions are "padding": 0, }, undefined, + undefined, ] } > @@ -8503,6 +8511,7 @@ exports[`BuildQuote View renders correctly 1`] = ` "padding": 0, }, undefined, + undefined, ] } > @@ -8833,6 +8842,7 @@ exports[`BuildQuote View renders correctly 1`] = ` "padding": 0, }, undefined, + undefined, ] } > @@ -9127,6 +9137,7 @@ exports[`BuildQuote View renders correctly 1`] = ` "padding": 0, }, undefined, + undefined, ] } > @@ -11609,6 +11620,7 @@ exports[`BuildQuote View renders correctly 2`] = ` "padding": 0, }, undefined, + undefined, ] } > @@ -11939,6 +11951,7 @@ exports[`BuildQuote View renders correctly 2`] = ` "padding": 0, }, undefined, + undefined, ] } > @@ -12081,6 +12094,7 @@ exports[`BuildQuote View renders correctly 2`] = ` "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 2e25a8d696d..e5a9d3b334f 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,6 +495,7 @@ exports[`PaymentMethods View renders correctly 1`] = ` "padding": 0, }, undefined, + undefined, ] } > @@ -780,6 +781,7 @@ exports[`PaymentMethods View renders correctly 1`] = ` "padding": 0, }, undefined, + undefined, ] } > @@ -1082,6 +1084,7 @@ exports[`PaymentMethods View renders correctly 1`] = ` "padding": 0, }, undefined, + undefined, ] } > @@ -1942,6 +1945,7 @@ exports[`PaymentMethods View renders correctly for sell 1`] = ` "padding": 0, }, undefined, + undefined, ] } > @@ -2227,6 +2231,7 @@ exports[`PaymentMethods View renders correctly for sell 1`] = ` "padding": 0, }, undefined, + undefined, ] } > @@ -2529,6 +2534,7 @@ exports[`PaymentMethods View renders correctly for sell 1`] = ` "padding": 0, }, undefined, + undefined, ] } > @@ -3385,6 +3391,7 @@ exports[`PaymentMethods View renders correctly while loading 1`] = ` "padding": 0, }, undefined, + undefined, ] } > @@ -3634,6 +3641,7 @@ exports[`PaymentMethods View renders correctly while loading 1`] = ` "padding": 0, }, undefined, + undefined, ] } > @@ -3885,6 +3893,7 @@ exports[`PaymentMethods View renders correctly while loading 1`] = ` "padding": 0, }, undefined, + undefined, ] } > @@ -6582,6 +6591,7 @@ exports[`PaymentMethods View renders correctly with null data 1`] = ` "padding": 0, }, undefined, + undefined, ] } > @@ -6831,6 +6841,7 @@ exports[`PaymentMethods View renders correctly with null data 1`] = ` "padding": 0, }, undefined, + undefined, ] } > @@ -7082,6 +7093,7 @@ exports[`PaymentMethods View renders correctly with null data 1`] = ` "padding": 0, }, undefined, + undefined, ] } > @@ -7811,6 +7823,7 @@ exports[`PaymentMethods View renders correctly with payment method with disclaim "padding": 0, }, undefined, + undefined, ] } > @@ -8096,6 +8109,7 @@ exports[`PaymentMethods View renders correctly with payment method with disclaim "padding": 0, }, undefined, + undefined, ] } > @@ -8398,6 +8412,7 @@ exports[`PaymentMethods View renders correctly with payment method with disclaim "padding": 0, }, undefined, + undefined, ] } > @@ -9949,6 +9964,7 @@ exports[`PaymentMethods View renders correctly with show back button false 1`] = "padding": 0, }, undefined, + undefined, ] } > @@ -10234,6 +10250,7 @@ exports[`PaymentMethods View renders correctly with show back button false 1`] = "padding": 0, }, undefined, + undefined, ] } > @@ -10536,6 +10553,7 @@ 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 3ad1e4f304e..97f6fd0365a 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,6 +35,7 @@ exports[`LoadingQuotes component renders correctly 1`] = ` "padding": 0, }, undefined, + undefined, ] } > @@ -276,6 +277,7 @@ exports[`LoadingQuotes component renders correctly 1`] = ` "padding": 0, }, undefined, + undefined, ] } > @@ -414,6 +416,7 @@ exports[`LoadingQuotes component renders correctly 1`] = ` "padding": 0, }, undefined, + undefined, ] } > @@ -3543,6 +3546,7 @@ exports[`Quotes renders correctly after animation with quotes 1`] = ` "padding": 0, }, undefined, + undefined, ] } > @@ -3815,6 +3819,7 @@ exports[`Quotes renders correctly after animation with quotes 1`] = ` "padding": 0, }, undefined, + undefined, ] } > @@ -5018,6 +5023,7 @@ exports[`Quotes renders correctly after animation with quotes and expanded 2`] = "padding": 0, }, undefined, + undefined, ] } > @@ -5290,6 +5296,7 @@ exports[`Quotes renders correctly after animation with quotes and expanded 2`] = "padding": 0, }, undefined, + undefined, ] } > @@ -5589,6 +5596,7 @@ 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 d181f8b8542..cffaf29508d 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,6 +566,7 @@ exports[`Regions View renders correctly 1`] = ` "padding": 0, }, undefined, + undefined, ] } > @@ -1345,6 +1346,7 @@ exports[`Regions View renders correctly while loading 1`] = ` undefined, undefined, undefined, + undefined, ] } > @@ -2573,6 +2575,7 @@ exports[`Regions View renders correctly with no data 1`] = ` undefined, undefined, undefined, + undefined, ] } > @@ -3822,6 +3825,7 @@ exports[`Regions View renders correctly with selectedRegion 1`] = ` "padding": 0, }, undefined, + undefined, ] } > @@ -4620,6 +4624,7 @@ exports[`Regions View renders correctly with unsupportedRegion 1`] = ` "padding": 0, }, undefined, + undefined, ] } > @@ -4944,6 +4949,7 @@ exports[`Regions View renders correctly with unsupportedRegion 1`] = ` undefined, undefined, undefined, + undefined, { "backgroundColor": "#ffffff", "borderWidth": 0, @@ -5793,6 +5799,7 @@ exports[`Regions View renders correctly with unsupportedRegion 2`] = ` "padding": 0, }, undefined, + undefined, ] } > @@ -6117,6 +6124,7 @@ exports[`Regions View renders correctly with unsupportedRegion 2`] = ` undefined, undefined, undefined, + undefined, { "backgroundColor": "#ffffff", "borderWidth": 0, @@ -6966,6 +6974,7 @@ exports[`Regions View renders regions modal when pressing select button 1`] = ` "padding": 0, }, undefined, + undefined, ] } > From d53035f74b4d8445283c4a99d7d913d51545d7f5 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Tue, 7 Jan 2025 08:35:20 -0800 Subject: [PATCH 23/77] chore: ButtonPill pressed -> isPressed --- .../Buttons/ButtonPill/ButtonPill.styles.ts | 6 +++--- .../components/Buttons/ButtonPill/ButtonPill.tsx | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/component-library/components/Buttons/ButtonPill/ButtonPill.styles.ts b/app/component-library/components/Buttons/ButtonPill/ButtonPill.styles.ts index 8e55b9a27e3..a5d937a948c 100644 --- a/app/component-library/components/Buttons/ButtonPill/ButtonPill.styles.ts +++ b/app/component-library/components/Buttons/ButtonPill/ButtonPill.styles.ts @@ -8,8 +8,8 @@ import { Theme } from '../../../../util/theme/models'; * Style sheet input parameters. */ export interface ButtonPillStyleSheetVars { - pressed: boolean; isDisabled: boolean; + isPressed: boolean; } /** @@ -26,7 +26,7 @@ const styleSheet = (params: { }) => { const { theme: { colors }, - vars: { pressed, isDisabled } + vars: { isDisabled, isPressed } } = params; return StyleSheet.create({ @@ -39,7 +39,7 @@ const styleSheet = (params: { paddingVertical: 4, borderRadius: 99, opacity: isDisabled ? 0.5 : 1, - ...(pressed && { + ...(isPressed && { backgroundColor: colors.background.alternativePressed, }), }, diff --git a/app/component-library/components/Buttons/ButtonPill/ButtonPill.tsx b/app/component-library/components/Buttons/ButtonPill/ButtonPill.tsx index 23af13ea938..2b158cdce6d 100644 --- a/app/component-library/components/Buttons/ButtonPill/ButtonPill.tsx +++ b/app/component-library/components/Buttons/ButtonPill/ButtonPill.tsx @@ -27,27 +27,27 @@ const ButtonPill = ({ children, ...props }: ButtonPillProps) => { - const [pressed, setPressed] = useState(false); + const [isPressed, setIsPressed] = useState(false); const { styles } = useStyles(stylesheet, { style, - pressed, + isPressed, isDisabled, }); const triggerOnPressedIn = useCallback( (e: GestureResponderEvent) => { - setPressed(true); + setIsPressed(true); onPressIn?.(e); }, - [setPressed, onPressIn], + [setIsPressed, onPressIn], ); const triggerOnPressedOut = useCallback( (e: GestureResponderEvent) => { - setPressed(false); + setIsPressed(false); onPressOut?.(e); }, - [setPressed, onPressOut], + [setIsPressed, onPressOut], ); return ( From 2bdb950c0059659539423f52a52a768b0f94db35 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Tue, 7 Jan 2025 08:40:56 -0800 Subject: [PATCH 24/77] build: mv ButtonPill files -> components-temp --- .../Buttons/ButtonPill/ButtonPill.styles.ts | 0 .../Buttons/ButtonPill/ButtonPill.test.tsx | 0 .../Buttons/ButtonPill/ButtonPill.tsx | 0 .../Buttons/ButtonPill/__snapshots__/ButtonPill.test.tsx.snap | 0 .../{components => components-temp}/Buttons/ButtonPill/index.ts | 0 .../TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.tsx | 2 +- 6 files changed, 1 insertion(+), 1 deletion(-) rename app/component-library/{components => components-temp}/Buttons/ButtonPill/ButtonPill.styles.ts (100%) rename app/component-library/{components => components-temp}/Buttons/ButtonPill/ButtonPill.test.tsx (100%) rename app/component-library/{components => components-temp}/Buttons/ButtonPill/ButtonPill.tsx (100%) rename app/component-library/{components => components-temp}/Buttons/ButtonPill/__snapshots__/ButtonPill.test.tsx.snap (100%) rename app/component-library/{components => components-temp}/Buttons/ButtonPill/index.ts (100%) diff --git a/app/component-library/components/Buttons/ButtonPill/ButtonPill.styles.ts b/app/component-library/components-temp/Buttons/ButtonPill/ButtonPill.styles.ts similarity index 100% rename from app/component-library/components/Buttons/ButtonPill/ButtonPill.styles.ts rename to app/component-library/components-temp/Buttons/ButtonPill/ButtonPill.styles.ts diff --git a/app/component-library/components/Buttons/ButtonPill/ButtonPill.test.tsx b/app/component-library/components-temp/Buttons/ButtonPill/ButtonPill.test.tsx similarity index 100% rename from app/component-library/components/Buttons/ButtonPill/ButtonPill.test.tsx rename to app/component-library/components-temp/Buttons/ButtonPill/ButtonPill.test.tsx diff --git a/app/component-library/components/Buttons/ButtonPill/ButtonPill.tsx b/app/component-library/components-temp/Buttons/ButtonPill/ButtonPill.tsx similarity index 100% rename from app/component-library/components/Buttons/ButtonPill/ButtonPill.tsx rename to app/component-library/components-temp/Buttons/ButtonPill/ButtonPill.tsx diff --git a/app/component-library/components/Buttons/ButtonPill/__snapshots__/ButtonPill.test.tsx.snap b/app/component-library/components-temp/Buttons/ButtonPill/__snapshots__/ButtonPill.test.tsx.snap similarity index 100% rename from app/component-library/components/Buttons/ButtonPill/__snapshots__/ButtonPill.test.tsx.snap rename to app/component-library/components-temp/Buttons/ButtonPill/__snapshots__/ButtonPill.test.tsx.snap diff --git a/app/component-library/components/Buttons/ButtonPill/index.ts b/app/component-library/components-temp/Buttons/ButtonPill/index.ts similarity index 100% rename from app/component-library/components/Buttons/ButtonPill/index.ts rename to app/component-library/components-temp/Buttons/ButtonPill/index.ts diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.tsx index 5b49fb5006f..e2b1aaee662 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.tsx @@ -3,7 +3,7 @@ import { TouchableOpacity, View } from 'react-native'; import { useSelector } from 'react-redux'; import { Hex } from '@metamask/utils'; -import ButtonPill from '../../../../../../../../../component-library/components/Buttons/ButtonPill/ButtonPill'; +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'; From 1ffe22ea0fd14f4bfe9ed1de2b44875f4de308d2 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 8 Jan 2025 21:57:30 -0800 Subject: [PATCH 25/77] test: create confirmations/utils/signature test --- .../confirmations/utils/signature.test.ts | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 app/components/Views/confirmations/utils/signature.test.ts diff --git a/app/components/Views/confirmations/utils/signature.test.ts b/app/components/Views/confirmations/utils/signature.test.ts new file mode 100644 index 00000000000..3ac7f03e5ee --- /dev/null +++ b/app/components/Views/confirmations/utils/signature.test.ts @@ -0,0 +1,58 @@ +import { ApprovalRequest } from '@metamask/approval-controller'; +import { parseTypedDataMessage, isRecognizedPermit } from './signature'; +import { PRIMARY_TYPES_PERMIT } from '../constants/signatures'; + +describe('Signature Utils', () => { + describe('parseTypedDataMessage', () => { + it('should parse typed data message correctly', () => { + const data = JSON.stringify({ + message: { + value: '123' + } + }); + const result = parseTypedDataMessage(data); + expect(result).toEqual({ + message: { + value: '123' + } + }); + }); + + it('should handle large message values. This prevents native JS number coercion when the value is greater than Number.MAX_SAFE_INTEGER.', () => { + const largeValue = '123456789012345678901234567890'; + const data = JSON.stringify({ + message: { + value: largeValue + } + }); + const result = parseTypedDataMessage(data); + expect(result.message.value).toBe(largeValue); + }); + }); + + describe('isRecognizedPermit', () => { + it('should return true for recognized permit types', () => { + const mockRequest: ApprovalRequest<{ data: string }> = { + requestData: { + data: JSON.stringify({ + primaryType: PRIMARY_TYPES_PERMIT[0] + }) + } + } as ApprovalRequest<{ data: string }>; + + expect(isRecognizedPermit(mockRequest)).toBe(true); + }); + + it('should return false for unrecognized permit types', () => { + const mockRequest: ApprovalRequest<{ data: string }> = { + requestData: { + data: JSON.stringify({ + primaryType: 'UnrecognizedType' + }) + } + } as ApprovalRequest<{ data: string }>; + + expect(isRecognizedPermit(mockRequest)).toBe(false); + }); + }); +}); From 779f36fa4ad83851c2e312f828a87b1176452891 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 8 Jan 2025 22:37:08 -0800 Subject: [PATCH 26/77] test: add parseTypedDataMessage tests --- .../Views/confirmations/utils/signature.test.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/components/Views/confirmations/utils/signature.test.ts b/app/components/Views/confirmations/utils/signature.test.ts index 3ac7f03e5ee..13be1389591 100644 --- a/app/components/Views/confirmations/utils/signature.test.ts +++ b/app/components/Views/confirmations/utils/signature.test.ts @@ -18,6 +18,14 @@ describe('Signature Utils', () => { }); }); + it('parses message.value as a string', () => { + const result = parseTypedDataMessage( + '{"test": "dummy", "message": { "value": 3000123} }', + ); + expect(result.message.value).toBe('3000123'); + }); + + it('should handle large message values. This prevents native JS number coercion when the value is greater than Number.MAX_SAFE_INTEGER.', () => { const largeValue = '123456789012345678901234567890'; const data = JSON.stringify({ @@ -28,6 +36,12 @@ describe('Signature Utils', () => { const result = parseTypedDataMessage(data); expect(result.message.value).toBe(largeValue); }); + + it('throw error for invalid typedDataMessage', () => { + expect(() => { + parseTypedDataMessage(''); + }).toThrow(new Error('Unexpected end of JSON input')); + }); }); describe('isRecognizedPermit', () => { From 5852d8717b5da4a451560b8f04a75dd4ccf82203 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 8 Jan 2025 22:46:25 -0800 Subject: [PATCH 27/77] test: add shortenString tests for skipCharacterInEnd + update TS --- app/util/notifications/methods/common.test.ts | 28 +++++++++++++++++++ app/util/notifications/methods/common.ts | 20 ++++++------- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/app/util/notifications/methods/common.test.ts b/app/util/notifications/methods/common.test.ts index a60317e5ec1..d5ea5ddc285 100644 --- a/app/util/notifications/methods/common.test.ts +++ b/app/util/notifications/methods/common.test.ts @@ -1,6 +1,7 @@ import { formatMenuItemDate, parseNotification, + shortenString, getLeadingZeroCount, formatAmount, getUsdAmount, @@ -218,3 +219,30 @@ describe('parseNotification', () => { }); }); }); + +describe('shortenString', () => { + it('should return the same string if it is shorter than TRUNCATED_NAME_CHAR_LIMIT', () => { + expect(shortenString('string')).toStrictEqual('string'); + }); + + it('should return the shortened string according to the specified options', () => { + expect( + shortenString('0x1234567890123456789012345678901234567890', { + truncatedCharLimit: 10, + truncatedStartChars: 4, + truncatedEndChars: 4, + }), + ).toStrictEqual('0x12...7890'); + }); + + it('should shorten the string and remove all characters from the end if skipCharacterInEnd is true', () => { + expect( + shortenString('0x1234567890123456789012345678901234567890', { + truncatedCharLimit: 10, + truncatedStartChars: 4, + truncatedEndChars: 4, + skipCharacterInEnd: true, + }), + ).toStrictEqual('0x12...'); + }); +}); diff --git a/app/util/notifications/methods/common.ts b/app/util/notifications/methods/common.ts index 7365e78cab5..2ee0ce6e18d 100644 --- a/app/util/notifications/methods/common.ts +++ b/app/util/notifications/methods/common.ts @@ -314,16 +314,16 @@ export const TRUNCATED_ADDRESS_END_CHARS = 5; export function shortenString( stringToShorten = '', { - truncatedCharLimit, - truncatedStartChars, - truncatedEndChars, - skipCharacterInEnd, - } = { - truncatedCharLimit: TRUNCATED_NAME_CHAR_LIMIT, - truncatedStartChars: TRUNCATED_ADDRESS_START_CHARS, - truncatedEndChars: TRUNCATED_ADDRESS_END_CHARS, - skipCharacterInEnd: false, - }, + truncatedCharLimit = TRUNCATED_NAME_CHAR_LIMIT, + truncatedStartChars = TRUNCATED_ADDRESS_START_CHARS, + truncatedEndChars = TRUNCATED_ADDRESS_END_CHARS, + skipCharacterInEnd = false, + }: { + truncatedCharLimit?: number; + truncatedStartChars?: number; + truncatedEndChars?: number; + skipCharacterInEnd?: boolean; + } = {}, ) { if (stringToShorten.length < truncatedCharLimit) { return stringToShorten; From f0cc923459236a6d4ee898bdcd00d4f7ed0d0871 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Fri, 10 Jan 2025 19:15:10 -0800 Subject: [PATCH 28/77] build: mv permit simulation files --- .../Info/TypedSign/PermitSimulation/index.ts | 1 - .../Info/TypedSignV3V4/TypedSignV3V4.tsx | 2 +- .../Permit/Permit.test.tsx} | 6 ++-- .../Permit/Permit.tsx} | 18 +++++----- .../ValueDisplay/ValueDisplay.styles.ts | 2 +- .../ValueDisplay/ValueDisplay.test.tsx | 16 ++++----- .../Permit}/ValueDisplay/ValueDisplay.tsx | 36 +++++++++---------- .../Permit}/ValueDisplay/index.ts | 0 .../Confirm/Simulation/Permit/index.ts | 1 + 9 files changed, 41 insertions(+), 41 deletions(-) delete mode 100644 app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/index.ts rename app/components/Views/confirmations/components/Confirm/{Info/TypedSign/PermitSimulation/PermitSimulation.test.tsx => Simulation/Permit/Permit.test.tsx} (78%) rename app/components/Views/confirmations/components/Confirm/{Info/TypedSign/PermitSimulation/PermitSimulation.tsx => Simulation/Permit/Permit.tsx} (82%) rename app/components/Views/confirmations/components/Confirm/{Info/TypedSign/PermitSimulation => Simulation/Permit}/ValueDisplay/ValueDisplay.styles.ts (98%) rename app/components/Views/confirmations/components/Confirm/{Info/TypedSign/PermitSimulation => Simulation/Permit}/ValueDisplay/ValueDisplay.test.tsx (87%) rename app/components/Views/confirmations/components/Confirm/{Info/TypedSign/PermitSimulation => Simulation/Permit}/ValueDisplay/ValueDisplay.tsx (79%) rename app/components/Views/confirmations/components/Confirm/{Info/TypedSign/PermitSimulation => Simulation/Permit}/ValueDisplay/index.ts (100%) create mode 100644 app/components/Views/confirmations/components/Confirm/Simulation/Permit/index.ts diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/index.ts b/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/index.ts deleted file mode 100644 index 43538766e64..00000000000 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './PermitSimulation'; 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 89a953d71e5..4d0fe51abc5 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/TypedSignV3V4.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/TypedSignV3V4.tsx @@ -4,7 +4,7 @@ import { selectUseTransactionSimulations } from '../../../../../../../selectors/ import useApprovalRequest from '../../../../hooks/useApprovalRequest'; import { isRecognizedPermit } from '../../../../utils/signature'; import InfoRowOrigin from '../Shared/InfoRowOrigin'; -import PermitSimulation from '../TypedSign/PermitSimulation/PermitSimulation'; +import PermitSimulation from '../../Simulation/PermitSimulation'; import Message from './Message'; const TypedSignV3V4 = () => { diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/PermitSimulation.test.tsx b/app/components/Views/confirmations/components/Confirm/Simulation/Permit/Permit.test.tsx similarity index 78% rename from app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/PermitSimulation.test.tsx rename to app/components/Views/confirmations/components/Confirm/Simulation/Permit/Permit.test.tsx index 5b7157bd1d2..a9e1f651d41 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/PermitSimulation.test.tsx +++ b/app/components/Views/confirmations/components/Confirm/Simulation/Permit/Permit.test.tsx @@ -1,9 +1,9 @@ import React from 'react'; -import renderWithProvider from '../../../../../../../../util/test/renderWithProvider'; -import { typedSignV4ConfirmationState } from '../../../../../../../../util/test/confirm-data-helpers'; -import PermitSimulation from './PermitSimulation'; +import renderWithProvider from '../../../../../../../util/test/renderWithProvider'; +import { typedSignV4ConfirmationState } from '../../../../../../../util/test/confirm-data-helpers'; +import PermitSimulation from './Permit'; describe('PermitSimulation', () => { it('should render correctly for personal sign', async () => { diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/PermitSimulation.tsx b/app/components/Views/confirmations/components/Confirm/Simulation/Permit/Permit.tsx similarity index 82% rename from app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/PermitSimulation.tsx rename to app/components/Views/confirmations/components/Confirm/Simulation/Permit/Permit.tsx index cc80cae0f26..929dd8c7c2d 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/PermitSimulation.tsx +++ b/app/components/Views/confirmations/components/Confirm/Simulation/Permit/Permit.tsx @@ -2,15 +2,15 @@ import React from 'react'; import { useSelector } from 'react-redux'; import { StyleSheet, View } from 'react-native'; -import { strings } from '../../../../../../../../../locales/i18n'; -import { useStyles } from '../../../../../../../../component-library/hooks'; -import { selectChainId } from '../../../../../../../../selectors/networkController'; -import { safeToChecksumAddress } from '../../../../../../../../util/address'; -import { PrimaryType } from '../../../../../constants/signatures'; -import useApprovalRequest from '../../../../../hooks/useApprovalRequest'; -import { parseTypedDataMessage } from '../../../../../utils/signature'; -import InfoRow from '../../../../UI/InfoRow'; -import InfoSection from '../../../../UI/InfoRow/InfoSection'; +import { strings } from '../../../../../../../../locales/i18n'; +import { useStyles } from '../../../../../../../component-library/hooks'; +import { selectChainId } from '../../../../../../../selectors/networkController'; +import { safeToChecksumAddress } from '../../../../../../../util/address'; +import { PrimaryType } from '../../../../constants/signatures'; +import useApprovalRequest from '../../../../hooks/useApprovalRequest'; +import { parseTypedDataMessage } from '../../../../utils/signature'; +import InfoRow from '../../../UI/InfoRow'; +import InfoSection from '../../../UI/InfoRow/InfoSection'; import PermitSimulationValueDisplay from './ValueDisplay'; const styleSheet = () => diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.styles.ts b/app/components/Views/confirmations/components/Confirm/Simulation/Permit/ValueDisplay/ValueDisplay.styles.ts similarity index 98% rename from app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.styles.ts rename to app/components/Views/confirmations/components/Confirm/Simulation/Permit/ValueDisplay/ValueDisplay.styles.ts index 6ffd38f4ffc..b4269038bac 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.styles.ts +++ b/app/components/Views/confirmations/components/Confirm/Simulation/Permit/ValueDisplay/ValueDisplay.styles.ts @@ -1,6 +1,6 @@ import { StyleSheet } from 'react-native'; import { Theme } from '@metamask/design-tokens'; -import { fontStyles, colors as importedColors } from '../../../../../../../../../styles/common'; +import { fontStyles, colors as importedColors } from '../../../../../../../../styles/common'; const styleSheet = (colors: Theme['colors']) => StyleSheet.create({ diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.test.tsx b/app/components/Views/confirmations/components/Confirm/Simulation/Permit/ValueDisplay/ValueDisplay.test.tsx similarity index 87% rename from app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.test.tsx rename to app/components/Views/confirmations/components/Confirm/Simulation/Permit/ValueDisplay/ValueDisplay.test.tsx index 7f570674a9d..f73178c150f 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.test.tsx +++ b/app/components/Views/confirmations/components/Confirm/Simulation/Permit/ValueDisplay/ValueDisplay.test.tsx @@ -3,14 +3,14 @@ import React from 'react'; import { act } from '@testing-library/react-native'; import PermitSimulationValueDisplay from './ValueDisplay'; -import { memoizedGetTokenStandardAndDetails } from '../../../../../../utils/token'; -import useGetTokenStandardAndDetails from '../../../../../../hooks/useGetTokenStandardAndDetails'; -import { TokenStandard } from '../../../../../../../../UI/SimulationDetails/types'; -import { getTokenDetails } from '../../../../../../../../../util/address'; -import { backgroundState } from '../../../../../../../../../util/test/initial-root-state'; -import renderWithProvider from '../../../../../../../../../util/test/renderWithProvider'; -import { useMetrics } from '../../../../../../../../../components/hooks/useMetrics'; -import { MetricsEventBuilder } from '../../../../../../../../../core/Analytics/MetricsEventBuilder'; +import { memoizedGetTokenStandardAndDetails } from '../../../../../utils/token'; +import useGetTokenStandardAndDetails from '../../../../../hooks/useGetTokenStandardAndDetails'; +import { TokenStandard } from '../../../../../../../UI/SimulationDetails/types'; +import { getTokenDetails } from '../../../../../../../../util/address'; +import { backgroundState } from '../../../../../../../../util/test/initial-root-state'; +import renderWithProvider from '../../../../../../../../util/test/renderWithProvider'; +import { useMetrics } from '../../../../../../../hooks/useMetrics'; +import { MetricsEventBuilder } from '../../../../../../../../core/Analytics/MetricsEventBuilder'; const mockInitialState = { engine: { diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.tsx b/app/components/Views/confirmations/components/Confirm/Simulation/Permit/ValueDisplay/ValueDisplay.tsx similarity index 79% rename from app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.tsx rename to app/components/Views/confirmations/components/Confirm/Simulation/Permit/ValueDisplay/ValueDisplay.tsx index e2b1aaee662..77fc99e6dd9 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/ValueDisplay.tsx +++ b/app/components/Views/confirmations/components/Confirm/Simulation/Permit/ValueDisplay/ValueDisplay.tsx @@ -3,32 +3,32 @@ import { TouchableOpacity, View } from 'react-native'; import { useSelector } from 'react-redux'; import { Hex } from '@metamask/utils'; -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 Text from '../../../../../../../../../component-library/components/Texts/Text'; +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 Text from '../../../../../../../../component-library/components/Texts/Text'; -import { IndividualFiatDisplay } from '../../../../../../../../UI/SimulationDetails/FiatDisplay/FiatDisplay'; +import { IndividualFiatDisplay } from '../../../../../../../UI/SimulationDetails/FiatDisplay/FiatDisplay'; import { formatAmount, formatAmountMaxPrecision, -} from '../../../../../../../../UI/SimulationDetails/formatAmount'; +} from '../../../../../../../UI/SimulationDetails/formatAmount'; -import Box from '../../../../../../../../UI/Ramp/components/Box'; -import Address from '../../../../../UI/InfoRow/InfoValue/Address/Address'; +import Box from '../../../../../../../UI/Ramp/components/Box'; +import Address from '../../../../UI/InfoRow/InfoValue/Address/Address'; -import { selectContractExchangeRates } from '../../../../../../../../../selectors/tokenRatesController'; +import { selectContractExchangeRates } from '../../../../../../../../selectors/tokenRatesController'; -import Logger from '../../../../../../../../../util/Logger'; -import { shortenString } from '../../../../../../../../../util/notifications/methods/common'; -import { useTheme } from '../../../../../../../../../util/theme'; -import { calcTokenAmount } from '../../../../../../../../../util/transactions'; +import Logger from '../../../../../../../../util/Logger'; +import { shortenString } from '../../../../../../../../util/notifications/methods/common'; +import { useTheme } from '../../../../../../../../util/theme'; +import { calcTokenAmount } from '../../../../../../../../util/transactions'; -import useGetTokenStandardAndDetails from '../../../../../../hooks/useGetTokenStandardAndDetails'; -import useTrackERC20WithoutDecimalInformation from '../../../../../../hooks/useTrackERC20WithoutDecimalInformation'; -import { TokenDetailsERC20 } from '../../../../../../utils/token'; -import BottomModal from '../../../../../UI/BottomModal'; +import useGetTokenStandardAndDetails from '../../../../../hooks/useGetTokenStandardAndDetails'; +import useTrackERC20WithoutDecimalInformation from '../../../../../hooks/useTrackERC20WithoutDecimalInformation'; +import { TokenDetailsERC20 } from '../../../../../utils/token'; +import BottomModal from '../../../../UI/BottomModal'; import styleSheet from './ValueDisplay.styles'; diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/index.ts b/app/components/Views/confirmations/components/Confirm/Simulation/Permit/ValueDisplay/index.ts similarity index 100% rename from app/components/Views/confirmations/components/Confirm/Info/TypedSign/PermitSimulation/ValueDisplay/index.ts rename to app/components/Views/confirmations/components/Confirm/Simulation/Permit/ValueDisplay/index.ts diff --git a/app/components/Views/confirmations/components/Confirm/Simulation/Permit/index.ts b/app/components/Views/confirmations/components/Confirm/Simulation/Permit/index.ts new file mode 100644 index 00000000000..c08b448a956 --- /dev/null +++ b/app/components/Views/confirmations/components/Confirm/Simulation/Permit/index.ts @@ -0,0 +1 @@ +export { default } from './Permit'; From 98d89c70dad16c2b35bbbc98d78c5cf2eef8ce77 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Fri, 10 Jan 2025 19:21:36 -0800 Subject: [PATCH 29/77] build: rn path Simulation/Permit -> Simulation/TypedSignPermit --- .../confirmations/components/Confirm/Simulation/Permit/index.ts | 1 - .../TypedSignPermit.test.tsx} | 2 +- .../{Permit/Permit.tsx => TypedSignPermit/TypedSignPermit.tsx} | 0 .../ValueDisplay/ValueDisplay.styles.ts | 0 .../ValueDisplay/ValueDisplay.test.tsx | 0 .../{Permit => TypedSignPermit}/ValueDisplay/ValueDisplay.tsx | 0 .../{Permit => TypedSignPermit}/ValueDisplay/index.ts | 0 .../components/Confirm/Simulation/TypedSignPermit/index.ts | 1 + 8 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 app/components/Views/confirmations/components/Confirm/Simulation/Permit/index.ts rename app/components/Views/confirmations/components/Confirm/Simulation/{Permit/Permit.test.tsx => TypedSignPermit/TypedSignPermit.test.tsx} (93%) rename app/components/Views/confirmations/components/Confirm/Simulation/{Permit/Permit.tsx => TypedSignPermit/TypedSignPermit.tsx} (100%) rename app/components/Views/confirmations/components/Confirm/Simulation/{Permit => TypedSignPermit}/ValueDisplay/ValueDisplay.styles.ts (100%) rename app/components/Views/confirmations/components/Confirm/Simulation/{Permit => TypedSignPermit}/ValueDisplay/ValueDisplay.test.tsx (100%) rename app/components/Views/confirmations/components/Confirm/Simulation/{Permit => TypedSignPermit}/ValueDisplay/ValueDisplay.tsx (100%) rename app/components/Views/confirmations/components/Confirm/Simulation/{Permit => TypedSignPermit}/ValueDisplay/index.ts (100%) create mode 100644 app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/index.ts diff --git a/app/components/Views/confirmations/components/Confirm/Simulation/Permit/index.ts b/app/components/Views/confirmations/components/Confirm/Simulation/Permit/index.ts deleted file mode 100644 index c08b448a956..00000000000 --- a/app/components/Views/confirmations/components/Confirm/Simulation/Permit/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Permit'; diff --git a/app/components/Views/confirmations/components/Confirm/Simulation/Permit/Permit.test.tsx b/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/TypedSignPermit.test.tsx similarity index 93% rename from app/components/Views/confirmations/components/Confirm/Simulation/Permit/Permit.test.tsx rename to app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/TypedSignPermit.test.tsx index a9e1f651d41..dfa35443879 100644 --- a/app/components/Views/confirmations/components/Confirm/Simulation/Permit/Permit.test.tsx +++ b/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/TypedSignPermit.test.tsx @@ -3,7 +3,7 @@ import React from 'react'; import renderWithProvider from '../../../../../../../util/test/renderWithProvider'; import { typedSignV4ConfirmationState } from '../../../../../../../util/test/confirm-data-helpers'; -import PermitSimulation from './Permit'; +import PermitSimulation from './TypedSignPermit'; describe('PermitSimulation', () => { it('should render correctly for personal sign', async () => { diff --git a/app/components/Views/confirmations/components/Confirm/Simulation/Permit/Permit.tsx b/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/TypedSignPermit.tsx similarity index 100% rename from app/components/Views/confirmations/components/Confirm/Simulation/Permit/Permit.tsx rename to app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/TypedSignPermit.tsx diff --git a/app/components/Views/confirmations/components/Confirm/Simulation/Permit/ValueDisplay/ValueDisplay.styles.ts b/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/ValueDisplay/ValueDisplay.styles.ts similarity index 100% rename from app/components/Views/confirmations/components/Confirm/Simulation/Permit/ValueDisplay/ValueDisplay.styles.ts rename to app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/ValueDisplay/ValueDisplay.styles.ts diff --git a/app/components/Views/confirmations/components/Confirm/Simulation/Permit/ValueDisplay/ValueDisplay.test.tsx b/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/ValueDisplay/ValueDisplay.test.tsx similarity index 100% rename from app/components/Views/confirmations/components/Confirm/Simulation/Permit/ValueDisplay/ValueDisplay.test.tsx rename to app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/ValueDisplay/ValueDisplay.test.tsx diff --git a/app/components/Views/confirmations/components/Confirm/Simulation/Permit/ValueDisplay/ValueDisplay.tsx b/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/ValueDisplay/ValueDisplay.tsx similarity index 100% rename from app/components/Views/confirmations/components/Confirm/Simulation/Permit/ValueDisplay/ValueDisplay.tsx rename to app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/ValueDisplay/ValueDisplay.tsx diff --git a/app/components/Views/confirmations/components/Confirm/Simulation/Permit/ValueDisplay/index.ts b/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/ValueDisplay/index.ts similarity index 100% rename from app/components/Views/confirmations/components/Confirm/Simulation/Permit/ValueDisplay/index.ts rename to app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/ValueDisplay/index.ts diff --git a/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/index.ts b/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/index.ts new file mode 100644 index 00000000000..2dd1a7b5f59 --- /dev/null +++ b/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/index.ts @@ -0,0 +1 @@ +export { default } from './TypedSignPermit'; From 666f11dfd431a498a15a066bdaca071a9c52b20d Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Sun, 12 Jan 2025 23:27:55 -0800 Subject: [PATCH 30/77] fix: Simulation/TypedSignPermit path --- .../components/Confirm/Info/TypedSignV3V4/TypedSignV3V4.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 4d0fe51abc5..367b029781e 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/TypedSignV3V4.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/TypedSignV3V4.tsx @@ -4,7 +4,7 @@ import { selectUseTransactionSimulations } from '../../../../../../../selectors/ import useApprovalRequest from '../../../../hooks/useApprovalRequest'; import { isRecognizedPermit } from '../../../../utils/signature'; import InfoRowOrigin from '../Shared/InfoRowOrigin'; -import PermitSimulation from '../../Simulation/PermitSimulation'; +import PermitSimulation from '../../Simulation/TypedSignPermit'; import Message from './Message'; const TypedSignV3V4 = () => { From f2e16efc6fd38dab05b46fe63fab0984d98c8359 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Mon, 13 Jan 2025 00:40:20 -0800 Subject: [PATCH 31/77] build: mv Simulation Value Display --- .../Confirm/Simulation/TypedSignPermit/TypedSignPermit.tsx | 2 +- .../ValueDisplay/ValueDisplay.styles.ts | 0 .../ValueDisplay/ValueDisplay.test.tsx | 0 .../ValueDisplay/ValueDisplay.tsx | 0 .../{TypedSignPermit => components}/ValueDisplay/index.ts | 0 5 files changed, 1 insertion(+), 1 deletion(-) rename app/components/Views/confirmations/components/Confirm/Simulation/{TypedSignPermit => components}/ValueDisplay/ValueDisplay.styles.ts (100%) rename app/components/Views/confirmations/components/Confirm/Simulation/{TypedSignPermit => components}/ValueDisplay/ValueDisplay.test.tsx (100%) rename app/components/Views/confirmations/components/Confirm/Simulation/{TypedSignPermit => components}/ValueDisplay/ValueDisplay.tsx (100%) rename app/components/Views/confirmations/components/Confirm/Simulation/{TypedSignPermit => components}/ValueDisplay/index.ts (100%) diff --git a/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/TypedSignPermit.tsx b/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/TypedSignPermit.tsx index 929dd8c7c2d..404779f905b 100644 --- a/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/TypedSignPermit.tsx +++ b/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/TypedSignPermit.tsx @@ -11,7 +11,7 @@ import useApprovalRequest from '../../../../hooks/useApprovalRequest'; import { parseTypedDataMessage } from '../../../../utils/signature'; import InfoRow from '../../../UI/InfoRow'; import InfoSection from '../../../UI/InfoRow/InfoSection'; -import PermitSimulationValueDisplay from './ValueDisplay'; +import PermitSimulationValueDisplay from '../components/ValueDisplay'; const styleSheet = () => StyleSheet.create({ diff --git a/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/ValueDisplay/ValueDisplay.styles.ts b/app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.styles.ts similarity index 100% rename from app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/ValueDisplay/ValueDisplay.styles.ts rename to app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.styles.ts diff --git a/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/ValueDisplay/ValueDisplay.test.tsx b/app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.test.tsx similarity index 100% rename from app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/ValueDisplay/ValueDisplay.test.tsx rename to app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.test.tsx diff --git a/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/ValueDisplay/ValueDisplay.tsx b/app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.tsx similarity index 100% rename from app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/ValueDisplay/ValueDisplay.tsx rename to app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.tsx diff --git a/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/ValueDisplay/index.ts b/app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/index.ts similarity index 100% rename from app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/ValueDisplay/index.ts rename to app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/index.ts From fb9e3ae8815099bcb59382bf8a8d705b66bd0af2 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Mon, 13 Jan 2025 00:43:44 -0800 Subject: [PATCH 32/77] refactor: rn PermitSimulationValueDisplay -> --- .../components/ValueDisplay/ValueDisplay.test.tsx | 12 ++++++------ .../components/ValueDisplay/ValueDisplay.tsx | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.test.tsx b/app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.test.tsx index f73178c150f..dca6f9bc859 100644 --- a/app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.test.tsx +++ b/app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { act } from '@testing-library/react-native'; -import PermitSimulationValueDisplay from './ValueDisplay'; +import SimulationValueDisplay from './ValueDisplay'; import { memoizedGetTokenStandardAndDetails } from '../../../../../utils/token'; import useGetTokenStandardAndDetails from '../../../../../hooks/useGetTokenStandardAndDetails'; @@ -29,7 +29,7 @@ jest.mock('../../../../../../../../../util/address', () => ({ renderShortAddress: jest.requireActual('../../../../../../../../../util/address').renderShortAddress })); -describe('PermitSimulationValueDisplay', () => { +describe('SimulationValueDisplay', () => { beforeEach(() => { (useMetrics as jest.MockedFn<typeof useMetrics>).mockReturnValue({ trackEvent: mockTrackEvent, @@ -63,7 +63,7 @@ describe('PermitSimulationValueDisplay', () => { }); const { findByText } = renderWithProvider( - <PermitSimulationValueDisplay + <SimulationValueDisplay labelChangeType={'Spending Cap'} tokenContract={'0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'} value={'4321'} @@ -89,7 +89,7 @@ describe('PermitSimulationValueDisplay', () => { }); renderWithProvider( - <PermitSimulationValueDisplay + <SimulationValueDisplay labelChangeType={'Spending Cap'} tokenContract={'0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'} value={'4321'} @@ -115,7 +115,7 @@ describe('PermitSimulationValueDisplay', () => { }); renderWithProvider( - <PermitSimulationValueDisplay + <SimulationValueDisplay labelChangeType={'Spending Cap'} tokenContract={'0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'} value={'4321'} @@ -142,7 +142,7 @@ describe('PermitSimulationValueDisplay', () => { it('should not invoke method to track missing decimal information', async () => { renderWithProvider( - <PermitSimulationValueDisplay + <SimulationValueDisplay labelChangeType={'Withdraw'} tokenContract={'0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'} tokenId={'1234'} diff --git a/app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.tsx b/app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.tsx index 77fc99e6dd9..8614a360f71 100644 --- a/app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.tsx +++ b/app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.tsx @@ -32,7 +32,7 @@ import BottomModal from '../../../../UI/BottomModal'; import styleSheet from './ValueDisplay.styles'; -interface PermitSimulationValueDisplayParams { +interface SimulationValueDisplayParams { /** ID of the associated chain. */ chainId: Hex; @@ -64,8 +64,8 @@ interface PermitSimulationValueDisplayParams { value?: number | string; } -const PermitSimulationValueDisplay: React.FC< - PermitSimulationValueDisplayParams +const SimulationValueDisplay: React.FC< + SimulationValueDisplayParams > = ({ chainId, labelChangeType, @@ -121,7 +121,7 @@ const PermitSimulationValueDisplay: React.FC< if (!tokenContract) { Logger.error( new Error( - `PermitSimulationValueDisplay: Token contract address is missing where primaryType === ${primaryType}`, + `SimulationValueDisplay: Token contract address is missing where primaryType === ${primaryType}`, ), ); return null; @@ -198,4 +198,4 @@ const PermitSimulationValueDisplay: React.FC< ); }; -export default PermitSimulationValueDisplay; +export default SimulationValueDisplay; From f99a239880d2e91c01c45157eaf50c8fa1008701 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Mon, 13 Jan 2025 21:06:22 -0800 Subject: [PATCH 33/77] refactor: rn onPressTokenValue -> handlePressTokenValue --- .../Simulation/components/ValueDisplay/ValueDisplay.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.tsx b/app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.tsx index 8614a360f71..64893d934b1 100644 --- a/app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.tsx +++ b/app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.tsx @@ -127,7 +127,7 @@ const SimulationValueDisplay: React.FC< return null; } - function onPressTokenValue() { + function handlePressTokenValue() { setHasValueModalOpen(true); } @@ -136,9 +136,9 @@ const SimulationValueDisplay: React.FC< <Box style={styles.flexRowTokenValueAndAddress}> <View style={styles.valueAndAddress}> <ButtonPill - onPress={onPressTokenValue} - onPressIn={onPressTokenValue} - onPressOut={onPressTokenValue} + onPress={handlePressTokenValue} + onPressIn={handlePressTokenValue} + onPressOut={handlePressTokenValue} style={[credit && styles.valueIsCredit, debit && styles.valueIsDebit]} > <Text> From 2e75fb1551869bbeecf6fa1d310e1766b906d4e8 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Mon, 13 Jan 2025 21:06:51 -0800 Subject: [PATCH 34/77] refactor: PermitSimulation use signatureRequest chain id instead of global --- .../Simulation/TypedSignPermit/TypedSignPermit.tsx | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/TypedSignPermit.tsx b/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/TypedSignPermit.tsx index 404779f905b..44e0fbada39 100644 --- a/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/TypedSignPermit.tsx +++ b/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/TypedSignPermit.tsx @@ -1,13 +1,12 @@ import React from 'react'; -import { useSelector } from 'react-redux'; import { StyleSheet, View } from 'react-native'; +import { Hex } from '@metamask/utils'; import { strings } from '../../../../../../../../locales/i18n'; import { useStyles } from '../../../../../../../component-library/hooks'; -import { selectChainId } from '../../../../../../../selectors/networkController'; import { safeToChecksumAddress } from '../../../../../../../util/address'; import { PrimaryType } from '../../../../constants/signatures'; -import useApprovalRequest from '../../../../hooks/useApprovalRequest'; +import { useSignatureRequest } from '../../../../hooks/useSignatureRequest'; import { parseTypedDataMessage } from '../../../../utils/signature'; import InfoRow from '../../../UI/InfoRow'; import InfoSection from '../../../UI/InfoRow/InfoSection'; @@ -48,12 +47,9 @@ function extractTokenDetailsByPrimaryType( const PermitSimulation = () => { const { styles } = useStyles(styleSheet, {}); - const { approvalRequest } = useApprovalRequest(); - - // TODO: update logic to use chainId from approvalRequest. SignatureController needs to be updated prior - const chainId = useSelector(selectChainId); - - const msgData = approvalRequest?.requestData?.data; + const signatureRequest = useSignatureRequest(); + const chainId = signatureRequest?.chainId as Hex; + const msgData = signatureRequest?.messageParams?.data; if (!msgData) { return null; From b5a8372dc9d9434cec143f3ccbde361f0fda58b6 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Mon, 13 Jan 2025 22:21:35 -0800 Subject: [PATCH 35/77] test:fix: ValueDisplay update mock paths --- .../components/ValueDisplay/ValueDisplay.test.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.test.tsx b/app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.test.tsx index dca6f9bc859..d09c66ee160 100644 --- a/app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.test.tsx +++ b/app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.test.tsx @@ -20,13 +20,13 @@ const mockInitialState = { const mockTrackEvent = jest.fn(); -jest.mock('../../../../../../../../../components/hooks/useMetrics'); -jest.mock('../../../../../../hooks/useGetTokenStandardAndDetails'); +jest.mock('../../../../../../../hooks/useMetrics'); +jest.mock('../../../../../hooks/useGetTokenStandardAndDetails'); -jest.mock('../../../../../../../../../util/address', () => ({ +jest.mock('../../../../../../../../util/address', () => ({ getTokenDetails: jest.fn(), - renderShortAddress: jest.requireActual('../../../../../../../../../util/address').renderShortAddress + renderShortAddress: jest.requireActual('../../../../../../../../util/address').renderShortAddress })); describe('SimulationValueDisplay', () => { From 72ba710462ac210ffdcaf62db4e4860a408b3a3e Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Mon, 13 Jan 2025 22:28:20 -0800 Subject: [PATCH 36/77] refactor: revert Address style param + wrap ValueDisplay address --- .../components/ValueDisplay/ValueDisplay.tsx | 4 +++- .../UI/InfoRow/InfoValue/Address/Address.tsx | 11 ++--------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.tsx b/app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.tsx index 64893d934b1..272d2915d0e 100644 --- a/app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.tsx +++ b/app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.tsx @@ -154,7 +154,9 @@ const SimulationValueDisplay: React.FC< {tokenId && `#${tokenId}`} </Text> </ButtonPill> - <Address address={tokenContract} chainId={chainId} style={styles.tokenAddress} /> + <Box compact noBorder style={styles.tokenAddress}> + <Address address={tokenContract} chainId={chainId} /> + </Box> </View> </Box> <Box compact noBorder> diff --git a/app/components/Views/confirmations/components/UI/InfoRow/InfoValue/Address/Address.tsx b/app/components/Views/confirmations/components/UI/InfoRow/InfoValue/Address/Address.tsx index 47e096a247c..218e2b0dcf5 100644 --- a/app/components/Views/confirmations/components/UI/InfoRow/InfoValue/Address/Address.tsx +++ b/app/components/Views/confirmations/components/UI/InfoRow/InfoValue/Address/Address.tsx @@ -2,21 +2,14 @@ import React from 'react'; import Name from '../../../../../../../UI/Name'; import { NameType } from '../../../../../../../UI/Name/Name.types'; -import { ViewStyle } from 'react-native'; interface AddressProps { address: string; chainId: string; - style?: ViewStyle; } -const Address = ({ address, chainId, style }: AddressProps) => ( - <Name - type={NameType.EthereumAddress} - value={address} - variation={chainId} - style={style} - /> +const Address = ({ address, chainId }: AddressProps) => ( + <Name type={NameType.EthereumAddress} value={address} variation={chainId} /> ); export default Address; From 9053d1560b7ce811495a280a28b74a24dc7f4fc3 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Mon, 13 Jan 2025 22:38:05 -0800 Subject: [PATCH 37/77] refactor: useGetTokenStandardAndDetails accept options param --- .../hooks/useGetTokenStandardAndDetails.ts | 4 ++-- .../Views/confirmations/utils/token.ts | 20 +++++++++++-------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/app/components/Views/confirmations/hooks/useGetTokenStandardAndDetails.ts b/app/components/Views/confirmations/hooks/useGetTokenStandardAndDetails.ts index 212865ebcda..7b6a93c81f0 100644 --- a/app/components/Views/confirmations/hooks/useGetTokenStandardAndDetails.ts +++ b/app/components/Views/confirmations/hooks/useGetTokenStandardAndDetails.ts @@ -24,9 +24,9 @@ const useGetTokenStandardAndDetails = ( return Promise.resolve(null); } - return (await memoizedGetTokenStandardAndDetails( + return (await memoizedGetTokenStandardAndDetails({ tokenAddress, - )) as TokenDetailsERC20; + })) as TokenDetailsERC20; }, [tokenAddress]); if (!details) { diff --git a/app/components/Views/confirmations/utils/token.ts b/app/components/Views/confirmations/utils/token.ts index 49fc1335fbb..a6d0ed8ff1a 100644 --- a/app/components/Views/confirmations/utils/token.ts +++ b/app/components/Views/confirmations/utils/token.ts @@ -45,11 +45,15 @@ export const parseTokenDetailDecimals = ( }; export const memoizedGetTokenStandardAndDetails = memoize( - async ( - tokenAddress?: Hex | string, - userAddress?: string, - tokenId?: string, - ): Promise<TokenDetails | Record<string, never>> => { + async ({ + tokenAddress, + tokenId, + userAddress, + }: { + tokenAddress?: Hex | string; + userAddress?: string; + tokenId?: string; + }): Promise<TokenDetails | Record<string, never>> => { try { if (!tokenAddress) { return {}; @@ -77,9 +81,9 @@ export const fetchErc20Decimals = async ( address: Hex | string, ): Promise<number> => { try { - const { decimals: decStr } = (await memoizedGetTokenStandardAndDetails( - address, - )) as TokenDetailsERC20; + const { decimals: decStr } = (await memoizedGetTokenStandardAndDetails({ + tokenAddress: address, + })) as TokenDetailsERC20; const decimals = parseTokenDetailDecimals(decStr); return decimals ?? ERC20_DEFAULT_DECIMALS; From ddfbfb0867e455b6bf51ca3d4f4ae98b52380e07 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Mon, 13 Jan 2025 22:52:44 -0800 Subject: [PATCH 38/77] feat: permit simulation include NetworkClientId to get token details --- .../Confirm/Simulation/TypedSignPermit/TypedSignPermit.tsx | 7 +++++++ .../Simulation/components/ValueDisplay/ValueDisplay.tsx | 7 ++++++- .../confirmations/hooks/useGetTokenStandardAndDetails.ts | 3 +++ app/components/Views/confirmations/utils/token.ts | 6 ++++++ app/util/address/index.ts | 2 ++ 5 files changed, 24 insertions(+), 1 deletion(-) diff --git a/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/TypedSignPermit.tsx b/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/TypedSignPermit.tsx index 44e0fbada39..b691a3a6ae2 100644 --- a/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/TypedSignPermit.tsx +++ b/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/TypedSignPermit.tsx @@ -1,9 +1,11 @@ import React from 'react'; import { StyleSheet, View } from 'react-native'; +import { useSelector } from 'react-redux'; import { Hex } from '@metamask/utils'; import { strings } from '../../../../../../../../locales/i18n'; import { useStyles } from '../../../../../../../component-library/hooks'; +import { selectNetworkClientId } from '../../../../../../../selectors/networkController'; import { safeToChecksumAddress } from '../../../../../../../util/address'; import { PrimaryType } from '../../../../constants/signatures'; import { useSignatureRequest } from '../../../../hooks/useSignatureRequest'; @@ -47,10 +49,13 @@ function extractTokenDetailsByPrimaryType( const PermitSimulation = () => { const { styles } = useStyles(styleSheet, {}); + const networkClientId = useSelector(selectNetworkClientId); const signatureRequest = useSignatureRequest(); + const chainId = signatureRequest?.chainId as Hex; const msgData = signatureRequest?.messageParams?.data; + if (!msgData) { return null; } @@ -89,6 +94,7 @@ const PermitSimulation = () => { <PermitSimulationValueDisplay key={`${token}-${i}`} labelChangeType={labelChangeType} + networkClientId={networkClientId} primaryType={primaryType} tokenContract={safeToChecksumAddress(token)} value={amount} @@ -100,6 +106,7 @@ const PermitSimulation = () => { ) : ( <PermitSimulationValueDisplay labelChangeType={labelChangeType} + networkClientId={networkClientId} tokenContract={verifyingContract} value={message.value} tokenId={message.tokenId} diff --git a/app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.tsx b/app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.tsx index 272d2915d0e..3cbf85330ca 100644 --- a/app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.tsx +++ b/app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.tsx @@ -1,6 +1,7 @@ import React, { useMemo, 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 ButtonPill from '../../../../../../../../component-library/components-temp/Buttons/ButtonPill/ButtonPill'; @@ -39,6 +40,9 @@ interface SimulationValueDisplayParams { /** Change type to be displayed in value tooltip */ labelChangeType: string; + /** The network client ID */ + networkClientId?: NetworkClientId; + /** * The ethereum token contract address. It is expected to be in hex format. * We currently accept strings since we have a patch that accepts a custom string @@ -69,6 +73,7 @@ const SimulationValueDisplay: React.FC< > = ({ chainId, labelChangeType, + networkClientId, primaryType, tokenContract, tokenId, @@ -87,7 +92,7 @@ const SimulationValueDisplay: React.FC< ? contractExchangeRates[tokenContract as `0x${string}`]?.price : undefined; - const tokenDetails = useGetTokenStandardAndDetails(tokenContract); + const tokenDetails = useGetTokenStandardAndDetails(tokenContract, networkClientId); const { decimalsNumber: tokenDecimals } = tokenDetails; useTrackERC20WithoutDecimalInformation( diff --git a/app/components/Views/confirmations/hooks/useGetTokenStandardAndDetails.ts b/app/components/Views/confirmations/hooks/useGetTokenStandardAndDetails.ts index 7b6a93c81f0..a361c9fbcb5 100644 --- a/app/components/Views/confirmations/hooks/useGetTokenStandardAndDetails.ts +++ b/app/components/Views/confirmations/hooks/useGetTokenStandardAndDetails.ts @@ -1,3 +1,4 @@ +import { NetworkClientId } from '@metamask/network-controller'; import { Hex } from '@metamask/utils'; import { TokenStandard } from '../../../UI/SimulationDetails/types'; @@ -17,6 +18,7 @@ import { */ const useGetTokenStandardAndDetails = ( tokenAddress?: Hex | string | undefined, + networkClientId?: NetworkClientId, ) => { const { value: details } = useAsyncResult<TokenDetailsERC20 | null>(async () => { @@ -26,6 +28,7 @@ const useGetTokenStandardAndDetails = ( return (await memoizedGetTokenStandardAndDetails({ tokenAddress, + networkClientId, })) as TokenDetailsERC20; }, [tokenAddress]); diff --git a/app/components/Views/confirmations/utils/token.ts b/app/components/Views/confirmations/utils/token.ts index a6d0ed8ff1a..4014dd1f528 100644 --- a/app/components/Views/confirmations/utils/token.ts +++ b/app/components/Views/confirmations/utils/token.ts @@ -1,6 +1,7 @@ import { memoize } from 'lodash'; import { Hex } from '@metamask/utils'; import { AssetsContractController } from '@metamask/assets-controllers'; +import { NetworkClientId } from '@metamask/network-controller'; import { getTokenDetails } from '../../../../util/address'; export type TokenDetailsERC20 = Awaited< @@ -49,10 +50,12 @@ export const memoizedGetTokenStandardAndDetails = memoize( tokenAddress, tokenId, userAddress, + networkClientId, }: { tokenAddress?: Hex | string; userAddress?: string; tokenId?: string; + networkClientId?: NetworkClientId; }): Promise<TokenDetails | Record<string, never>> => { try { if (!tokenAddress) { @@ -63,6 +66,7 @@ export const memoizedGetTokenStandardAndDetails = memoize( tokenAddress, userAddress, tokenId, + networkClientId, )) as TokenDetails; } catch { return {}; @@ -79,10 +83,12 @@ export const memoizedGetTokenStandardAndDetails = memoize( */ export const fetchErc20Decimals = async ( address: Hex | string, + networkClientId?: NetworkClientId, ): Promise<number> => { try { const { decimals: decStr } = (await memoizedGetTokenStandardAndDetails({ tokenAddress: address, + networkClientId, })) as TokenDetailsERC20; const decimals = parseTokenDetailDecimals(decStr); diff --git a/app/util/address/index.ts b/app/util/address/index.ts index c3ce4fc2043..a961ed6cda5 100644 --- a/app/util/address/index.ts +++ b/app/util/address/index.ts @@ -640,12 +640,14 @@ export const getTokenDetails = async ( tokenAddress: string, userAddress?: string, tokenId?: string, + networkClientId?: NetworkClientId, ) => { const { AssetsContractController } = Engine.context; const tokenData = await AssetsContractController.getTokenStandardAndDetails( tokenAddress, userAddress, tokenId, + networkClientId, ); const { standard, name, symbol, decimals } = tokenData; if (standard === ERC721 || standard === ERC1155) { From e89622c0ff65b1a068681d086de32c66a44c1e1c Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Mon, 13 Jan 2025 23:03:23 -0800 Subject: [PATCH 39/77] fix: TypedSignPermit use networkClientId by chainID --- .../Simulation/TypedSignPermit/TypedSignPermit.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/TypedSignPermit.tsx b/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/TypedSignPermit.tsx index b691a3a6ae2..d8fe28e82d0 100644 --- a/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/TypedSignPermit.tsx +++ b/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/TypedSignPermit.tsx @@ -5,7 +5,7 @@ import { Hex } from '@metamask/utils'; import { strings } from '../../../../../../../../locales/i18n'; import { useStyles } from '../../../../../../../component-library/hooks'; -import { selectNetworkClientId } from '../../../../../../../selectors/networkController'; +import Engine from '../../../../../../../core/Engine'; import { safeToChecksumAddress } from '../../../../../../../util/address'; import { PrimaryType } from '../../../../constants/signatures'; import { useSignatureRequest } from '../../../../hooks/useSignatureRequest'; @@ -47,14 +47,17 @@ function extractTokenDetailsByPrimaryType( } const PermitSimulation = () => { + const { NetworkController } = Engine.context; const { styles } = useStyles(styleSheet, {}); - const networkClientId = useSelector(selectNetworkClientId); const signatureRequest = useSignatureRequest(); - + const chainId = signatureRequest?.chainId as Hex; const msgData = signatureRequest?.messageParams?.data; + const networkClientId = NetworkController.findNetworkClientIdByChainId( + chainId as Hex, + ); if (!msgData) { return null; From ab0b8c288d535850edca1a7dc64ac8f2d535f5a9 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Mon, 13 Jan 2025 23:40:18 -0800 Subject: [PATCH 40/77] fix: Simulation permit following use of useSignatureRequest hook --- .../TypedSignPermit/TypedSignPermit.test.tsx | 9 ++++++- app/util/test/confirm-data-helpers.ts | 24 ++++++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/TypedSignPermit.test.tsx b/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/TypedSignPermit.test.tsx index dfa35443879..a68cf834fc6 100644 --- a/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/TypedSignPermit.test.tsx +++ b/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/TypedSignPermit.test.tsx @@ -1,10 +1,17 @@ - import React from 'react'; import renderWithProvider from '../../../../../../../util/test/renderWithProvider'; import { typedSignV4ConfirmationState } from '../../../../../../../util/test/confirm-data-helpers'; import PermitSimulation from './TypedSignPermit'; +jest.mock('../../../../../../../core/Engine', () => ({ + context: { + NetworkController: { + findNetworkClientIdByChainId: () => 'mainnet', + }, + }, +})); + describe('PermitSimulation', () => { it('should render correctly for personal sign', async () => { const { getByText } = renderWithProvider(<PermitSimulation />, { diff --git a/app/util/test/confirm-data-helpers.ts b/app/util/test/confirm-data-helpers.ts index 74c01731745..206458dd773 100644 --- a/app/util/test/confirm-data-helpers.ts +++ b/app/util/test/confirm-data-helpers.ts @@ -1,4 +1,8 @@ -import { MessageParamsTyped } from '@metamask/signature-controller'; +import { + MessageParamsTyped, + SignatureRequestStatus, + SignatureRequestType +} from '@metamask/signature-controller'; import { backgroundState } from './initial-root-state'; import { Hex } from '@metamask/utils'; @@ -221,6 +225,24 @@ export const typedSignV4ConfirmationState = { pendingApprovalCount: 1, approvalFlows: [], }, + SignatureController: { + signatureRequests: { + 'fb2029e1-b0ab-11ef-9227-05a11087c334': { + id: 'fb2029e1-b0ab-11ef-9227-05a11087c334', + chainId: '0x1' as Hex, + type: SignatureRequestType.TypedSign, + messageParams: { + data: '{"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"Permit":[{"name":"owner","type":"address"},{"name":"spender","type":"address"},{"name":"value","type":"uint256"},{"name":"nonce","type":"uint256"},{"name":"deadline","type":"uint256"}]},"primaryType":"Permit","domain":{"name":"MyToken","version":"1","verifyingContract":"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC","chainId":1},"message":{"owner":"0x935e73edb9ff52e23bac7f7e043a1ecd06d05477","spender":"0x5B38Da6a701c568545dCfcB03FcB875f56beddC4","value":3000,"nonce":0,"deadline":50000000000}}', + from: '0x935e73edb9ff52e23bac7f7e043a1ecd06d05477', + metamaskId: 'fb2029e0-b0ab-11ef-9227-05a11087c334', + origin: 'https://metamask.github.io' + }, + networkClientId: '1', + status: SignatureRequestStatus.Unapproved, + time: 1733143817088 + }, + }, + }, }, }, }; From a41589409318abfa6b023f0f8e34ebea0946dd1e Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Tue, 14 Jan 2025 07:42:59 -0800 Subject: [PATCH 41/77] fix: rm unused useSelector import Permit simulation --- .../Confirm/Simulation/TypedSignPermit/TypedSignPermit.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/TypedSignPermit.tsx b/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/TypedSignPermit.tsx index d8fe28e82d0..735d792e1ec 100644 --- a/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/TypedSignPermit.tsx +++ b/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/TypedSignPermit.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { StyleSheet, View } from 'react-native'; -import { useSelector } from 'react-redux'; import { Hex } from '@metamask/utils'; import { strings } from '../../../../../../../../locales/i18n'; From 97ad7856bd2c2fb1ee30887d99a93b3d53772d9f Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Tue, 14 Jan 2025 11:23:42 -0800 Subject: [PATCH 42/77] build: mv simulation code back into info --- .../TypedSignPermit/TypedSignPermit.test.tsx | 4 +-- .../TypedSignPermit/TypedSignPermit.tsx | 18 +++++----- .../Simulation/TypedSignPermit/index.ts | 0 .../ValueDisplay/ValueDisplay.styles.ts | 2 +- .../ValueDisplay/ValueDisplay.test.tsx | 16 ++++----- .../components/ValueDisplay/ValueDisplay.tsx | 36 +++++++++---------- .../components/ValueDisplay/index.ts | 0 .../Info/TypedSignV3V4/TypedSignV3V4.tsx | 2 +- 8 files changed, 39 insertions(+), 39 deletions(-) rename app/components/Views/confirmations/components/Confirm/{ => Info/TypedSignV3V4}/Simulation/TypedSignPermit/TypedSignPermit.test.tsx (86%) rename app/components/Views/confirmations/components/Confirm/{ => Info/TypedSignV3V4}/Simulation/TypedSignPermit/TypedSignPermit.tsx (83%) rename app/components/Views/confirmations/components/Confirm/{ => Info/TypedSignV3V4}/Simulation/TypedSignPermit/index.ts (100%) rename app/components/Views/confirmations/components/Confirm/{ => Info/TypedSignV3V4}/Simulation/components/ValueDisplay/ValueDisplay.styles.ts (98%) rename app/components/Views/confirmations/components/Confirm/{ => Info/TypedSignV3V4}/Simulation/components/ValueDisplay/ValueDisplay.test.tsx (86%) rename app/components/Views/confirmations/components/Confirm/{ => Info/TypedSignV3V4}/Simulation/components/ValueDisplay/ValueDisplay.tsx (79%) rename app/components/Views/confirmations/components/Confirm/{ => Info/TypedSignV3V4}/Simulation/components/ValueDisplay/index.ts (100%) diff --git a/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/TypedSignPermit.test.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignPermit/TypedSignPermit.test.tsx similarity index 86% rename from app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/TypedSignPermit.test.tsx rename to app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignPermit/TypedSignPermit.test.tsx index a68cf834fc6..175ec417d33 100644 --- a/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/TypedSignPermit.test.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignPermit/TypedSignPermit.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import renderWithProvider from '../../../../../../../util/test/renderWithProvider'; -import { typedSignV4ConfirmationState } from '../../../../../../../util/test/confirm-data-helpers'; +import renderWithProvider from '../../../../../../../../../util/test/renderWithProvider'; +import { typedSignV4ConfirmationState } from '../../../../../../../../../util/test/confirm-data-helpers'; import PermitSimulation from './TypedSignPermit'; jest.mock('../../../../../../../core/Engine', () => ({ diff --git a/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/TypedSignPermit.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignPermit/TypedSignPermit.tsx similarity index 83% rename from app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/TypedSignPermit.tsx rename to app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignPermit/TypedSignPermit.tsx index 735d792e1ec..bb326cb1463 100644 --- a/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/TypedSignPermit.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignPermit/TypedSignPermit.tsx @@ -2,15 +2,15 @@ import React from 'react'; import { StyleSheet, View } from 'react-native'; import { Hex } from '@metamask/utils'; -import { strings } from '../../../../../../../../locales/i18n'; -import { useStyles } from '../../../../../../../component-library/hooks'; -import Engine from '../../../../../../../core/Engine'; -import { safeToChecksumAddress } from '../../../../../../../util/address'; -import { PrimaryType } from '../../../../constants/signatures'; -import { useSignatureRequest } from '../../../../hooks/useSignatureRequest'; -import { parseTypedDataMessage } from '../../../../utils/signature'; -import InfoRow from '../../../UI/InfoRow'; -import InfoSection from '../../../UI/InfoRow/InfoSection'; +import { strings } from '../../../../../../../../../../locales/i18n'; +import { useStyles } from '../../../../../../../../../component-library/hooks'; +import Engine from '../../../../../../../../../core/Engine'; +import { safeToChecksumAddress } from '../../../../../../../../../util/address'; +import { PrimaryType } from '../../../../../../constants/signatures'; +import { useSignatureRequest } from '../../../../../../hooks/useSignatureRequest'; +import { parseTypedDataMessage } from '../../../../../../utils/signature'; +import InfoRow from '../../../../../UI/InfoRow'; +import InfoSection from '../../../../../UI/InfoRow/InfoSection'; import PermitSimulationValueDisplay from '../components/ValueDisplay'; const styleSheet = () => diff --git a/app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/index.ts b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignPermit/index.ts similarity index 100% rename from app/components/Views/confirmations/components/Confirm/Simulation/TypedSignPermit/index.ts rename to app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignPermit/index.ts diff --git a/app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.styles.ts b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.styles.ts similarity index 98% rename from app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.styles.ts rename to app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.styles.ts index b4269038bac..350b52322d3 100644 --- a/app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.styles.ts +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.styles.ts @@ -1,6 +1,6 @@ import { StyleSheet } from 'react-native'; import { Theme } from '@metamask/design-tokens'; -import { fontStyles, colors as importedColors } from '../../../../../../../../styles/common'; +import { fontStyles, colors as importedColors } from '../../../../../../../../../../styles/common'; const styleSheet = (colors: Theme['colors']) => StyleSheet.create({ diff --git a/app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.test.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.test.tsx similarity index 86% rename from app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.test.tsx rename to app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.test.tsx index d09c66ee160..d02ae8a5cee 100644 --- a/app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.test.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.test.tsx @@ -3,14 +3,14 @@ import React from 'react'; import { act } from '@testing-library/react-native'; import SimulationValueDisplay from './ValueDisplay'; -import { memoizedGetTokenStandardAndDetails } from '../../../../../utils/token'; -import useGetTokenStandardAndDetails from '../../../../../hooks/useGetTokenStandardAndDetails'; -import { TokenStandard } from '../../../../../../../UI/SimulationDetails/types'; -import { getTokenDetails } from '../../../../../../../../util/address'; -import { backgroundState } from '../../../../../../../../util/test/initial-root-state'; -import renderWithProvider from '../../../../../../../../util/test/renderWithProvider'; -import { useMetrics } from '../../../../../../../hooks/useMetrics'; -import { MetricsEventBuilder } from '../../../../../../../../core/Analytics/MetricsEventBuilder'; +import { memoizedGetTokenStandardAndDetails } from '../../../../../../../utils/token'; +import useGetTokenStandardAndDetails from '../../../../../../../hooks/useGetTokenStandardAndDetails'; +import { TokenStandard } from '../../../../../../../../../UI/SimulationDetails/types'; +import { getTokenDetails } from '../../../../../../../../../../util/address'; +import { backgroundState } from '../../../../../../../../../../util/test/initial-root-state'; +import renderWithProvider from '../../../../../../../../../../util/test/renderWithProvider'; +import { useMetrics } from '../../../../../../../../../hooks/useMetrics'; +import { MetricsEventBuilder } from '../../../../../../../../../../core/Analytics/MetricsEventBuilder'; const mockInitialState = { engine: { diff --git a/app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.tsx similarity index 79% rename from app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.tsx rename to app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.tsx index 3cbf85330ca..e59dcf7d43e 100644 --- a/app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/ValueDisplay.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.tsx @@ -4,32 +4,32 @@ import { useSelector } from 'react-redux'; import { NetworkClientId } from '@metamask/network-controller'; import { Hex } from '@metamask/utils'; -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 Text from '../../../../../../../../component-library/components/Texts/Text'; +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 Text from '../../../../../../../../../../component-library/components/Texts/Text'; -import { IndividualFiatDisplay } from '../../../../../../../UI/SimulationDetails/FiatDisplay/FiatDisplay'; +import { IndividualFiatDisplay } from '../../../../../../../../../UI/SimulationDetails/FiatDisplay/FiatDisplay'; import { formatAmount, formatAmountMaxPrecision, -} from '../../../../../../../UI/SimulationDetails/formatAmount'; +} from '../../../../../../../../../UI/SimulationDetails/formatAmount'; -import Box from '../../../../../../../UI/Ramp/components/Box'; -import Address from '../../../../UI/InfoRow/InfoValue/Address/Address'; +import Box from '../../../../../../../../../UI/Ramp/components/Box'; +import Address from '../../../../../../UI/InfoRow/InfoValue/Address/Address'; -import { selectContractExchangeRates } from '../../../../../../../../selectors/tokenRatesController'; +import { selectContractExchangeRates } from '../../../../../../../../../../selectors/tokenRatesController'; -import Logger from '../../../../../../../../util/Logger'; -import { shortenString } from '../../../../../../../../util/notifications/methods/common'; -import { useTheme } from '../../../../../../../../util/theme'; -import { calcTokenAmount } from '../../../../../../../../util/transactions'; +import Logger from '../../../../../../../../../../util/Logger'; +import { shortenString } from '../../../../../../../../../../util/notifications/methods/common'; +import { useTheme } from '../../../../../../../../../../util/theme'; +import { calcTokenAmount } from '../../../../../../../../../../util/transactions'; -import useGetTokenStandardAndDetails from '../../../../../hooks/useGetTokenStandardAndDetails'; -import useTrackERC20WithoutDecimalInformation from '../../../../../hooks/useTrackERC20WithoutDecimalInformation'; -import { TokenDetailsERC20 } from '../../../../../utils/token'; -import BottomModal from '../../../../UI/BottomModal'; +import useGetTokenStandardAndDetails from '../../../../../../../hooks/useGetTokenStandardAndDetails'; +import useTrackERC20WithoutDecimalInformation from '../../../../../../../hooks/useTrackERC20WithoutDecimalInformation'; +import { TokenDetailsERC20 } from '../../../../../../../utils/token'; +import BottomModal from '../../../../../../UI/BottomModal'; import styleSheet from './ValueDisplay.styles'; diff --git a/app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/index.ts b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/index.ts similarity index 100% rename from app/components/Views/confirmations/components/Confirm/Simulation/components/ValueDisplay/index.ts rename to app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/index.ts 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 367b029781e..57ba7552428 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/TypedSignV3V4.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/TypedSignV3V4.tsx @@ -4,7 +4,7 @@ import { selectUseTransactionSimulations } from '../../../../../../../selectors/ import useApprovalRequest from '../../../../hooks/useApprovalRequest'; import { isRecognizedPermit } from '../../../../utils/signature'; import InfoRowOrigin from '../Shared/InfoRowOrigin'; -import PermitSimulation from '../../Simulation/TypedSignPermit'; +import PermitSimulation from './Simulation/TypedSignPermit'; import Message from './Message'; const TypedSignV3V4 = () => { From cab0624ae9e53737cc8084a969cd22e617dfc34d Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Tue, 14 Jan 2025 21:20:05 -0800 Subject: [PATCH 43/77] test:fix: mock paths after relocation Simulation files --- .../Simulation/TypedSignPermit/TypedSignPermit.test.tsx | 2 +- .../components/ValueDisplay/ValueDisplay.test.tsx | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignPermit/TypedSignPermit.test.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignPermit/TypedSignPermit.test.tsx index 175ec417d33..2b814f9629b 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignPermit/TypedSignPermit.test.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignPermit/TypedSignPermit.test.tsx @@ -4,7 +4,7 @@ import renderWithProvider from '../../../../../../../../../util/test/renderWithP import { typedSignV4ConfirmationState } from '../../../../../../../../../util/test/confirm-data-helpers'; import PermitSimulation from './TypedSignPermit'; -jest.mock('../../../../../../../core/Engine', () => ({ +jest.mock('../../../../../../../../../core/Engine', () => ({ context: { NetworkController: { findNetworkClientIdByChainId: () => 'mainnet', 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 d02ae8a5cee..8decf038ea1 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 @@ -20,13 +20,13 @@ const mockInitialState = { const mockTrackEvent = jest.fn(); -jest.mock('../../../../../../../hooks/useMetrics'); -jest.mock('../../../../../hooks/useGetTokenStandardAndDetails'); +jest.mock('../../../../../../../../../hooks/useMetrics'); +jest.mock('../../../../../../../hooks/useGetTokenStandardAndDetails'); -jest.mock('../../../../../../../../util/address', () => ({ +jest.mock('../../../../../../../../../../util/address', () => ({ getTokenDetails: jest.fn(), - renderShortAddress: jest.requireActual('../../../../../../../../util/address').renderShortAddress + renderShortAddress: jest.requireActual('../../../../../../../../../../util/address').renderShortAddress })); describe('SimulationValueDisplay', () => { From 0505891eab881f6f5d8261214eb42c8d71659867 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 15 Jan 2025 00:43:24 -0800 Subject: [PATCH 44/77] style: add alignItems: 'center' to flexRowTokenValueAndAddress --- .../Simulation/components/ValueDisplay/ValueDisplay.styles.ts | 1 + 1 file changed, 1 insertion(+) 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 350b52322d3..6414e76e0ba 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 @@ -17,6 +17,7 @@ const styleSheet = (colors: Theme['colors']) => display: 'flex', flexDirection: 'row', justifyContent: 'flex-end', + alignItems: 'center', borderColor: importedColors.transparent, borderWidth: 0, padding: 0, From 41c0cbd90be5232c8dbebe0cfdaba9d5f286d8a1 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 15 Jan 2025 00:48:27 -0800 Subject: [PATCH 45/77] refactor: add isRecognizedPermit SignatureRequest support --- .../confirmations/utils/signature.test.ts | 65 ++++++++++++++----- .../Views/confirmations/utils/signature.ts | 12 +++- 2 files changed, 57 insertions(+), 20 deletions(-) diff --git a/app/components/Views/confirmations/utils/signature.test.ts b/app/components/Views/confirmations/utils/signature.test.ts index 13be1389591..71a4f78a9da 100644 --- a/app/components/Views/confirmations/utils/signature.test.ts +++ b/app/components/Views/confirmations/utils/signature.test.ts @@ -1,6 +1,7 @@ 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', () => { @@ -45,28 +46,56 @@ describe('Signature Utils', () => { }); describe('isRecognizedPermit', () => { - it('should return true for recognized permit types', () => { - const mockRequest: ApprovalRequest<{ data: string }> = { - requestData: { - data: JSON.stringify({ - primaryType: PRIMARY_TYPES_PERMIT[0] - }) - } - } as ApprovalRequest<{ data: string }>; + describe('when the request is an ApprovalRequest', () => { + it('should return true for recognized permit types', () => { + const mockRequest: ApprovalRequest<{ data: string }> = { + requestData: { + data: JSON.stringify({ + primaryType: PRIMARY_TYPES_PERMIT[0] + }) + } + } as ApprovalRequest<{ data: string }>; + + expect(isRecognizedPermit(mockRequest)).toBe(true); + }); - expect(isRecognizedPermit(mockRequest)).toBe(true); + it('should return false for unrecognized permit types', () => { + const mockRequest: ApprovalRequest<{ data: string }> = { + requestData: { + data: JSON.stringify({ + primaryType: 'UnrecognizedType' + }) + } + } as ApprovalRequest<{ data: string }>; + + expect(isRecognizedPermit(mockRequest)).toBe(false); + }); }); - it('should return false for unrecognized permit types', () => { - const mockRequest: ApprovalRequest<{ data: string }> = { - requestData: { - data: JSON.stringify({ - primaryType: 'UnrecognizedType' - }) - } - } as ApprovalRequest<{ data: string }>; + describe('when the request is a SignatureRequest', () => { + it('should return true for recognized permit types', () => { + const mockRequest: SignatureRequest = { + messageParams: { + data: JSON.stringify({ + primaryType: PRIMARY_TYPES_PERMIT[0] + }) + } + } as SignatureRequest; + + expect(isRecognizedPermit(mockRequest)).toBe(true); + }); - expect(isRecognizedPermit(mockRequest)).toBe(false); + it('should return false for unrecognized permit types', () => { + const mockRequest: SignatureRequest = { + messageParams: { + data: JSON.stringify({ + primaryType: 'UnrecognizedType' + }) + } + } 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..b3e8b2d7890 100644 --- a/app/components/Views/confirmations/utils/signature.ts +++ b/app/components/Views/confirmations/utils/signature.ts @@ -1,4 +1,5 @@ import { ApprovalRequest } from '@metamask/approval-controller'; +import { SignatureRequest } from '@metamask/signature-controller'; import { PRIMARY_TYPES_PERMIT } from '../constants/signatures'; /** @@ -49,7 +50,14 @@ export const parseTypedDataMessage = (dataToParse: string) => { * * @param request - The confirmation request to check */ -export const isRecognizedPermit = (approvalRequest: ApprovalRequest<{ data: string }>) => { - const { primaryType } = parseTypedDataMessage(approvalRequest.requestData.data); +export const isRecognizedPermit = (request?: ApprovalRequest<{ data: string }> | SignatureRequest) => { + if (!request) { + return false; + } + + const data = (request as ApprovalRequest<{ data: string }>).requestData?.data + || (request as SignatureRequest).messageParams?.data as string; + + const { primaryType } = parseTypedDataMessage(data); return PRIMARY_TYPES_PERMIT.includes(primaryType); }; From 64d7415feb98e5552f3fe036e02b9c45f953dda1 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 15 Jan 2025 01:34:33 -0800 Subject: [PATCH 46/77] refactor: configure decoding for SignatureController --- app/core/Engine/Engine.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/core/Engine/Engine.ts b/app/core/Engine/Engine.ts index a0fe49693c3..948fe0da39c 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: process.env.DECODING_API_URL || 'https://signature-insights.api.cx.metamask.io/v1', + // TODO: check preferences useExternalServices + isDecodeSignatureRequestEnabled: () => + preferencesController.state.useTransactionSimulations, }), LoggingController: loggingController, ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) From 770e80b13395c865b78943126ee458987da85d5b Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 15 Jan 2025 01:35:34 -0800 Subject: [PATCH 47/77] feat: add NativeValueDisplay component and update ValueDisplay component --- .../NativeValueDisplay.styles.ts | 90 ++++++++++ .../NativeValueDisplay/NativeValueDisplay.tsx | 155 ++++++++++++++++++ .../ValueDisplay/ValueDisplay.styles.ts | 5 +- .../components/ValueDisplay/ValueDisplay.tsx | 6 +- 4 files changed, 252 insertions(+), 4 deletions(-) create mode 100644 app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/NativeValueDisplay/NativeValueDisplay.styles.ts create mode 100644 app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/NativeValueDisplay/NativeValueDisplay.tsx diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/NativeValueDisplay/NativeValueDisplay.styles.ts b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/NativeValueDisplay/NativeValueDisplay.styles.ts new file mode 100644 index 00000000000..5370976bb7d --- /dev/null +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/NativeValueDisplay/NativeValueDisplay.styles.ts @@ -0,0 +1,90 @@ +import { StyleSheet } from 'react-native'; +import { Theme } from '@metamask/design-tokens'; +import { fontStyles, colors as importedColors } from '../../../../../../../../../../styles/common'; + +const styleSheet = (colors: Theme['colors']) => + StyleSheet.create({ + wrapper: { + marginLeft: 'auto', + maxWidth: '100%', + alignSelf: 'flex-end', + justifyContent: 'flex-end', + borderWidth: 0, + padding: 0, + }, + flexRowValueDisplay: { + display: 'flex', + justifyContent: 'flex-end', + flexDirection: 'row', + alignItems: 'center', + borderColor: importedColors.transparent, + borderWidth: 0, + padding: 0, + }, + fiatDisplay: { + paddingEnd: 8, + }, + tokenAssetPill: { + marginStart: 4, + }, + tokenValueTooltipContent: { + borderRadius: 12, + paddingHorizontal: 8, + paddingTop: 4, + paddingBottom: 4, + textAlign: 'center', + }, + valueAndAddress: { + paddingVertical: 4, + paddingLeft: 8, + gap: 5, + flexDirection: 'row', + alignItems: 'center', + alignSelf: 'center', + }, + valueIsCredit: { + backgroundColor: colors.success.muted, + color: colors.success.default, + }, + valueIsDebit: { + backgroundColor: colors.error.muted, + color: colors.error.default, + }, + valueModal: { + backgroundColor: colors.background.alternative, + paddingTop: 24, + paddingBottom: 34, + paddingHorizontal: 16, + borderTopLeftRadius: 8, + borderTopRightRadius: 8, + }, + valueModalHeader: { + alignItems: 'center', + display: 'flex', + flexDirection: 'row', + paddingBottom: 16, + position: 'relative', + textAlign: 'center', + width: '100%', + }, + valueModalHeaderIcon: { + position: 'absolute', + top: 0, + left: 0, + }, + valueModalHeaderText: { + color: colors.text.default, + ...fontStyles.bold, + fontSize: 14, + fontWeight: '700', + textAlign: 'center', + width: '100%', + // height of header icon + minHeight: 24, + }, + valueModalText: { + textAlign: 'center', + }, + }); + +export default styleSheet; 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..bbcc42ccf50 --- /dev/null +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/NativeValueDisplay/NativeValueDisplay.tsx @@ -0,0 +1,155 @@ +import { BigNumber } from 'bignumber.js'; +import React, { useMemo, useState } from 'react'; +import { Text,TouchableOpacity, View } from 'react-native'; +import { useSelector } from 'react-redux'; +import { Hex } from '@metamask/utils'; + +import { RootState } from '../../../../../../../../../../reducers'; +import { selectConversionRate } 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 Box from '../../../../../../../../../UI/Ramp/components/Box'; +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 { calcTokenAmount } from '../../../../../../../../../../util/transactions'; +import BottomModal from '../../../../../../UI/BottomModal'; + +import styleSheet from './NativeValueDisplay.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<PermitSimulationValueDisplayParams> = ({ + chainId, + credit, + debit, + labelChangeType, + value, +}) => { + const [hasValueModalOpen, setHasValueModalOpen] = useState(false); + + const { colors } = useTheme(); + const styles = styleSheet(colors); + + const conversionRate = useSelector((state: RootState) => + selectConversionRate(state, chainId), + ); + + const { fiatValue, tokenValue, tokenValueMaxPrecision } = useMemo(() => { + if (!value) { + return { tokenValue: null, tokenValueMaxPrecision: null }; + } + + const tokenAmount = calcTokenAmount(value, NATIVE_DECIMALS); + + return { + fiatValue: conversionRate + ? new BigNumber(tokenAmount).times(String(conversionRate)).toNumber() + : undefined, + tokenValue: formatAmount('en-US', tokenAmount), + tokenValueMaxPrecision: formatAmountMaxPrecision('en-US', tokenAmount), + }; + }, [conversionRate, value]); + + function handlePressTokenValue() { + setHasValueModalOpen(true); + } + + return ( + <View style={styles.wrapper}> + <View style={styles.flexRowValueDisplay}> + <View style={styles.valueAndAddress}> + <ButtonPill + onPress={handlePressTokenValue} + onPressIn={handlePressTokenValue} + onPressOut={handlePressTokenValue} + style={[credit && styles.valueIsCredit, debit && styles.valueIsDebit]} + > + <Text> + {credit && '+ '} + {debit && '- '} + {tokenValue !== null && + shortenString(tokenValue || '', { + truncatedCharLimit: 15, + truncatedStartChars: 15, + truncatedEndChars: 0, + skipCharacterInEnd: true, + })} + </Text> + </ButtonPill> + <Box compact noBorder style={styles.tokenAssetPill}> + <AssetPill asset={{ chainId, type: AssetType.Native }} /> + </Box> + </View> + </View> + <Box compact noBorder style={styles.fiatDisplay}> + {/** + TODO - add fiat shorten prop after tooltip logic has been updated + {@see {@link https://github.com/MetaMask/metamask-mobile/issues/12656} + */} + {fiatValue !== undefined && ( + <IndividualFiatDisplay fiatAmount={fiatValue} /> + )} + </Box> + {hasValueModalOpen && ( + /** + * TODO replace BottomModal instances with BottomSheet + * {@see {@link https://github.com/MetaMask/metamask-mobile/issues/12656}} + */ + <BottomModal onClose={() => setHasValueModalOpen(false)}> + <TouchableOpacity + activeOpacity={1} + onPress={() => setHasValueModalOpen(false)} + > + <View style={styles.valueModal} > + <View style={styles.valueModalHeader}> + <ButtonIcon + iconColor={IconColor.Default} + size={ButtonIconSizes.Sm} + style={styles.valueModalHeaderIcon} + onPress={() => setHasValueModalOpen(false)} + iconName={IconName.ArrowLeft} + /> + <Text style={styles.valueModalHeaderText}> + {labelChangeType} + </Text> + </View> + <Text style={styles.valueModalText}> + {tokenValueMaxPrecision} + </Text> + </View> + </TouchableOpacity> + </BottomModal> + )} + </View> + ); +}; + +export default 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..f27d9d879f9 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', @@ -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.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.tsx index e59dcf7d43e..2e07336f83a 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 @@ -133,6 +133,8 @@ const SimulationValueDisplay: React.FC< } function handlePressTokenValue() { + if (!tokenId) { return; } + setHasValueModalOpen(true); } @@ -164,8 +166,8 @@ const SimulationValueDisplay: React.FC< </Box> </View> </Box> - <Box compact noBorder> - {/* + <Box compact noBorder style={styles.fiatDisplay}> + {/** TODO - add fiat shorten prop after tooltip logic has been updated {@see {@link https://github.com/MetaMask/metamask-mobile/issues/12656} */} From 95387ba5f69a95a5c8213f32e6f3a71698d4fcd6 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 15 Jan 2025 01:35:56 -0800 Subject: [PATCH 48/77] refactor: update type ApprovalRequestType to include decoding props --- .../Views/confirmations/hooks/useApprovalRequest.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/components/Views/confirmations/hooks/useApprovalRequest.ts b/app/components/Views/confirmations/hooks/useApprovalRequest.ts index 0eb31b4a468..d7a54c4130b 100644 --- a/app/components/Views/confirmations/hooks/useApprovalRequest.ts +++ b/app/components/Views/confirmations/hooks/useApprovalRequest.ts @@ -5,10 +5,14 @@ import { useSelector } from 'react-redux'; import { selectPendingApprovals } from '../../../../selectors/approvalController'; import { cloneDeep, isEqual } from 'lodash'; import { ApprovalRequest } from '@metamask/approval-controller'; +import { DecodingData } from '@metamask/signature-controller'; // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any -type ApprovalRequestType = ApprovalRequest<any>; +type ApprovalRequestType = ApprovalRequest<any> & { + decodingData?: DecodingData; + decodingLoading?: boolean; +};; const useApprovalRequest = () => { const pendingApprovals = useSelector(selectPendingApprovals, isEqual); From 1dc8c13edffd25206a0597a99c8d1de3a0eebbe2 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 15 Jan 2025 01:38:24 -0800 Subject: [PATCH 49/77] feat: add Decoding Simulation components - update selectConversionRate to allow chainId param - create useTypesSignSimulationEnabled hook --- .../TypedSignV3V4/Simulation/Simulation.tsx | 28 +++ .../Simulation/Static/Static.tsx | 55 +++++ .../TypedSignV3V4/Simulation/Static/index.ts | 1 + .../TypedSignDecoded/TypedSignDecoded.tsx | 206 ++++++++++++++++++ .../Simulation/TypedSignDecoded/index.ts | 1 + .../components/NativeValueDisplay/index.ts | 1 + .../Info/TypedSignV3V4/Simulation/index.ts | 1 + .../Info/TypedSignV3V4/TypedSignV3V4.tsx | 23 +- .../hooks/useTypedSignSimulationEnabled.ts | 65 ++++++ app/selectors/currencyRateController.ts | 5 +- locales/languages/en.json | 11 +- 11 files changed, 375 insertions(+), 22 deletions(-) create mode 100644 app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/Simulation.tsx create mode 100644 app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/Static/Static.tsx create mode 100644 app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/Static/index.ts create mode 100644 app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/TypedSignDecoded.tsx create mode 100644 app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/index.ts create mode 100644 app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/NativeValueDisplay/index.ts create mode 100644 app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/index.ts create mode 100644 app/components/Views/confirmations/hooks/useTypedSignSimulationEnabled.ts 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..955b825b574 --- /dev/null +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/Simulation.tsx @@ -0,0 +1,28 @@ +import React from 'react'; + +import useTypesSignSimulationEnabled 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<object> = () => { + const signatureRequest = useSignatureRequest(); + const isPermit = isRecognizedPermit(signatureRequest); + const isSimulationSupported = useTypesSignSimulationEnabled(); + + if (!isSimulationSupported || !signatureRequest) { + return null; + } + + const { decodingData, decodingLoading } = signatureRequest; + const hasDecodingData = !((!decodingLoading && decodingData === undefined) || decodingData?.error); + + if (!hasDecodingData && isPermit) { + return <PermitSimulation />; + } + + return <DecodedSimulation />; +}; + +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( + <View style={isCollapsed ? styles.base : {}}> + <InfoSection> + <InfoRow label={title} tooltip={titleTooltip}> + {description} + </InfoRow> + {isLoading ? ( + <View style={styles.loaderContainer}> + <Loader size={'small'} /> + </View> + ) : ( + simulationElements + )} + </InfoSection> + </View> + ); +}; + +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.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/TypedSignDecoded.tsx new file mode 100644 index 00000000000..01494699c6c --- /dev/null +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/TypedSignDecoded.tsx @@ -0,0 +1,206 @@ +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'; + +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; + // todo: add + // const canDisplayValueAsUnlimited = + // assetType === TokenStandard.ERC20 && + // (changeType === DecodingDataChangeType.Approve || + // changeType === DecodingDataChangeType.Revoke); + + const changeLabel = shouldDisplayLabel + ? getStateChangeLabelMap(changeType, nftTransactionType) + : ''; + + return ( + <InfoRow + label={changeLabel} + tooltip={tooltip} + > + {(assetType === TokenStandard.ERC20 || + assetType === TokenStandard.ERC721 || + assetType === TokenStandard.ERC1155) && ( + <SimulationValueDisplay + labelChangeType={changeType} + tokenContract={contractAddress} + value={amount} + chainId={chainId} + tokenId={tokenID} + credit={ + nftTransactionType !== StateChangeType.NFTListingReceive && + changeType === DecodingDataChangeType.Receive + } + debit={changeType === DecodingDataChangeType.Transfer} + // todo: add + // canDisplayValueAsUnlimited={canDisplayValueAsUnlimited} + /> + )} + {assetType === 'NATIVE' && ( + <NativeValueDisplay + value={amount} + chainId={chainId} + credit={ + nftTransactionType !== StateChangeType.NFTListingReceive && + changeType === DecodingDataChangeType.Receive + } + debit={changeType === DecodingDataChangeType.Transfer} + labelChangeType={changeLabel} + /> + )} + </InfoRow> + ); +}; + +const DecodedSimulation: React.FC<object> = () => { + 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<string, DecodingDataStateChange[]> = ( + orderedStateChanges ?? [] + ).reduce<Record<string, DecodingDataStateChange[]>>( + (result, stateChange) => { + result[stateChange.changeType] = [ + ...(result[stateChange.changeType] ?? []), + stateChange, + ]; + return result; + }, + {}, + ); + + return Object.entries(stateChangesGrouped).flatMap(([_, changeList]) => + changeList.map((change: DecodingDataStateChange, index: number) => ( + <StateChangeRow + key={`${change.changeType}-${index}`} + stateChangeList={decodingData?.stateChanges ?? []} + stateChange={change} + chainId={chainId} + shouldDisplayLabel={index === 0} + /> + )), + ); + }, [chainId, decodingData?.stateChanges]); + + return ( + <StaticSimulation + title={strings('confirm.simulation.title')} + titleTooltip={strings('confirm.simulation.tooltip')} + simulationElements={ + stateChangeFragment.length ? ( + stateChangeFragment + ) : ( + <View style={{ paddingHorizontal: 8, paddingBottom: 8 }}> + <Text>{strings('confirm.simulation.unavailable')}</Text> + </View> + ) + } + 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/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/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 && <PermitSimulation />} + <TypedSignV3V4Simulation /> <InfoRowOrigin /> <Message /> </> ); -}; export default TypedSignV3V4; diff --git a/app/components/Views/confirmations/hooks/useTypedSignSimulationEnabled.ts b/app/components/Views/confirmations/hooks/useTypedSignSimulationEnabled.ts new file mode 100644 index 00000000000..d05a2447b9f --- /dev/null +++ b/app/components/Views/confirmations/hooks/useTypedSignSimulationEnabled.ts @@ -0,0 +1,65 @@ +import { useSelector } from 'react-redux'; +import { ApprovalRequest } from '@metamask/approval-controller'; +import { MESSAGE_TYPE } from '../../../../core/createTracingMiddleware'; +import { selectUseTransactionSimulations } from '../../../../selectors/preferencesController'; +import { isRecognizedPermit, parseTypedDataMessage } from '../utils/signature'; +import useApprovalRequest from './useApprovalRequest'; + +type ApprovalRequestType = ApprovalRequest<{ data: string }>; + +const NON_PERMIT_SUPPORTED_TYPES_SIGNS = [ + { + domainName: 'Seaport', + primaryTypeList: ['BulkOrder'], + versionList: ['1.4', '1.5', '1.6'], + }, + { + domainName: 'Seaport', + primaryTypeList: ['OrderComponents'], + }, +]; + +const isNonPermitSupportedByDecodingAPI = ( + approvalRequest: ApprovalRequestType, +) => { + const data = approvalRequest.requestData?.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 default function useTypesSignSimulationEnabled() { + const { approvalRequest } = useApprovalRequest(); + const useTransactionSimulations = useSelector( + selectUseTransactionSimulations, + ); + + if (!approvalRequest) { + return undefined; + } + + const signatureMethod = approvalRequest?.requestData?.signatureMethod; + const isTypedSignV3V4 = + signatureMethod === MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4 || + signatureMethod === MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V3; + const isPermit = isRecognizedPermit(approvalRequest); + + const nonPermitSupportedByDecodingAPI: boolean = + isTypedSignV3V4 && isNonPermitSupportedByDecodingAPI(approvalRequest); + + return ( + useTransactionSimulations && + isTypedSignV3V4 && + (isPermit || nonPermitSupportedByDecodingAPI) + ); +} diff --git a/app/selectors/currencyRateController.ts b/app/selectors/currencyRateController.ts index 715ebeb4e8b..0104e8817ac 100644 --- a/app/selectors/currencyRateController.ts +++ b/app/selectors/currencyRateController.ts @@ -10,14 +10,17 @@ const selectCurrencyRateControllerState = (state: RootState) => export const selectConversionRate = createSelector( selectCurrencyRateControllerState, selectChainId, + (_: RootState, chainId?: string) => chainId, // Optional chainId parameter selectTicker, (state: RootState) => state.settings.showFiatOnTestnets, ( currencyRateControllerState: CurrencyRateState, - chainId: string, + currentChainId: string, + providedChainId: string | undefined, ticker: string, showFiatOnTestnets, ) => { + const chainId = providedChainId || currentChainId; if (chainId && isTestNet(chainId) && !showFiatOnTestnets) { return undefined; } diff --git a/locales/languages/en.json b/locales/languages/en.json index 861594b3a49..926e11a77f2 100644 --- a/locales/languages/en.json +++ b/locales/languages/en.json @@ -3607,12 +3607,21 @@ "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_nft_listing": "Listing price", + "label_change_type_bidding": "You bid", + "label_change_type_listing": "You list", "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" } }, "change_in_simulation_modal": { From 724bcbe648411d5ff9cfcd8b10aa166ca179b222 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 15 Jan 2025 10:32:11 -0800 Subject: [PATCH 50/77] fix: selectConversionRate optional param --- app/selectors/currencyRateController.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/selectors/currencyRateController.ts b/app/selectors/currencyRateController.ts index 0104e8817ac..6f7d4004b6a 100644 --- a/app/selectors/currencyRateController.ts +++ b/app/selectors/currencyRateController.ts @@ -10,15 +10,15 @@ const selectCurrencyRateControllerState = (state: RootState) => export const selectConversionRate = createSelector( selectCurrencyRateControllerState, selectChainId, - (_: RootState, chainId?: string) => chainId, // Optional chainId parameter selectTicker, (state: RootState) => state.settings.showFiatOnTestnets, + (_: RootState, chainId?: string) => chainId, // Optional chainId parameter ( currencyRateControllerState: CurrencyRateState, currentChainId: string, - providedChainId: string | undefined, ticker: string, showFiatOnTestnets, + providedChainId?: string | undefined, ) => { const chainId = providedChainId || currentChainId; if (chainId && isTestNet(chainId) && !showFiatOnTestnets) { From b71c8ef2f9ebf73baa24f99fcc4509465ddf6e95 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 15 Jan 2025 11:47:10 -0800 Subject: [PATCH 51/77] refactor: selectCurrencyRateControllerState optionalize showFiatOnTestnets --- app/selectors/currencyRateController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/selectors/currencyRateController.ts b/app/selectors/currencyRateController.ts index 6f7d4004b6a..dcea5cc05cf 100644 --- a/app/selectors/currencyRateController.ts +++ b/app/selectors/currencyRateController.ts @@ -11,7 +11,7 @@ export const selectConversionRate = createSelector( selectCurrencyRateControllerState, selectChainId, selectTicker, - (state: RootState) => state.settings.showFiatOnTestnets, + (state: RootState) => state?.settings?.showFiatOnTestnets ?? false, (_: RootState, chainId?: string) => chainId, // Optional chainId parameter ( currencyRateControllerState: CurrencyRateState, From 2f8a2b40afc7797a22f1dc23bdd4c11788966b63 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 15 Jan 2025 11:50:15 -0800 Subject: [PATCH 52/77] fix: tslint --- .../Simulation/TypedSignDecoded/TypedSignDecoded.tsx | 9 ++++++++- .../Views/confirmations/hooks/useApprovalRequest.ts | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) 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 index 01494699c6c..3e712edf440 100644 --- 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 @@ -16,6 +16,13 @@ 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', @@ -192,7 +199,7 @@ const DecodedSimulation: React.FC<object> = () => { stateChangeFragment.length ? ( stateChangeFragment ) : ( - <View style={{ paddingHorizontal: 8, paddingBottom: 8 }}> + <View style={styles.unavailableContainer}> <Text>{strings('confirm.simulation.unavailable')}</Text> </View> ) diff --git a/app/components/Views/confirmations/hooks/useApprovalRequest.ts b/app/components/Views/confirmations/hooks/useApprovalRequest.ts index d7a54c4130b..f7cd5382f39 100644 --- a/app/components/Views/confirmations/hooks/useApprovalRequest.ts +++ b/app/components/Views/confirmations/hooks/useApprovalRequest.ts @@ -12,7 +12,7 @@ import { DecodingData } from '@metamask/signature-controller'; type ApprovalRequestType = ApprovalRequest<any> & { decodingData?: DecodingData; decodingLoading?: boolean; -};; +}; const useApprovalRequest = () => { const pendingApprovals = useSelector(selectPendingApprovals, isEqual); From ad6945a6d4e2e229947ac2c8df0f1e2de6deed06 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 15 Jan 2025 12:05:28 -0800 Subject: [PATCH 53/77] fix: replace Ramp/Box with View components --- .../NativeValueDisplay/NativeValueDisplay.tsx | 9 ++++----- .../components/ValueDisplay/ValueDisplay.tsx | 19 +++++++++---------- 2 files changed, 13 insertions(+), 15 deletions(-) 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 index bbcc42ccf50..59d3daf0480 100644 --- 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 @@ -13,7 +13,6 @@ import { ButtonIconSizes } from '../../../../../../../../../../component-library import ButtonIcon from '../../../../../../../../../../component-library/components/Buttons/ButtonIcon/ButtonIcon'; import { IconName , IconColor } from '../../../../../../../../../../component-library/components/Icons/Icon'; -import Box from '../../../../../../../../../UI/Ramp/components/Box'; import AssetPill from '../../../../../../../../../UI/SimulationDetails/AssetPill/AssetPill'; import { IndividualFiatDisplay } from '../../../../../../../../../UI/SimulationDetails/FiatDisplay/FiatDisplay'; import { @@ -104,12 +103,12 @@ const NativeValueDisplay: React.FC<PermitSimulationValueDisplayParams> = ({ })} </Text> </ButtonPill> - <Box compact noBorder style={styles.tokenAssetPill}> + <View style={styles.tokenAssetPill}> <AssetPill asset={{ chainId, type: AssetType.Native }} /> - </Box> + </View> </View> </View> - <Box compact noBorder style={styles.fiatDisplay}> + <View style={styles.fiatDisplay}> {/** TODO - add fiat shorten prop after tooltip logic has been updated {@see {@link https://github.com/MetaMask/metamask-mobile/issues/12656} @@ -117,7 +116,7 @@ const NativeValueDisplay: React.FC<PermitSimulationValueDisplayParams> = ({ {fiatValue !== undefined && ( <IndividualFiatDisplay fiatAmount={fiatValue} /> )} - </Box> + </View> {hasValueModalOpen && ( /** * TODO replace BottomModal instances with BottomSheet 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 2e07336f83a..ba987bb5147 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 @@ -16,7 +16,6 @@ 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'; @@ -134,13 +133,13 @@ const SimulationValueDisplay: React.FC< function handlePressTokenValue() { if (!tokenId) { return; } - + setHasValueModalOpen(true); } return ( - <Box style={styles.wrapper}> - <Box style={styles.flexRowTokenValueAndAddress}> + <View style={styles.wrapper}> + <View style={styles.flexRowTokenValueAndAddress}> <View style={styles.valueAndAddress}> <ButtonPill onPress={handlePressTokenValue} @@ -161,18 +160,18 @@ const SimulationValueDisplay: React.FC< {tokenId && `#${tokenId}`} </Text> </ButtonPill> - <Box compact noBorder style={styles.tokenAddress}> + <View style={styles.tokenAddress}> <Address address={tokenContract} chainId={chainId} /> - </Box> + </View> </View> - </Box> - <Box compact noBorder style={styles.fiatDisplay}> + </View> + <View style={styles.fiatDisplay}> {/** TODO - add fiat shorten prop after tooltip logic has been updated {@see {@link https://github.com/MetaMask/metamask-mobile/issues/12656} */} {fiatValue && <IndividualFiatDisplay fiatAmount={fiatValue} /* shorten*/ />} - </Box> + </View> {hasValueModalOpen && ( /** * TODO replace BottomModal instances with BottomSheet @@ -203,7 +202,7 @@ const SimulationValueDisplay: React.FC< </TouchableOpacity> </BottomModal> )} - </Box> + </View> ); }; From 4273dab372b27c561e8b54cef0d327e9c4378086 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 15 Jan 2025 15:02:32 -0800 Subject: [PATCH 54/77] fix: Revoke en i18n casing --- locales/languages/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/languages/en.json b/locales/languages/en.json index 926e11a77f2..9f88888bc64 100644 --- a/locales/languages/en.json +++ b/locales/languages/en.json @@ -3616,7 +3616,7 @@ "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_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", From 786ed460fa002c8597431a0471661ec7918aea9f Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 15 Jan 2025 15:06:14 -0800 Subject: [PATCH 55/77] fix: i18n en alphabetical ordering --- locales/languages/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/languages/en.json b/locales/languages/en.json index 9f88888bc64..f9bb4d1f0c5 100644 --- a/locales/languages/en.json +++ b/locales/languages/en.json @@ -3610,9 +3610,9 @@ "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_nft_listing": "Listing price", "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", From 77705196e86f342ee3ad17558ecbf2eb1fc86d91 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 15 Jan 2025 15:11:08 -0800 Subject: [PATCH 56/77] fix: optional chainId selectConversionRate selector --- app/selectors/currencyRateController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/selectors/currencyRateController.ts b/app/selectors/currencyRateController.ts index dcea5cc05cf..3f6c1bd1a6d 100644 --- a/app/selectors/currencyRateController.ts +++ b/app/selectors/currencyRateController.ts @@ -12,7 +12,7 @@ export const selectConversionRate = createSelector( selectChainId, selectTicker, (state: RootState) => state?.settings?.showFiatOnTestnets ?? false, - (_: RootState, chainId?: string) => chainId, // Optional chainId parameter + (_: RootState, chainId?: string) => chainId ?? undefined, // Optional chainId parameter ( currencyRateControllerState: CurrencyRateState, currentChainId: string, From b19b59cc37d7a17374d25eb34edfefcf8fd01efe Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 15 Jan 2025 15:12:09 -0800 Subject: [PATCH 57/77] refactor: format code Simulation --- .../Confirm/Info/TypedSignV3V4/Simulation/Simulation.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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 index 955b825b574..547033a496f 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/Simulation.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/Simulation.tsx @@ -10,13 +10,16 @@ const TypedSignV3V4Simulation: React.FC<object> = () => { const signatureRequest = useSignatureRequest(); const isPermit = isRecognizedPermit(signatureRequest); const isSimulationSupported = useTypesSignSimulationEnabled(); - + if (!isSimulationSupported || !signatureRequest) { return null; } const { decodingData, decodingLoading } = signatureRequest; - const hasDecodingData = !((!decodingLoading && decodingData === undefined) || decodingData?.error); + const hasDecodingData = !( + (!decodingLoading && decodingData === undefined) || + decodingData?.error + ); if (!hasDecodingData && isPermit) { return <PermitSimulation />; From 2c7875205792581017dbcd20ad6097f3c26140af Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 15 Jan 2025 15:20:04 -0800 Subject: [PATCH 58/77] refactor: revert selectConversionRate param updates + fix lint --- app/selectors/currencyRateController.test.ts | 3 +++ app/selectors/currencyRateController.ts | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/selectors/currencyRateController.test.ts b/app/selectors/currencyRateController.test.ts index e48f6b957a4..e6462afc21a 100644 --- a/app/selectors/currencyRateController.test.ts +++ b/app/selectors/currencyRateController.test.ts @@ -35,6 +35,7 @@ describe('CurrencyRateController Selectors', () => { mockChainId as `0x${string}`, mockTicker, false, + undefined, ); expect(result).toBeUndefined(); }); @@ -47,6 +48,7 @@ describe('CurrencyRateController Selectors', () => { mockChainId as `0x${string}`, mockTicker, true, + undefined, ); expect(result).toBe(3000); }); @@ -59,6 +61,7 @@ describe('CurrencyRateController Selectors', () => { mockChainId as `0x${string}`, '', true, + undefined, ); expect(result).toBeUndefined(); }); diff --git a/app/selectors/currencyRateController.ts b/app/selectors/currencyRateController.ts index 3f6c1bd1a6d..6f7d4004b6a 100644 --- a/app/selectors/currencyRateController.ts +++ b/app/selectors/currencyRateController.ts @@ -11,8 +11,8 @@ export const selectConversionRate = createSelector( selectCurrencyRateControllerState, selectChainId, selectTicker, - (state: RootState) => state?.settings?.showFiatOnTestnets ?? false, - (_: RootState, chainId?: string) => chainId ?? undefined, // Optional chainId parameter + (state: RootState) => state.settings.showFiatOnTestnets, + (_: RootState, chainId?: string) => chainId, // Optional chainId parameter ( currencyRateControllerState: CurrencyRateState, currentChainId: string, From f3c81b9a2360ab36ba64f54ca395b41bf1b6a00a Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 15 Jan 2025 15:49:39 -0800 Subject: [PATCH 59/77] refactor: useTypesSignSimulationEnabled -> useTypedSignSimulationEnabled --- .../Confirm/Info/TypedSignV3V4/Simulation/Simulation.tsx | 4 ++-- .../confirmations/hooks/useTypedSignSimulationEnabled.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) 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 index 547033a496f..2d6d9002687 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/Simulation.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/Simulation.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import useTypesSignSimulationEnabled from '../../../../../hooks/useTypedSignSimulationEnabled'; +import useTypedSignSimulationEnabled from '../../../../../hooks/useTypedSignSimulationEnabled'; import { isRecognizedPermit } from '../../../../../utils/signature'; import { useSignatureRequest } from '../../../../../hooks/useSignatureRequest'; import DecodedSimulation from './TypedSignDecoded'; @@ -9,7 +9,7 @@ import PermitSimulation from './TypedSignPermit'; const TypedSignV3V4Simulation: React.FC<object> = () => { const signatureRequest = useSignatureRequest(); const isPermit = isRecognizedPermit(signatureRequest); - const isSimulationSupported = useTypesSignSimulationEnabled(); + const isSimulationSupported = useTypedSignSimulationEnabled(); if (!isSimulationSupported || !signatureRequest) { return null; diff --git a/app/components/Views/confirmations/hooks/useTypedSignSimulationEnabled.ts b/app/components/Views/confirmations/hooks/useTypedSignSimulationEnabled.ts index d05a2447b9f..c9c3491cedc 100644 --- a/app/components/Views/confirmations/hooks/useTypedSignSimulationEnabled.ts +++ b/app/components/Views/confirmations/hooks/useTypedSignSimulationEnabled.ts @@ -38,7 +38,7 @@ const isNonPermitSupportedByDecodingAPI = ( ); }; -export default function useTypesSignSimulationEnabled() { +export default function useTypedSignSimulationEnabled() { const { approvalRequest } = useApprovalRequest(); const useTransactionSimulations = useSelector( selectUseTransactionSimulations, From 9f96a79d1120c884a6944ce6b4d2ba45c560f8a6 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 15 Jan 2025 15:57:57 -0800 Subject: [PATCH 60/77] refactor: update useTypedSignSimulationEnabled to use useSignatureRequest --- .../hooks/useTypedSignSimulationEnabled.ts | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/app/components/Views/confirmations/hooks/useTypedSignSimulationEnabled.ts b/app/components/Views/confirmations/hooks/useTypedSignSimulationEnabled.ts index c9c3491cedc..c0d85ee8297 100644 --- a/app/components/Views/confirmations/hooks/useTypedSignSimulationEnabled.ts +++ b/app/components/Views/confirmations/hooks/useTypedSignSimulationEnabled.ts @@ -1,11 +1,9 @@ import { useSelector } from 'react-redux'; -import { ApprovalRequest } from '@metamask/approval-controller'; -import { MESSAGE_TYPE } from '../../../../core/createTracingMiddleware'; +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 useApprovalRequest from './useApprovalRequest'; - -type ApprovalRequestType = ApprovalRequest<{ data: string }>; +import { useSignatureRequest } from './useSignatureRequest'; const NON_PERMIT_SUPPORTED_TYPES_SIGNS = [ { @@ -20,9 +18,9 @@ const NON_PERMIT_SUPPORTED_TYPES_SIGNS = [ ]; const isNonPermitSupportedByDecodingAPI = ( - approvalRequest: ApprovalRequestType, + signatureRequest: SignatureRequest, ) => { - const data = approvalRequest.requestData?.data as string; + const data = signatureRequest.messageParams?.data as string; if (!data) { return false; } const { @@ -39,23 +37,26 @@ const isNonPermitSupportedByDecodingAPI = ( }; export default function useTypedSignSimulationEnabled() { - const { approvalRequest } = useApprovalRequest(); + const signatureRequest = useSignatureRequest(); const useTransactionSimulations = useSelector( selectUseTransactionSimulations, ); - if (!approvalRequest) { + if (!signatureRequest) { return undefined; } - const signatureMethod = approvalRequest?.requestData?.signatureMethod; - const isTypedSignV3V4 = - signatureMethod === MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4 || - signatureMethod === MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V3; - const isPermit = isRecognizedPermit(approvalRequest); + 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(approvalRequest); + isTypedSignV3V4 && isNonPermitSupportedByDecodingAPI(signatureRequest); return ( useTransactionSimulations && From 43d7a0fcf51b3bc27767b9943e702e80b956ff2e Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 15 Jan 2025 16:05:53 -0800 Subject: [PATCH 61/77] fix: mv DECODING_API_URL to AppConstants --- .js.env.example | 3 +++ app/core/AppConstants.ts | 1 + app/core/Engine/Engine.ts | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) 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/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 948fe0da39c..740c50e7b45 100644 --- a/app/core/Engine/Engine.ts +++ b/app/core/Engine/Engine.ts @@ -1441,7 +1441,7 @@ 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: process.env.DECODING_API_URL || 'https://signature-insights.api.cx.metamask.io/v1', + decodingApiUrl: AppConstants.DECODING_API_URL, // TODO: check preferences useExternalServices isDecodeSignatureRequestEnabled: () => preferencesController.state.useTransactionSimulations, From af9e69b7c7f419ab5f9c9deec3891500cb1608e1 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 15 Jan 2025 16:08:43 -0800 Subject: [PATCH 62/77] fix: update isRecognizedPermit to check only signature request types --- .../confirmations/utils/signature.test.ts | 64 ++++++------------- .../Views/confirmations/utils/signature.ts | 7 +- 2 files changed, 21 insertions(+), 50 deletions(-) diff --git a/app/components/Views/confirmations/utils/signature.test.ts b/app/components/Views/confirmations/utils/signature.test.ts index 71a4f78a9da..c0d39c33bbd 100644 --- a/app/components/Views/confirmations/utils/signature.test.ts +++ b/app/components/Views/confirmations/utils/signature.test.ts @@ -46,56 +46,28 @@ describe('Signature Utils', () => { }); describe('isRecognizedPermit', () => { - describe('when the request is an ApprovalRequest', () => { - it('should return true for recognized permit types', () => { - const mockRequest: ApprovalRequest<{ data: string }> = { - requestData: { - data: JSON.stringify({ - primaryType: PRIMARY_TYPES_PERMIT[0] - }) - } - } as ApprovalRequest<{ data: string }>; - - expect(isRecognizedPermit(mockRequest)).toBe(true); - }); - - it('should return false for unrecognized permit types', () => { - const mockRequest: ApprovalRequest<{ data: string }> = { - requestData: { - data: JSON.stringify({ - primaryType: 'UnrecognizedType' - }) - } - } as ApprovalRequest<{ data: string }>; + it('should return true for recognized permit types', () => { + const mockRequest: SignatureRequest = { + messageParams: { + data: JSON.stringify({ + primaryType: PRIMARY_TYPES_PERMIT[0] + }) + } + } as SignatureRequest; - expect(isRecognizedPermit(mockRequest)).toBe(false); - }); + expect(isRecognizedPermit(mockRequest)).toBe(true); }); - describe('when the request is a SignatureRequest', () => { - it('should return true for recognized permit types', () => { - const mockRequest: SignatureRequest = { - messageParams: { - data: JSON.stringify({ - primaryType: PRIMARY_TYPES_PERMIT[0] - }) - } - } as SignatureRequest; - - expect(isRecognizedPermit(mockRequest)).toBe(true); - }); - - it('should return false for unrecognized permit types', () => { - const mockRequest: SignatureRequest = { - messageParams: { - data: JSON.stringify({ - primaryType: 'UnrecognizedType' - }) - } - } as SignatureRequest; + it('should return false for unrecognized permit types', () => { + const mockRequest: SignatureRequest = { + messageParams: { + data: JSON.stringify({ + primaryType: 'UnrecognizedType' + }) + } + } as SignatureRequest; - expect(isRecognizedPermit(mockRequest)).toBe(false); - }); + expect(isRecognizedPermit(mockRequest)).toBe(false); }); }); }); diff --git a/app/components/Views/confirmations/utils/signature.ts b/app/components/Views/confirmations/utils/signature.ts index b3e8b2d7890..732e91ecb94 100644 --- a/app/components/Views/confirmations/utils/signature.ts +++ b/app/components/Views/confirmations/utils/signature.ts @@ -48,15 +48,14 @@ 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 = (request?: ApprovalRequest<{ data: string }> | SignatureRequest) => { +export const isRecognizedPermit = (request: SignatureRequest) => { if (!request) { return false; } - const data = (request as ApprovalRequest<{ data: string }>).requestData?.data - || (request as SignatureRequest).messageParams?.data as string; + const data = (request as SignatureRequest).messageParams?.data as string; const { primaryType } = parseTypedDataMessage(data); return PRIMARY_TYPES_PERMIT.includes(primaryType); From 18999f8ff74759b9ccd89934e54561bde857b23c Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 15 Jan 2025 16:11:01 -0800 Subject: [PATCH 63/77] fix: rm unused ApprovalRequest import --- app/components/Views/confirmations/utils/signature.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/app/components/Views/confirmations/utils/signature.ts b/app/components/Views/confirmations/utils/signature.ts index 732e91ecb94..9035a2b3087 100644 --- a/app/components/Views/confirmations/utils/signature.ts +++ b/app/components/Views/confirmations/utils/signature.ts @@ -1,4 +1,3 @@ -import { ApprovalRequest } from '@metamask/approval-controller'; import { SignatureRequest } from '@metamask/signature-controller'; import { PRIMARY_TYPES_PERMIT } from '../constants/signatures'; From 29fbf83546cd5ecf013bbc0566cfbd320520c48d Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 15 Jan 2025 16:11:52 -0800 Subject: [PATCH 64/77] fix: revert ApprovalRequestType type update --- .../Views/confirmations/hooks/useApprovalRequest.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/components/Views/confirmations/hooks/useApprovalRequest.ts b/app/components/Views/confirmations/hooks/useApprovalRequest.ts index f7cd5382f39..b967f132508 100644 --- a/app/components/Views/confirmations/hooks/useApprovalRequest.ts +++ b/app/components/Views/confirmations/hooks/useApprovalRequest.ts @@ -9,10 +9,7 @@ import { DecodingData } from '@metamask/signature-controller'; // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any -type ApprovalRequestType = ApprovalRequest<any> & { - decodingData?: DecodingData; - decodingLoading?: boolean; -}; +type ApprovalRequestType = ApprovalRequest<any>; const useApprovalRequest = () => { const pendingApprovals = useSelector(selectPendingApprovals, isEqual); From f42522df40b0bc8d3d2c16242490cb882ae6bf20 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 15 Jan 2025 18:08:51 -0800 Subject: [PATCH 65/77] refactor: rm useMemo from ValueDisplay + add isNumberValue util --- .../NativeValueDisplay/NativeValueDisplay.tsx | 29 ++++++--------- .../components/ValueDisplay/ValueDisplay.tsx | 33 ++++++----------- app/util/number/index.js | 18 ++++++++- app/util/number/index.test.ts | 37 +++++++++++++++++++ 4 files changed, 78 insertions(+), 39 deletions(-) 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 index 59d3daf0480..c232fff6131 100644 --- 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 @@ -1,8 +1,8 @@ -import { BigNumber } from 'bignumber.js'; -import React, { useMemo, useState } from 'react'; +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 { selectConversionRate } from '../../../../../../../../../../selectors/currencyRateController'; @@ -21,6 +21,7 @@ import { } 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'; @@ -61,21 +62,15 @@ const NativeValueDisplay: React.FC<PermitSimulationValueDisplayParams> = ({ selectConversionRate(state, chainId), ); - const { fiatValue, tokenValue, tokenValueMaxPrecision } = useMemo(() => { - if (!value) { - return { tokenValue: null, tokenValueMaxPrecision: null }; - } - - const tokenAmount = calcTokenAmount(value, NATIVE_DECIMALS); - - return { - fiatValue: conversionRate - ? new BigNumber(tokenAmount).times(String(conversionRate)).toNumber() - : undefined, - tokenValue: formatAmount('en-US', tokenAmount), - tokenValueMaxPrecision: formatAmountMaxPrecision('en-US', tokenAmount), - }; - }, [conversionRate, value]); + 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); 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 2bbfafa5f0a..599d5bd4799 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'; @@ -22,6 +23,7 @@ import { selectContractExchangeRates } from '../../../../../../../../../../selec 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'; @@ -100,26 +102,15 @@ 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 { tokenValue, tokenValueMaxPrecision } = useMemo(() => { - if (!value || tokenId) { - return { tokenValue: null, tokenValueMaxPrecision: null }; - } - - const tokenAmount = calcTokenAmount(value, tokenDecimals); - - return { - tokenValue: formatAmount('en-US', tokenAmount), - tokenValueMaxPrecision: formatAmountMaxPrecision('en-US', tokenAmount), - }; - }, [tokenDecimals, tokenId, value]); + const tokenAmount = isNumberValue(value) && !tokenId ? calcTokenAmount(value, tokenDecimals) : null; + const isValidTokenAmount = tokenAmount !== null && tokenAmount !== undefined && tokenAmount instanceof BigNumber; + + const fiatValue = isValidTokenAmount && exchangeRate && !tokenId + ? tokenAmount.multipliedBy(exchangeRate).toNumber() + : undefined; + + const tokenValue = isValidTokenAmount ? formatAmount('en-US', tokenAmount) : null; + const tokenValueMaxPrecision = isValidTokenAmount ? formatAmountMaxPrecision('en-US', tokenAmount) : null; /** Temporary error capturing as we are building out Permit Simulations */ if (!tokenContract) { diff --git a/app/util/number/index.js b/app/util/number/index.js index 894716562aa..05e8cbececd 100644 --- a/app/util/number/index.js +++ b/app/util/number/index.js @@ -349,7 +349,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) { @@ -380,6 +380,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'); From a0834900c84bf490045aa9e4a53fc69499099405 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 15 Jan 2025 18:09:42 -0800 Subject: [PATCH 66/77] fix: update ValueDisplay disable ButtonPill when tokenId --- .../Simulation/components/ValueDisplay/ValueDisplay.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 599d5bd4799..c0465b4d904 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 @@ -123,8 +123,6 @@ const SimulationValueDisplay: React.FC< } function handlePressTokenValue() { - if (!tokenId) { return; } - setHasValueModalOpen(true); } @@ -133,6 +131,7 @@ const SimulationValueDisplay: React.FC< <View style={styles.flexRowTokenValueAndAddress}> <View style={styles.valueAndAddress}> <ButtonPill + isDisabled={!!tokenId || tokenId === '0'} onPress={handlePressTokenValue} onPressIn={handlePressTokenValue} onPressOut={handlePressTokenValue} From 00d6f0967082c861579e9ca22818be039f207940 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 15 Jan 2025 21:51:42 -0800 Subject: [PATCH 67/77] revert: selectConversionRate optional chainId param --- .../components/NativeValueDisplay/NativeValueDisplay.tsx | 5 +++-- app/selectors/currencyRateController.ts | 5 +---- 2 files changed, 4 insertions(+), 6 deletions(-) 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 index c232fff6131..89948aed0b5 100644 --- 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 @@ -5,7 +5,7 @@ import { Hex } from '@metamask/utils'; import { BigNumber } from 'bignumber.js'; import { RootState } from '../../../../../../../../../../reducers'; -import { selectConversionRate } from '../../../../../../../../../../selectors/currencyRateController'; +// import { selectConversionRate } from '../../../../../../../../../../selectors/currencyRateController'; import { useTheme } from '../../../../../../../../../../util/theme'; import ButtonPill from '../../../../../../../../../../component-library/components-temp/Buttons/ButtonPill/ButtonPill'; @@ -58,8 +58,9 @@ const NativeValueDisplay: React.FC<PermitSimulationValueDisplayParams> = ({ const { colors } = useTheme(); const styles = styleSheet(colors); + // todo: fix w/ upcoming selectConversionRateByChainId const conversionRate = useSelector((state: RootState) => - selectConversionRate(state, chainId), + selectConversionRate(state), ); const tokenAmount = isNumberValue(value) ? calcTokenAmount(value, NATIVE_DECIMALS) : null; diff --git a/app/selectors/currencyRateController.ts b/app/selectors/currencyRateController.ts index 6f7d4004b6a..715ebeb4e8b 100644 --- a/app/selectors/currencyRateController.ts +++ b/app/selectors/currencyRateController.ts @@ -12,15 +12,12 @@ export const selectConversionRate = createSelector( selectChainId, selectTicker, (state: RootState) => state.settings.showFiatOnTestnets, - (_: RootState, chainId?: string) => chainId, // Optional chainId parameter ( currencyRateControllerState: CurrencyRateState, - currentChainId: string, + chainId: string, ticker: string, showFiatOnTestnets, - providedChainId?: string | undefined, ) => { - const chainId = providedChainId || currentChainId; if (chainId && isTestNet(chainId) && !showFiatOnTestnets) { return undefined; } From fb047c501de74fde058d78f62e61cf618b8ad99b Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 15 Jan 2025 21:54:09 -0800 Subject: [PATCH 68/77] refactor: utilize new selectConversionRateByChainId in NativeValueDisplay --- .../NativeValueDisplay/NativeValueDisplay.tsx | 5 ++-- app/selectors/currencyRateController.ts | 25 ++++++++++++++++++- 2 files changed, 26 insertions(+), 4 deletions(-) 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 index 89948aed0b5..c15c720de80 100644 --- 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 @@ -5,7 +5,7 @@ import { Hex } from '@metamask/utils'; import { BigNumber } from 'bignumber.js'; import { RootState } from '../../../../../../../../../../reducers'; -// import { selectConversionRate } from '../../../../../../../../../../selectors/currencyRateController'; +import { selectConversionRateByChainId } from '../../../../../../../../../../selectors/currencyRateController'; import { useTheme } from '../../../../../../../../../../util/theme'; import ButtonPill from '../../../../../../../../../../component-library/components-temp/Buttons/ButtonPill/ButtonPill'; @@ -58,9 +58,8 @@ const NativeValueDisplay: React.FC<PermitSimulationValueDisplayParams> = ({ const { colors } = useTheme(); const styles = styleSheet(colors); - // todo: fix w/ upcoming selectConversionRateByChainId const conversionRate = useSelector((state: RootState) => - selectConversionRate(state), + selectConversionRateByChainId(state, chainId), ); const tokenAmount = isNumberValue(value) ? calcTokenAmount(value, NATIVE_DECIMALS) : null; 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; + }, +); From 28fcc626ab44fc3edf33e5cc4218d659a5bd3e35 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 15 Jan 2025 21:55:24 -0800 Subject: [PATCH 69/77] revert: selectConversionRate 5th param --- app/selectors/currencyRateController.test.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/selectors/currencyRateController.test.ts b/app/selectors/currencyRateController.test.ts index e6462afc21a..e48f6b957a4 100644 --- a/app/selectors/currencyRateController.test.ts +++ b/app/selectors/currencyRateController.test.ts @@ -35,7 +35,6 @@ describe('CurrencyRateController Selectors', () => { mockChainId as `0x${string}`, mockTicker, false, - undefined, ); expect(result).toBeUndefined(); }); @@ -48,7 +47,6 @@ describe('CurrencyRateController Selectors', () => { mockChainId as `0x${string}`, mockTicker, true, - undefined, ); expect(result).toBe(3000); }); @@ -61,7 +59,6 @@ describe('CurrencyRateController Selectors', () => { mockChainId as `0x${string}`, '', true, - undefined, ); expect(result).toBeUndefined(); }); From 76ba380b967c9e3bcb84fb9d2f2c1e526abb03b9 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 15 Jan 2025 22:04:35 -0800 Subject: [PATCH 70/77] test: CurrencyRateController#selectConversionRateByChainId --- app/selectors/currencyRateController.test.ts | 38 ++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/app/selectors/currencyRateController.test.ts b/app/selectors/currencyRateController.test.ts index e48f6b957a4..38d032545de 100644 --- a/app/selectors/currencyRateController.test.ts +++ b/app/selectors/currencyRateController.test.ts @@ -2,9 +2,12 @@ import { selectConversionRate, selectCurrentCurrency, selectCurrencyRates, + selectConversionRateByChainId, + selectConversionRateFoAllChains, } from './currencyRateController'; import { isTestNet } from '../../app/util/networks'; import { CurrencyRateState } from '@metamask/assets-controllers'; +import { selectNativeCurrencyByChainId } from './networkController'; jest.mock('../../app/util/networks', () => ({ isTestNet: jest.fn(), @@ -64,6 +67,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( From 21ad733d36258572864763ee93ca5b5317390452 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 15 Jan 2025 22:11:37 -0800 Subject: [PATCH 71/77] refactor: non-default export useTypedSignSimulationEnabled --- .../Confirm/Info/TypedSignV3V4/Simulation/Simulation.tsx | 4 ++-- .../confirmations/hooks/useTypedSignSimulationEnabled.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) 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 index 2d6d9002687..981a9355911 100644 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/Simulation.tsx +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/Simulation.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import useTypedSignSimulationEnabled from '../../../../../hooks/useTypedSignSimulationEnabled'; +import { useTypedSignSimulationEnabled } from '../../../../../hooks/useTypedSignSimulationEnabled'; import { isRecognizedPermit } from '../../../../../utils/signature'; import { useSignatureRequest } from '../../../../../hooks/useSignatureRequest'; import DecodedSimulation from './TypedSignDecoded'; @@ -8,7 +8,7 @@ import PermitSimulation from './TypedSignPermit'; const TypedSignV3V4Simulation: React.FC<object> = () => { const signatureRequest = useSignatureRequest(); - const isPermit = isRecognizedPermit(signatureRequest); + const isPermit = signatureRequest && isRecognizedPermit(signatureRequest); const isSimulationSupported = useTypedSignSimulationEnabled(); if (!isSimulationSupported || !signatureRequest) { diff --git a/app/components/Views/confirmations/hooks/useTypedSignSimulationEnabled.ts b/app/components/Views/confirmations/hooks/useTypedSignSimulationEnabled.ts index c0d85ee8297..0553f8fcae2 100644 --- a/app/components/Views/confirmations/hooks/useTypedSignSimulationEnabled.ts +++ b/app/components/Views/confirmations/hooks/useTypedSignSimulationEnabled.ts @@ -36,7 +36,7 @@ const isNonPermitSupportedByDecodingAPI = ( ); }; -export default function useTypedSignSimulationEnabled() { +export function useTypedSignSimulationEnabled() { const signatureRequest = useSignatureRequest(); const useTransactionSimulations = useSelector( selectUseTransactionSimulations, From 08030f0f1783a51a669b3e860577be7966652b14 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 15 Jan 2025 22:58:21 -0800 Subject: [PATCH 72/77] fix: lint unused imports currencyRateController test --- app/selectors/currencyRateController.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/selectors/currencyRateController.test.ts b/app/selectors/currencyRateController.test.ts index 38d032545de..61a29d38137 100644 --- a/app/selectors/currencyRateController.test.ts +++ b/app/selectors/currencyRateController.test.ts @@ -3,11 +3,9 @@ import { selectCurrentCurrency, selectCurrencyRates, selectConversionRateByChainId, - selectConversionRateFoAllChains, } from './currencyRateController'; import { isTestNet } from '../../app/util/networks'; import { CurrencyRateState } from '@metamask/assets-controllers'; -import { selectNativeCurrencyByChainId } from './networkController'; jest.mock('../../app/util/networks', () => ({ isTestNet: jest.fn(), From ccc001a26da1844602e0166c4020c013e351ac7b Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Wed, 15 Jan 2025 23:53:17 -0800 Subject: [PATCH 73/77] test: create TypedSignDecoded tests --- .../TypedSignDecoded.test.tsx | 167 ++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/TypedSignDecoded.test.tsx 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..c37fc11bc81 --- /dev/null +++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/TypedSignDecoded.test.tsx @@ -0,0 +1,167 @@ +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(<TypedSignDecoded />, { + state: mockState(stateChangesApprove), + }); + + expect(await getByText('Estimated changes')).toBeDefined(); + expect(await getByText('Spending cap')).toBeDefined(); + expect(await getByText('12,345')).toBeDefined(); + }); + + it('renders for ERC712 token', async () => { + const { getByText } = renderWithProvider(<TypedSignDecoded />, { + 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(<TypedSignDecoded />, { + 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(<TypedSignDecoded />, { + 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(<TypedSignDecoded />, { + 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); + }); + }); +}); From add8c45d1b141febec05bc9038d5d0b8cd915910 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Thu, 16 Jan 2025 00:35:33 -0800 Subject: [PATCH 74/77] fix: lint paths --- .../Views/confirmations/hooks/useApprovalRequest.ts | 7 +++---- app/components/Views/confirmations/utils/signature.test.ts | 1 - 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/components/Views/confirmations/hooks/useApprovalRequest.ts b/app/components/Views/confirmations/hooks/useApprovalRequest.ts index b967f132508..61128e7895c 100644 --- a/app/components/Views/confirmations/hooks/useApprovalRequest.ts +++ b/app/components/Views/confirmations/hooks/useApprovalRequest.ts @@ -1,11 +1,10 @@ -import Engine from '../../../../core/Engine'; import { useCallback, useMemo } from 'react'; -import { providerErrors } from '@metamask/rpc-errors'; import { useSelector } from 'react-redux'; -import { selectPendingApprovals } from '../../../../selectors/approvalController'; import { cloneDeep, isEqual } from 'lodash'; import { ApprovalRequest } from '@metamask/approval-controller'; -import { DecodingData } from '@metamask/signature-controller'; +import { providerErrors } from '@metamask/rpc-errors'; +import Engine from '../../../../core/Engine'; +import { selectPendingApprovals } from '../../../../selectors/approvalController'; // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/app/components/Views/confirmations/utils/signature.test.ts b/app/components/Views/confirmations/utils/signature.test.ts index c0d39c33bbd..08472ef3f27 100644 --- a/app/components/Views/confirmations/utils/signature.test.ts +++ b/app/components/Views/confirmations/utils/signature.test.ts @@ -1,4 +1,3 @@ -import { ApprovalRequest } from '@metamask/approval-controller'; import { parseTypedDataMessage, isRecognizedPermit } from './signature'; import { PRIMARY_TYPES_PERMIT } from '../constants/signatures'; import { SignatureRequest } from '@metamask/signature-controller'; From ee1d98d7301d50d58e23362dd6102bd0aa2c6af6 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Thu, 16 Jan 2025 00:44:23 -0800 Subject: [PATCH 75/77] refactor: remove duplicate NativeValueDisplay styles --- .../NativeValueDisplay.styles.ts | 90 ------------------- .../NativeValueDisplay/NativeValueDisplay.tsx | 8 +- .../ValueDisplay/ValueDisplay.styles.ts | 2 +- .../components/ValueDisplay/ValueDisplay.tsx | 2 +- 4 files changed, 8 insertions(+), 94 deletions(-) delete mode 100644 app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/NativeValueDisplay/NativeValueDisplay.styles.ts diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/NativeValueDisplay/NativeValueDisplay.styles.ts b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/NativeValueDisplay/NativeValueDisplay.styles.ts deleted file mode 100644 index 5370976bb7d..00000000000 --- a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/NativeValueDisplay/NativeValueDisplay.styles.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { StyleSheet } from 'react-native'; -import { Theme } from '@metamask/design-tokens'; -import { fontStyles, colors as importedColors } from '../../../../../../../../../../styles/common'; - -const styleSheet = (colors: Theme['colors']) => - StyleSheet.create({ - wrapper: { - marginLeft: 'auto', - maxWidth: '100%', - alignSelf: 'flex-end', - justifyContent: 'flex-end', - borderWidth: 0, - padding: 0, - }, - flexRowValueDisplay: { - display: 'flex', - justifyContent: 'flex-end', - flexDirection: 'row', - alignItems: 'center', - borderColor: importedColors.transparent, - borderWidth: 0, - padding: 0, - }, - fiatDisplay: { - paddingEnd: 8, - }, - tokenAssetPill: { - marginStart: 4, - }, - tokenValueTooltipContent: { - borderRadius: 12, - paddingHorizontal: 8, - paddingTop: 4, - paddingBottom: 4, - textAlign: 'center', - }, - valueAndAddress: { - paddingVertical: 4, - paddingLeft: 8, - gap: 5, - flexDirection: 'row', - alignItems: 'center', - alignSelf: 'center', - }, - valueIsCredit: { - backgroundColor: colors.success.muted, - color: colors.success.default, - }, - valueIsDebit: { - backgroundColor: colors.error.muted, - color: colors.error.default, - }, - valueModal: { - backgroundColor: colors.background.alternative, - paddingTop: 24, - paddingBottom: 34, - paddingHorizontal: 16, - borderTopLeftRadius: 8, - borderTopRightRadius: 8, - }, - valueModalHeader: { - alignItems: 'center', - display: 'flex', - flexDirection: 'row', - paddingBottom: 16, - position: 'relative', - textAlign: 'center', - width: '100%', - }, - valueModalHeaderIcon: { - position: 'absolute', - top: 0, - left: 0, - }, - valueModalHeaderText: { - color: colors.text.default, - ...fontStyles.bold, - fontSize: 14, - fontWeight: '700', - textAlign: 'center', - width: '100%', - // height of header icon - minHeight: 24, - }, - valueModalText: { - textAlign: 'center', - }, - }); - -export default styleSheet; 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 index c15c720de80..02bed953630 100644 --- 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 @@ -25,7 +25,11 @@ import { isNumberValue } from '../../../../../../../../../../util/number'; import { calcTokenAmount } from '../../../../../../../../../../util/transactions'; import BottomModal from '../../../../../../UI/BottomModal'; -import styleSheet from './NativeValueDisplay.styles'; +/** + * 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; @@ -78,7 +82,7 @@ const NativeValueDisplay: React.FC<PermitSimulationValueDisplayParams> = ({ return ( <View style={styles.wrapper}> - <View style={styles.flexRowValueDisplay}> + <View style={styles.marginStart4}> <View style={styles.valueAndAddress}> <ButtonPill onPress={handlePressTokenValue} 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 f27d9d879f9..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 @@ -24,7 +24,7 @@ const styleSheet = (colors: Theme['colors']) => borderWidth: 0, padding: 0, }, - tokenAddress: { + marginStart4: { marginStart: 4, }, tokenValueTooltipContent: { 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 c0465b4d904..dddcc95d8de 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 @@ -150,7 +150,7 @@ const SimulationValueDisplay: React.FC< {tokenId && `#${tokenId}`} </Text> </ButtonPill> - <View style={styles.tokenAddress}> + <View style={styles.marginStart4}> <Address address={tokenContract} chainId={chainId} /> </View> </View> From 95ffdf8a30086248ad9745c4d10b275ccf5bfd0e Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Thu, 16 Jan 2025 00:49:31 -0800 Subject: [PATCH 76/77] fix: NativeValueDisplay style names --- .../components/NativeValueDisplay/NativeValueDisplay.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 index 02bed953630..54ad3cd28e8 100644 --- 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 @@ -82,7 +82,7 @@ const NativeValueDisplay: React.FC<PermitSimulationValueDisplayParams> = ({ return ( <View style={styles.wrapper}> - <View style={styles.marginStart4}> + <View style={styles.flexRowTokenValueAndAddress}> <View style={styles.valueAndAddress}> <ButtonPill onPress={handlePressTokenValue} @@ -102,7 +102,7 @@ const NativeValueDisplay: React.FC<PermitSimulationValueDisplayParams> = ({ })} </Text> </ButtonPill> - <View style={styles.tokenAssetPill}> + <View style={styles.marginStart4}> <AssetPill asset={{ chainId, type: AssetType.Native }} /> </View> </View> From a1e4776bb13663b4cbca60c1026c974f9e18b692 Mon Sep 17 00:00:00 2001 From: digiwand <20778143+digiwand@users.noreply.github.com> Date: Thu, 16 Jan 2025 02:59:47 -0800 Subject: [PATCH 77/77] refactor: non-default export useConfirmationRedesignEnabled --- app/components/Views/confirmations/Confirm/Confirm.tsx | 2 +- .../confirmations/components/SignatureRequest/Root/Root.tsx | 2 +- .../hooks/useConfirmationRedesignEnabled.test.ts | 2 +- .../confirmations/hooks/useConfirmationRedesignEnabled.ts | 4 +--- 4 files changed, 4 insertions(+), 6 deletions(-) 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/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/hooks/useConfirmationRedesignEnabled.test.ts b/app/components/Views/confirmations/hooks/useConfirmationRedesignEnabled.test.ts index ffa03104883..20640e9606b 100644 --- a/app/components/Views/confirmations/hooks/useConfirmationRedesignEnabled.test.ts +++ b/app/components/Views/confirmations/hooks/useConfirmationRedesignEnabled.test.ts @@ -5,7 +5,7 @@ import { personalSignatureConfirmationState } from '../../../../util/test/confir // eslint-disable-next-line import/no-namespace import * as QRHardwareAwareness from './useQRHardwareAwareness'; -import useConfirmationRedesignEnabled from './useConfirmationRedesignEnabled'; +import { useConfirmationRedesignEnabled } from './useConfirmationRedesignEnabled'; jest.mock('../../../../core/Engine', () => ({ getTotalFiatAccountBalance: () => ({ tokenFiat: 10 }), diff --git a/app/components/Views/confirmations/hooks/useConfirmationRedesignEnabled.ts b/app/components/Views/confirmations/hooks/useConfirmationRedesignEnabled.ts index 140c58dcde4..7de75e498ff 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); @@ -43,5 +43,3 @@ const useConfirmationRedesignEnabled = () => { return { isRedesignedEnabled }; }; - -export default useConfirmationRedesignEnabled;