From 406b4732d3762519b3bc845107bf3fc2d213e642 Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Fri, 11 Oct 2024 19:53:25 +0530 Subject: [PATCH 1/9] Adding typed sign support for NFT permit --- test/data/confirmations/typed_sign.ts | 15 ++++++ .../components/confirm/title/title.test.tsx | 20 +++++++- .../components/confirm/title/title.tsx | 45 ++++++++++++++---- ui/pages/confirmations/constants/index.ts | 5 ++ .../hooks/useTypedSignSignatureInfo.test.js | 20 ++++++++ .../hooks/useTypedSignSignatureInfo.ts | 46 +++++++++++++++++++ 6 files changed, 140 insertions(+), 11 deletions(-) create mode 100644 ui/pages/confirmations/hooks/useTypedSignSignatureInfo.test.js create mode 100644 ui/pages/confirmations/hooks/useTypedSignSignatureInfo.ts diff --git a/test/data/confirmations/typed_sign.ts b/test/data/confirmations/typed_sign.ts index f02705a2540b..7be24a1389c6 100644 --- a/test/data/confirmations/typed_sign.ts +++ b/test/data/confirmations/typed_sign.ts @@ -183,6 +183,21 @@ export const permitSignatureMsg = { }, } as SignatureRequestType; +export const permitNFTSignatureMsg = { + id: 'c5067710-87cf-11ef-916c-71f266571322', + status: 'unapproved', + time: 1728651190529, + type: 'eth_signTypedData', + msgParams: { + data: '{"domain":{"name":"Uniswap V3 Positions NFT-V1","version":"1","chainId":1,"verifyingContract":"0xC36442b4a4522E871399CD717aBDD847Ab11FE88"},"types":{"Permit":[{"name":"spender","type":"address"},{"name":"tokenId","type":"uint256"},{"name":"nonce","type":"uint256"},{"name":"deadline","type":"uint256"}]},"primaryType":"Permit","message":{"spender":"0x00000000Ede6d8D217c60f93191C060747324bca","tokenId":"3606393","nonce":"0","deadline":"1734995006"}}', + from: '0x935e73edb9ff52e23bac7f7e043a1ecd06d05477', + version: 'V4', + signatureMethod: 'eth_signTypedData_v4', + requestId: 2874791875, + origin: 'https://metamask.github.io', + }, +} as SignatureRequestType; + export const permitSignatureMsgWithNoDeadline = { id: '0b1787a0-1c44-11ef-b70d-e7064bd7b659', securityAlertResponse: { diff --git a/ui/pages/confirmations/components/confirm/title/title.test.tsx b/ui/pages/confirmations/components/confirm/title/title.test.tsx index 3c03343c2afb..3d4d6672940d 100644 --- a/ui/pages/confirmations/components/confirm/title/title.test.tsx +++ b/ui/pages/confirmations/components/confirm/title/title.test.tsx @@ -11,7 +11,10 @@ import { getMockTypedSignConfirmStateForRequest, } from '../../../../../../test/data/confirmations/helper'; import { unapprovedPersonalSignMsg } from '../../../../../../test/data/confirmations/personal_sign'; -import { permitSignatureMsg } from '../../../../../../test/data/confirmations/typed_sign'; +import { + permitNFTSignatureMsg, + permitSignatureMsg, +} from '../../../../../../test/data/confirmations/typed_sign'; import { renderWithConfirmContextProvider } from '../../../../../../test/lib/confirmations/render-helpers'; import { tEn } from '../../../../../../test/lib/i18n-helpers'; import { @@ -71,6 +74,21 @@ describe('ConfirmTitle', () => { ).toBeInTheDocument(); }); + it('should render the title and description for a NFT permit signature', () => { + const mockStore = configureMockStore([])( + getMockTypedSignConfirmStateForRequest(permitNFTSignatureMsg), + ); + const { getByText } = renderWithConfirmContextProvider( + , + mockStore, + ); + + expect(getByText('Withdrawal request')).toBeInTheDocument(); + expect( + getByText('This site wants permission to withdraw your NFTs'), + ).toBeInTheDocument(); + }); + it('should render the title and description for typed signature', () => { const mockStore = configureMockStore([])(getMockTypedSignConfirmState()); const { getByText } = renderWithConfirmContextProvider( diff --git a/ui/pages/confirmations/components/confirm/title/title.tsx b/ui/pages/confirmations/components/confirm/title/title.tsx index 2645feed8a41..7a5169cc5bdc 100644 --- a/ui/pages/confirmations/components/confirm/title/title.tsx +++ b/ui/pages/confirmations/components/confirm/title/title.tsx @@ -3,6 +3,8 @@ import { TransactionType, } from '@metamask/transaction-controller'; import React, { memo, useMemo } from 'react'; + +import { TokenStandard } from '../../../../../../shared/constants/transaction'; import GeneralAlert from '../../../../../components/app/alert-system/general-alert/general-alert'; import { Box, Text } from '../../../../../components/component-library'; import { @@ -12,12 +14,11 @@ import { } from '../../../../../helpers/constants/design-system'; import useAlerts from '../../../../../hooks/useAlerts'; import { useI18nContext } from '../../../../../hooks/useI18nContext'; +import { TypedSignSignaturePrimaryTypes } from '../../../constants'; import { useConfirmContext } from '../../../context/confirm'; import { Confirmation, SignatureRequestType } from '../../../types/confirm'; -import { - isPermitSignatureRequest, - isSIWESignatureRequest, -} from '../../../utils'; +import { isSIWESignatureRequest } from '../../../utils'; +import { useTypedSignSignatureInfo } from '../../../hooks/useTypedSignSignatureInfo'; import { useIsNFT } from '../info/approve/hooks/use-is-nft'; import { useDecodedTransactionData } from '../info/hooks/useDecodedTransactionData'; import { getIsRevokeSetApprovalForAll } from '../info/utils'; @@ -58,6 +59,8 @@ const getTitle = ( customSpendingCap?: string, isRevokeSetApprovalForAll?: boolean, pending?: boolean, + primaryType?: keyof typeof TypedSignSignaturePrimaryTypes, + tokenStandard?: string, ) => { if (pending) { return ''; @@ -74,9 +77,13 @@ const getTitle = ( } return t('confirmTitleSignature'); case TransactionType.signTypedData: - return isPermitSignatureRequest(confirmation as SignatureRequestType) - ? t('confirmTitlePermitTokens') - : t('confirmTitleSignature'); + if (primaryType === TypedSignSignaturePrimaryTypes.PERMIT) { + if (tokenStandard === TokenStandard.ERC721) { + return t('setApprovalForAllRedesignedTitle'); + } + return t('confirmTitlePermitTokens'); + } + return t('confirmTitleSignature'); case TransactionType.tokenMethodApprove: if (isNFT) { return t('confirmTitleApproveTransaction'); @@ -104,6 +111,8 @@ const getDescription = ( customSpendingCap?: string, isRevokeSetApprovalForAll?: boolean, pending?: boolean, + primaryType?: keyof typeof TypedSignSignaturePrimaryTypes, + tokenStandard?: string, ) => { if (pending) { return ''; @@ -120,9 +129,13 @@ const getDescription = ( } return t('confirmTitleDescSign'); case TransactionType.signTypedData: - return isPermitSignatureRequest(confirmation as SignatureRequestType) - ? t('confirmTitleDescPermitSignature') - : t('confirmTitleDescSign'); + if (primaryType === TypedSignSignaturePrimaryTypes.PERMIT) { + if (tokenStandard === TokenStandard.ERC721) { + return t('confirmTitleDescApproveTransaction'); + } + return t('confirmTitleDescPermitSignature'); + } + return t('confirmTitleDescSign'); case TransactionType.tokenMethodApprove: if (isNFT) { return t('confirmTitleDescApproveTransaction'); @@ -150,6 +163,10 @@ const ConfirmTitle: React.FC = memo(() => { const { isNFT } = useIsNFT(currentConfirmation as TransactionMeta); + const { primaryType, tokenStandard } = useTypedSignSignatureInfo( + currentConfirmation as SignatureRequestType, + ); + const { customSpendingCap, pending: spendingCapPending } = useCurrentSpendingCap(currentConfirmation); @@ -175,6 +192,8 @@ const ConfirmTitle: React.FC = memo(() => { customSpendingCap, isRevokeSetApprovalForAll, spendingCapPending || revokePending, + primaryType, + tokenStandard, ), [ currentConfirmation, @@ -183,6 +202,8 @@ const ConfirmTitle: React.FC = memo(() => { isRevokeSetApprovalForAll, spendingCapPending, revokePending, + primaryType, + tokenStandard, ], ); @@ -195,6 +216,8 @@ const ConfirmTitle: React.FC = memo(() => { customSpendingCap, isRevokeSetApprovalForAll, spendingCapPending || revokePending, + primaryType, + tokenStandard, ), [ currentConfirmation, @@ -203,6 +226,8 @@ const ConfirmTitle: React.FC = memo(() => { isRevokeSetApprovalForAll, spendingCapPending, revokePending, + primaryType, + tokenStandard, ], ); diff --git a/ui/pages/confirmations/constants/index.ts b/ui/pages/confirmations/constants/index.ts index 38fd05b714ba..7e26ce5c6d62 100644 --- a/ui/pages/confirmations/constants/index.ts +++ b/ui/pages/confirmations/constants/index.ts @@ -9,3 +9,8 @@ export const TYPED_SIGNATURE_VERSIONS = { }; export const SPENDING_CAP_UNLIMITED_MSG = 'UNLIMITED MESSAGE'; + +export const TypedSignSignaturePrimaryTypes = { + PERMIT: 'Permit', + ORDER: 'Order', +}; diff --git a/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.test.js b/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.test.js new file mode 100644 index 000000000000..9dd0dbcd9b57 --- /dev/null +++ b/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.test.js @@ -0,0 +1,20 @@ +import { renderHook } from '@testing-library/react-hooks'; + +import { TokenStandard } from '../../../../shared/constants/transaction'; +import { permitNFTSignatureMsg } from '../../../../test/data/confirmations/typed_sign'; +import { TypedSignSignaturePrimaryTypes } from '../constants'; +import { useTypedSignSignatureInfo } from './useTypedSignSignatureInfo'; + +describe('useTypedSignSignatureInfo', () => { + describe('isNftTransfer', () => { + it('should return false if transaction is not NFT transfer', () => { + const { result } = renderHook(() => + useTypedSignSignatureInfo(permitNFTSignatureMsg), + ); + expect(result.current.primaryType).toStrictEqual( + TypedSignSignaturePrimaryTypes.PERMIT, + ); + expect(result.current.tokenStandard).toStrictEqual(TokenStandard.ERC721); + }); + }); +}); diff --git a/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.ts b/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.ts new file mode 100644 index 000000000000..c5ef52ca25e0 --- /dev/null +++ b/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.ts @@ -0,0 +1,46 @@ +import { useMemo } from 'react'; + +import { + isOrderSignatureRequest, + isPermitSignatureRequest, + isSignatureTransactionType, +} from '../utils'; +import { SignatureRequestType } from '../types/confirm'; +import { parseTypedDataMessage } from '../../../../shared/modules/transaction.utils'; +import { TokenStandard } from '../../../../shared/constants/transaction'; +import { MESSAGE_TYPE } from '../../../../shared/constants/app'; +import { TypedSignSignaturePrimaryTypes } from '../constants'; + +export const useTypedSignSignatureInfo = ( + confirmation: SignatureRequestType, +) => { + if (!confirmation) { + return {}; + } + if ( + !isSignatureTransactionType(confirmation) || + confirmation?.type !== MESSAGE_TYPE.ETH_SIGN_TYPED_DATA + ) { + return {}; + } + const primaryType = useMemo(() => { + if (isPermitSignatureRequest(confirmation)) { + return TypedSignSignaturePrimaryTypes.PERMIT; + } else if (isOrderSignatureRequest(confirmation)) { + return TypedSignSignaturePrimaryTypes.ORDER; + } + return undefined; + }, [confirmation]); + + const tokenStandard = useMemo(() => { + const { + message: { tokenId }, + } = parseTypedDataMessage(confirmation?.msgParams?.data as string); + if (tokenId !== undefined) { + return TokenStandard.ERC721; + } + return undefined; + }, [confirmation]); + + return { primaryType, tokenStandard }; +}; From 48415315a0ae0acfc0fa8bebd73264418454681c Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Mon, 14 Oct 2024 15:13:03 +0530 Subject: [PATCH 2/9] update --- .../hooks/useTypedSignSignatureInfo.test.js | 26 ++++++++++++------- .../hooks/useTypedSignSignatureInfo.ts | 8 +++--- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.test.js b/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.test.js index 9dd0dbcd9b57..ab41c29ddffd 100644 --- a/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.test.js +++ b/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.test.js @@ -2,19 +2,25 @@ import { renderHook } from '@testing-library/react-hooks'; import { TokenStandard } from '../../../../shared/constants/transaction'; import { permitNFTSignatureMsg } from '../../../../test/data/confirmations/typed_sign'; +import { unapprovedPersonalSignMsg } from '../../../../test/data/confirmations/personal_sign'; import { TypedSignSignaturePrimaryTypes } from '../constants'; import { useTypedSignSignatureInfo } from './useTypedSignSignatureInfo'; describe('useTypedSignSignatureInfo', () => { - describe('isNftTransfer', () => { - it('should return false if transaction is not NFT transfer', () => { - const { result } = renderHook(() => - useTypedSignSignatureInfo(permitNFTSignatureMsg), - ); - expect(result.current.primaryType).toStrictEqual( - TypedSignSignaturePrimaryTypes.PERMIT, - ); - expect(result.current.tokenStandard).toStrictEqual(TokenStandard.ERC721); - }); + it('should return details for primaty type and token standard', () => { + const { result } = renderHook(() => + useTypedSignSignatureInfo(permitNFTSignatureMsg), + ); + expect(result.current.primaryType).toStrictEqual( + TypedSignSignaturePrimaryTypes.PERMIT, + ); + expect(result.current.tokenStandard).toStrictEqual(TokenStandard.ERC721); + }); + + it('should return empty object if confirmation is not typed sign', () => { + const { result } = renderHook(() => + useTypedSignSignatureInfo(unapprovedPersonalSignMsg), + ); + expect(result.current).toStrictEqual({}); }); }); diff --git a/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.ts b/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.ts index c5ef52ca25e0..a6c704c6097a 100644 --- a/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.ts +++ b/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.ts @@ -14,15 +14,14 @@ import { TypedSignSignaturePrimaryTypes } from '../constants'; export const useTypedSignSignatureInfo = ( confirmation: SignatureRequestType, ) => { - if (!confirmation) { - return {}; - } if ( + !confirmation || !isSignatureTransactionType(confirmation) || confirmation?.type !== MESSAGE_TYPE.ETH_SIGN_TYPED_DATA ) { return {}; } + const primaryType = useMemo(() => { if (isPermitSignatureRequest(confirmation)) { return TypedSignSignaturePrimaryTypes.PERMIT; @@ -32,6 +31,9 @@ export const useTypedSignSignatureInfo = ( return undefined; }, [confirmation]); + // here we are using presence of tokenId in typed message data to know if its NFT permit + // we can get contract details for verifyingContract but that is async process taking longer + // and result in confirmation page content loading late const tokenStandard = useMemo(() => { const { message: { tokenId }, From 528420893cd1c5958d63ce3d853d55db17e150ee Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Mon, 14 Oct 2024 15:29:23 +0530 Subject: [PATCH 3/9] update --- ui/pages/confirmations/hooks/useTypedSignSignatureInfo.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.ts b/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.ts index a6c704c6097a..cc9e274f9abc 100644 --- a/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.ts +++ b/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.ts @@ -44,5 +44,8 @@ export const useTypedSignSignatureInfo = ( return undefined; }, [confirmation]); - return { primaryType, tokenStandard }; + return { + primaryType: primaryType as keyof typeof TypedSignSignaturePrimaryTypes, + tokenStandard, + }; }; From e40f494874def130981fc9ff46b5797b8acdfe23 Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Mon, 14 Oct 2024 15:37:15 +0530 Subject: [PATCH 4/9] update --- ui/pages/confirmations/components/confirm/title/title.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ui/pages/confirmations/components/confirm/title/title.tsx b/ui/pages/confirmations/components/confirm/title/title.tsx index 7a5169cc5bdc..969e9c05518d 100644 --- a/ui/pages/confirmations/components/confirm/title/title.tsx +++ b/ui/pages/confirmations/components/confirm/title/title.tsx @@ -52,6 +52,8 @@ function ConfirmBannerAlert({ ownerId }: { ownerId: string }) { type IntlFunction = (str: string) => string; +// todo: getTitle and getDescription can be merged to remove code duplication. + const getTitle = ( t: IntlFunction, confirmation?: Confirmation, From 14eac712fa9227fa5bffb1f50af03697b84d8108 Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Mon, 14 Oct 2024 17:10:21 +0530 Subject: [PATCH 5/9] update --- .../hooks/useTypedSignSignatureInfo.test.js | 3 ++- .../hooks/useTypedSignSignatureInfo.ts | 21 ++++++++++++------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.test.js b/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.test.js index ab41c29ddffd..38468749782d 100644 --- a/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.test.js +++ b/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.test.js @@ -21,6 +21,7 @@ describe('useTypedSignSignatureInfo', () => { const { result } = renderHook(() => useTypedSignSignatureInfo(unapprovedPersonalSignMsg), ); - expect(result.current).toStrictEqual({}); + expect(result.current.primaryType).toBeUndefined(); + expect(result.current.tokenStandard).toBeUndefined(); }); }); diff --git a/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.ts b/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.ts index cc9e274f9abc..c0ef240c25d7 100644 --- a/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.ts +++ b/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.ts @@ -14,15 +14,15 @@ import { TypedSignSignaturePrimaryTypes } from '../constants'; export const useTypedSignSignatureInfo = ( confirmation: SignatureRequestType, ) => { - if ( - !confirmation || - !isSignatureTransactionType(confirmation) || - confirmation?.type !== MESSAGE_TYPE.ETH_SIGN_TYPED_DATA - ) { - return {}; - } const primaryType = useMemo(() => { + if ( + !confirmation || + !isSignatureTransactionType(confirmation) || + confirmation?.type !== MESSAGE_TYPE.ETH_SIGN_TYPED_DATA + ) { + return undefined; + } if (isPermitSignatureRequest(confirmation)) { return TypedSignSignaturePrimaryTypes.PERMIT; } else if (isOrderSignatureRequest(confirmation)) { @@ -35,6 +35,13 @@ export const useTypedSignSignatureInfo = ( // we can get contract details for verifyingContract but that is async process taking longer // and result in confirmation page content loading late const tokenStandard = useMemo(() => { + if ( + !confirmation || + !isSignatureTransactionType(confirmation) || + confirmation?.type !== MESSAGE_TYPE.ETH_SIGN_TYPED_DATA + ) { + return undefined; + } const { message: { tokenId }, } = parseTypedDataMessage(confirmation?.msgParams?.data as string); From 3463768785ec308caf6c61a68538e8b42ca069ac Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Mon, 14 Oct 2024 17:10:50 +0530 Subject: [PATCH 6/9] update --- ui/pages/confirmations/hooks/useTypedSignSignatureInfo.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.ts b/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.ts index c0ef240c25d7..a17faeacaa75 100644 --- a/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.ts +++ b/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.ts @@ -14,7 +14,6 @@ import { TypedSignSignaturePrimaryTypes } from '../constants'; export const useTypedSignSignatureInfo = ( confirmation: SignatureRequestType, ) => { - const primaryType = useMemo(() => { if ( !confirmation || From a1e918798d2476ea0ba69e8190db6442a98e528c Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Mon, 14 Oct 2024 17:14:05 +0530 Subject: [PATCH 7/9] update --- .../hooks/useTypedSignSignatureInfo.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.ts b/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.ts index a17faeacaa75..5f24a04c2107 100644 --- a/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.ts +++ b/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.ts @@ -11,15 +11,18 @@ import { TokenStandard } from '../../../../shared/constants/transaction'; import { MESSAGE_TYPE } from '../../../../shared/constants/app'; import { TypedSignSignaturePrimaryTypes } from '../constants'; +const isNotTypedSignDataSignatureRequest = ( + confirmation: SignatureRequestType, +) => + !confirmation || + !isSignatureTransactionType(confirmation) || + confirmation?.type !== MESSAGE_TYPE.ETH_SIGN_TYPED_DATA; + export const useTypedSignSignatureInfo = ( confirmation: SignatureRequestType, ) => { const primaryType = useMemo(() => { - if ( - !confirmation || - !isSignatureTransactionType(confirmation) || - confirmation?.type !== MESSAGE_TYPE.ETH_SIGN_TYPED_DATA - ) { + if (isNotTypedSignDataSignatureRequest(confirmation)) { return undefined; } if (isPermitSignatureRequest(confirmation)) { @@ -34,11 +37,7 @@ export const useTypedSignSignatureInfo = ( // we can get contract details for verifyingContract but that is async process taking longer // and result in confirmation page content loading late const tokenStandard = useMemo(() => { - if ( - !confirmation || - !isSignatureTransactionType(confirmation) || - confirmation?.type !== MESSAGE_TYPE.ETH_SIGN_TYPED_DATA - ) { + if (isNotTypedSignDataSignatureRequest(confirmation)) { return undefined; } const { From 2c8ca9fa6cfef1038b84a4c862c980d189daa241 Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Mon, 14 Oct 2024 18:03:00 +0530 Subject: [PATCH 8/9] Add simulation section for NFT permit --- .../permit-simulation.test.tsx.snap | 117 ++++++++++++++++++ .../permit-simulation.test.tsx | 21 +++- .../permit-simulation/permit-simulation.tsx | 11 +- .../__snapshots__/value-display.test.tsx.snap | 46 +++++++ .../value-display/value-display.test.tsx | 17 +++ .../value-display/value-display.tsx | 35 +++--- 6 files changed, 230 insertions(+), 17 deletions(-) diff --git a/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/__snapshots__/permit-simulation.test.tsx.snap b/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/__snapshots__/permit-simulation.test.tsx.snap index 7c5553495eb0..f35e2218cbfb 100644 --- a/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/__snapshots__/permit-simulation.test.tsx.snap +++ b/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/__snapshots__/permit-simulation.test.tsx.snap @@ -127,3 +127,120 @@ exports[`PermitSimulation renders component correctly 1`] = ` `; + +exports[`PermitSimulation renders correctly for NFT permit 1`] = ` +
+
+
+
+
+

+ Estimated changes +

+
+
+ +
+
+
+
+
+

+ You're giving someone else permission to withdraw NFTs from your account. +

+
+
+
+
+
+

+ Withdraw +

+
+
+
+
+
+
+
+

+ #3606393 +

+
+
+
+
+ +

+ 0xC3644...1FE88 +

+
+
+
+
+
+
+
+
+
+`; diff --git a/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/permit-simulation.test.tsx b/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/permit-simulation.test.tsx index 1be34109a637..e89efb3c0dc1 100644 --- a/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/permit-simulation.test.tsx +++ b/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/permit-simulation.test.tsx @@ -4,7 +4,10 @@ import { act } from 'react-dom/test-utils'; import { getMockTypedSignConfirmStateForRequest } from '../../../../../../../../test/data/confirmations/helper'; import { renderWithConfirmContextProvider } from '../../../../../../../../test/lib/confirmations/render-helpers'; -import { permitSignatureMsg } from '../../../../../../../../test/data/confirmations/typed_sign'; +import { + permitNFTSignatureMsg, + permitSignatureMsg, +} from '../../../../../../../../test/data/confirmations/typed_sign'; import PermitSimulation from './permit-simulation'; jest.mock('../../../../../../../store/actions', () => { @@ -28,4 +31,20 @@ describe('PermitSimulation', () => { expect(container).toMatchSnapshot(); }); }); + + it('renders correctly for NFT permit', async () => { + const state = getMockTypedSignConfirmStateForRequest(permitNFTSignatureMsg); + const mockStore = configureMockStore([])(state); + + await act(async () => { + const { container, findByText } = renderWithConfirmContextProvider( + , + mockStore, + ); + + expect(await findByText('Withdraw')).toBeInTheDocument(); + expect(await findByText('#3606393')).toBeInTheDocument(); + expect(container).toMatchSnapshot(); + }); + }); }); diff --git a/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/permit-simulation.tsx b/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/permit-simulation.tsx index 231997d18547..44131ec18fbf 100644 --- a/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/permit-simulation.tsx +++ b/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/permit-simulation.tsx @@ -45,8 +45,10 @@ const PermitSimulation: React.FC = () => { const { domain: { verifyingContract }, message, + message: { tokenId }, primaryType, } = parseTypedDataMessage(msgData as string); + const isNFT = tokenId !== undefined; const tokenDetails = extractTokenDetailsByPrimaryType(message, primaryType); @@ -68,7 +70,9 @@ const PermitSimulation: React.FC = () => { ); const SpendingCapRow = ( - + {Array.isArray(tokenDetails) ? ( = () => { )} @@ -99,7 +104,9 @@ const PermitSimulation: React.FC = () => { ); diff --git a/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/value-display/__snapshots__/value-display.test.tsx.snap b/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/value-display/__snapshots__/value-display.test.tsx.snap index 26def806c6fa..9c4134aa1b2d 100644 --- a/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/value-display/__snapshots__/value-display.test.tsx.snap +++ b/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/value-display/__snapshots__/value-display.test.tsx.snap @@ -56,3 +56,49 @@ exports[`PermitSimulationValueDisplay renders component correctly 1`] = ` `; + +exports[`PermitSimulationValueDisplay renders component correctly for NFT token 1`] = ` +
+
+
+
+
+

+ #4321 +

+
+
+
+
+ +

+ 0xA0b86...6eB48 +

+
+
+
+
+
+
+`; diff --git a/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/value-display/value-display.test.tsx b/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/value-display/value-display.test.tsx index f6af7357502d..da86d497aac1 100644 --- a/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/value-display/value-display.test.tsx +++ b/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/value-display/value-display.test.tsx @@ -29,4 +29,21 @@ describe('PermitSimulationValueDisplay', () => { expect(container).toMatchSnapshot(); }); }); + + it('renders component correctly for NFT token', async () => { + const mockStore = configureMockStore([])(mockState); + + await act(async () => { + const { container, findByText } = renderWithProvider( + , + mockStore, + ); + + expect(await findByText('#4321')).toBeInTheDocument(); + expect(container).toMatchSnapshot(); + }); + }); }); diff --git a/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/value-display/value-display.tsx b/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/value-display/value-display.tsx index 633191cd2638..360559493596 100644 --- a/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/value-display/value-display.tsx +++ b/ui/pages/confirmations/components/confirm/info/typed-sign/permit-simulation/value-display/value-display.tsx @@ -41,21 +41,26 @@ type PermitSimulationValueDisplayParams = { tokenContract: Hex | string; /** The token amount */ - value: number | string; + value?: number | string; + + /** The tokenId for NFT */ + tokenId?: string; }; const PermitSimulationValueDisplay: React.FC< PermitSimulationValueDisplayParams -> = ({ primaryType, tokenContract, value }) => { +> = ({ primaryType, tokenContract, value, tokenId }) => { const exchangeRate = useTokenExchangeRate(tokenContract); - const { value: tokenDecimals } = useAsyncResult( - async () => await fetchErc20Decimals(tokenContract), - [tokenContract], - ); + const { value: tokenDecimals } = useAsyncResult(async () => { + if (tokenId) { + return undefined; + } + return await fetchErc20Decimals(tokenContract); + }, [tokenContract]); const fiatValue = useMemo(() => { - if (exchangeRate && value) { + if (exchangeRate && value && !tokenId) { const tokenAmount = calcTokenAmount(value, tokenDecimals); return exchangeRate.times(tokenAmount).toNumber(); } @@ -63,7 +68,7 @@ const PermitSimulationValueDisplay: React.FC< }, [exchangeRate, tokenDecimals, value]); const { tokenValue, tokenValueMaxPrecision } = useMemo(() => { - if (!value) { + if (!value || tokenId) { return { tokenValue: null, tokenValueMaxPrecision: null }; } @@ -107,12 +112,14 @@ const PermitSimulationValueDisplay: React.FC< style={{ paddingTop: '1px', paddingBottom: '1px' }} textAlign={TextAlign.Center} > - {shortenString(tokenValue || '', { - truncatedCharLimit: 15, - truncatedStartChars: 15, - truncatedEndChars: 0, - skipCharacterInEnd: true, - })} + {tokenValue !== null && + shortenString(tokenValue || '', { + truncatedCharLimit: 15, + truncatedStartChars: 15, + truncatedEndChars: 0, + skipCharacterInEnd: true, + })} + {tokenId && `#${tokenId}`} From d002def12a5a39da438ab360eac3362d2b070f40 Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Mon, 14 Oct 2024 18:10:44 +0530 Subject: [PATCH 9/9] update --- .../hooks/useTypedSignSignatureInfo.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.ts b/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.ts index 5f24a04c2107..587eedc94d0a 100644 --- a/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.ts +++ b/ui/pages/confirmations/hooks/useTypedSignSignatureInfo.ts @@ -11,18 +11,15 @@ import { TokenStandard } from '../../../../shared/constants/transaction'; import { MESSAGE_TYPE } from '../../../../shared/constants/app'; import { TypedSignSignaturePrimaryTypes } from '../constants'; -const isNotTypedSignDataSignatureRequest = ( - confirmation: SignatureRequestType, -) => - !confirmation || - !isSignatureTransactionType(confirmation) || - confirmation?.type !== MESSAGE_TYPE.ETH_SIGN_TYPED_DATA; - export const useTypedSignSignatureInfo = ( confirmation: SignatureRequestType, ) => { const primaryType = useMemo(() => { - if (isNotTypedSignDataSignatureRequest(confirmation)) { + if ( + !confirmation || + !isSignatureTransactionType(confirmation) || + confirmation?.type !== MESSAGE_TYPE.ETH_SIGN_TYPED_DATA + ) { return undefined; } if (isPermitSignatureRequest(confirmation)) { @@ -37,9 +34,10 @@ export const useTypedSignSignatureInfo = ( // we can get contract details for verifyingContract but that is async process taking longer // and result in confirmation page content loading late const tokenStandard = useMemo(() => { - if (isNotTypedSignDataSignatureRequest(confirmation)) { + if (primaryType !== TypedSignSignaturePrimaryTypes.PERMIT) { return undefined; } + console.log(confirmation, confirmation?.msgParams?.data); const { message: { tokenId }, } = parseTypedDataMessage(confirmation?.msgParams?.data as string); @@ -47,7 +45,7 @@ export const useTypedSignSignatureInfo = ( return TokenStandard.ERC721; } return undefined; - }, [confirmation]); + }, [confirmation, primaryType]); return { primaryType: primaryType as keyof typeof TypedSignSignaturePrimaryTypes,