diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 25ad96fce540..4d251becd20a 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -4679,9 +4679,24 @@ "simulationsSettingSubHeader": { "message": "Estimate balance changes" }, + "siweIssued": { + "message": "Issued" + }, + "siweNetwork": { + "message": "Network" + }, + "siweRequestId": { + "message": "Request ID" + }, + "siweResources": { + "message": "Resources" + }, "siweSignatureSimulationDetailInfo": { "message": "This type of signature is not able to move your assets and is used for signing in." }, + "siweURI": { + "message": "URL" + }, "skip": { "message": "Skip" }, diff --git a/package.json b/package.json index 41e2dbc4cec4..041520f59739 100644 --- a/package.json +++ b/package.json @@ -489,6 +489,7 @@ "@types/gulp-sourcemaps": "^0.0.35", "@types/he": "^1", "@types/jest": "^29.5.12", + "@types/luxon": "^3.4.2", "@types/mocha": "^10.0.3", "@types/node": "^20", "@types/pify": "^5.0.1", diff --git a/test/data/confirmations/personal_sign.ts b/test/data/confirmations/personal_sign.ts index e0c9d093d78c..c6f7907eccc4 100644 --- a/test/data/confirmations/personal_sign.ts +++ b/test/data/confirmations/personal_sign.ts @@ -46,3 +46,41 @@ export const signatureRequestSIWE = { }, }, }; + +export const SignatureRequestSIWEWithResources = { + id: '210ca3b0-1ccb-11ef-b096-89c4d726ebb5', + securityAlertResponse: { + reason: 'loading', + result_type: 'validation_in_progress', + securityAlertId: 'b826df20-2eda-41bf-becf-6a100141a8be', + }, + status: 'unapproved', + time: 1716884423019, + type: 'personal_sign', + msgParams: { + from: '0x935e73edb9ff52e23bac7f7e049a1ecd06d05477', + data: '0x6d6574616d61736b2e6769746875622e696f2077616e747320796f7520746f207369676e20696e207769746820796f757220457468657265756d206163636f756e743a0a3078393335653733656462396666353265323362616337663765303433613165636430366430353437370a0a492061636365707420746865204d6574614d61736b205465726d73206f6620536572766963653a2068747470733a2f2f636f6d6d756e6974792e6d6574616d61736b2e696f2f746f730a0a5552493a2068747470733a2f2f6d6574616d61736b2e6769746875622e696f0a56657273696f6e3a20310a436861696e2049443a20310a4e6f6e63653a2033323839313735370a4973737565642041743a20323032312d30392d33305431363a32353a32342e3030305a', + signatureMethod: 'personal_sign', + origin: 'https://metamask.github.io', + siwe: { + isSIWEMessage: true, + parsedMessage: { + domain: 'metamask.github.io', + address: '0x935e73edb9ff52e23bac7f7e043a1ecd06d05477', + statement: + 'I accept the MetaMask Terms of Service: https://community.metamask.io/tos', + uri: 'https://metamask.github.io', + version: '1', + chainId: 1, + nonce: '32891757', + issuedAt: '2021-09-30T16:25:24.000Z', + notBefore: '2022-03-17T12:45:13.610Z', + requestId: 'some_id', + resources: [ + 'ipfs://Qme7ss3ARVgxv6rXqVPiikMJ8u2NLgmgszg13pYrDKEoiu', + 'https://example.com/my-web2-claim.json', + ], + }, + }, + }, +}; diff --git a/ui/pages/confirmations/components/confirm/info/personal-sign/personal-sign.tsx b/ui/pages/confirmations/components/confirm/info/personal-sign/personal-sign.tsx index 2fc34a299182..93e8aff2ef35 100644 --- a/ui/pages/confirmations/components/confirm/info/personal-sign/personal-sign.tsx +++ b/ui/pages/confirmations/components/confirm/info/personal-sign/personal-sign.tsx @@ -22,6 +22,7 @@ import { SignatureRequestType } from '../../../../types/confirm'; import { selectUseTransactionSimulations } from '../../../../selectors/preferences'; import { isSIWESignatureRequest } from '../../../../utils'; import { AlertRow } from '../../../../../../components/app/confirm/info/row/alert-row/alert-row'; +import { SIWESignInfo } from './siwe-sign'; const PersonalSignInfo: React.FC = () => { const t = useI18nContext(); @@ -37,11 +38,11 @@ const PersonalSignInfo: React.FC = () => { } const { from } = currentConfirmation.msgParams; - const isSiweSigReq = isSIWESignatureRequest(currentConfirmation); + const isSIWE = isSIWESignatureRequest(currentConfirmation); return ( <> - {isSiweSigReq && useTransactionSimulations && ( + {isSIWE && useTransactionSimulations && ( { > - {isSiweSigReq && ( + {isSIWE && ( @@ -82,17 +83,21 @@ const PersonalSignInfo: React.FC = () => { padding={2} marginBottom={4} > - - - + {isSIWE ? ( + + ) : ( + + + + )} ); diff --git a/ui/pages/confirmations/components/confirm/info/personal-sign/siwe-sign/__snapshots__/siwe-sign.test.tsx.snap b/ui/pages/confirmations/components/confirm/info/personal-sign/siwe-sign/__snapshots__/siwe-sign.test.tsx.snap new file mode 100644 index 000000000000..1e62756cf223 --- /dev/null +++ b/ui/pages/confirmations/components/confirm/info/personal-sign/siwe-sign/__snapshots__/siwe-sign.test.tsx.snap @@ -0,0 +1,547 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`SIWESignInfo renders correctly for SIWE signature request 1`] = ` +
+
+
+

+ Message +

+
+
+

+ I accept the MetaMask Terms of Service: https://community.metamask.io/tos +

+
+
+
+
+

+ URL +

+
+
+

+ metamask.github.io +

+
+
+
+
+

+ Network +

+
+
+

+ Ethereum Mainnet +

+
+
+
+
+

+ Account +

+
+
+
+ +

+ 0x935e7...05477 +

+
+
+
+
+
+

+ Version +

+
+
+

+ 1 +

+
+
+
+
+

+ Chain ID +

+
+
+

+ 1 +

+
+
+
+
+

+ Nonce +

+
+
+

+ 32891757 +

+
+
+
+
+

+ Issued +

+
+
+

+ 30 September 2021, 16:25 +

+
+
+
+`; + +exports[`SIWESignInfo renders correctly for SIWE signature request with resources 1`] = ` +
+
+
+

+ Message +

+
+
+

+ I accept the MetaMask Terms of Service: https://community.metamask.io/tos +

+
+
+
+
+

+ URL +

+
+
+

+ metamask.github.io +

+
+
+
+
+

+ Network +

+
+
+

+ Ethereum Mainnet +

+
+
+
+
+

+ Account +

+
+
+
+ +

+ 0x935E7...05477 +

+
+
+
+
+
+

+ Version +

+
+
+

+ 1 +

+
+
+
+
+

+ Chain ID +

+
+
+

+ 1 +

+
+
+
+
+

+ Nonce +

+
+
+

+ 32891757 +

+
+
+
+
+

+ Issued +

+
+
+

+ 30 September 2021, 16:25 +

+
+
+
+
+

+ Request ID +

+
+
+

+ some_id +

+
+
+
+
+

+ Resources +

+
+
+

+ ipfs://Qme7ss3ARVgxv6rXqVPiikMJ8u2NLgmgszg13pYrDKEoiu +

+
+
+

+ https://example.com/my-web2-claim.json +

+
+
+
+`; diff --git a/ui/pages/confirmations/components/confirm/info/personal-sign/siwe-sign/index.ts b/ui/pages/confirmations/components/confirm/info/personal-sign/siwe-sign/index.ts new file mode 100644 index 000000000000..e62d38d2b793 --- /dev/null +++ b/ui/pages/confirmations/components/confirm/info/personal-sign/siwe-sign/index.ts @@ -0,0 +1 @@ +export { default as SIWESignInfo } from './siwe-sign'; diff --git a/ui/pages/confirmations/components/confirm/info/personal-sign/siwe-sign/siwe-sign.stories.tsx b/ui/pages/confirmations/components/confirm/info/personal-sign/siwe-sign/siwe-sign.stories.tsx new file mode 100644 index 000000000000..8434f23752eb --- /dev/null +++ b/ui/pages/confirmations/components/confirm/info/personal-sign/siwe-sign/siwe-sign.stories.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { Provider } from 'react-redux'; + +import { SignatureRequestSIWEWithResources } from '../../../../../../../../test/data/confirmations/personal_sign'; +import mockState from '../../../../../../../../test/data/mock-state.json'; +import configureStore from '../../../../../../../store/store'; + +import SIWESignInfo from './siwe-sign'; + +const store = configureStore({ + metamask: { + ...mockState.metamask, + }, + confirm: { + currentConfirmation: SignatureRequestSIWEWithResources, + }, +}); + +const Story = { + title: 'Components/App/Confirm/info/SIWESignInfo', + component: SIWESignInfo, + decorators: [ + (story: () => any) => {story()}, + ], +}; + +export default Story; + +export const DefaultStory = () => ; + +DefaultStory.storyName = 'Default'; diff --git a/ui/pages/confirmations/components/confirm/info/personal-sign/siwe-sign/siwe-sign.test.tsx b/ui/pages/confirmations/components/confirm/info/personal-sign/siwe-sign/siwe-sign.test.tsx new file mode 100644 index 000000000000..abede6223caa --- /dev/null +++ b/ui/pages/confirmations/components/confirm/info/personal-sign/siwe-sign/siwe-sign.test.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import configureMockStore from 'redux-mock-store'; + +import mockState from '../../../../../../../../test/data/mock-state.json'; +import { renderWithProvider } from '../../../../../../../../test/lib/render-helpers'; +import { + SignatureRequestSIWEWithResources, + signatureRequestSIWE, +} from '../../../../../../../../test/data/confirmations/personal_sign'; +import SIWESignInfo from './siwe-sign'; + +describe('SIWESignInfo', () => { + it('renders correctly for SIWE signature request', () => { + const state = { + ...mockState, + confirm: { + currentConfirmation: signatureRequestSIWE, + }, + }; + const mockStore = configureMockStore([])(state); + const { container } = renderWithProvider(, mockStore); + expect(container).toMatchSnapshot(); + }); + + it('renders correctly for SIWE signature request with resources', () => { + const state = { + ...mockState, + confirm: { + currentConfirmation: SignatureRequestSIWEWithResources, + }, + }; + const mockStore = configureMockStore([])(state); + const { container } = renderWithProvider(, mockStore); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/ui/pages/confirmations/components/confirm/info/personal-sign/siwe-sign/siwe-sign.tsx b/ui/pages/confirmations/components/confirm/info/personal-sign/siwe-sign/siwe-sign.tsx new file mode 100644 index 000000000000..8c1260c3ede5 --- /dev/null +++ b/ui/pages/confirmations/components/confirm/info/personal-sign/siwe-sign/siwe-sign.tsx @@ -0,0 +1,85 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; +import { toHex } from '@metamask/controller-utils'; + +import { NETWORK_TO_NAME_MAP } from '../../../../../../../../shared/constants/network'; +import { useI18nContext } from '../../../../../../../hooks/useI18nContext'; +import { currentConfirmationSelector } from '../../../../../../../selectors'; +import { SignatureRequestType } from '../../../../../types/confirm'; +import { + ConfirmInfoRow, + ConfirmInfoRowAddress, + ConfirmInfoRowText, +} from '../../../../../../../components/app/confirm/info/row'; +import { formatDate } from '../../../../../utils/date'; + +const SIWESignInfo: React.FC = () => { + const t = useI18nContext(); + const currentConfirmation = useSelector( + currentConfirmationSelector, + ) as SignatureRequestType; + + const siweMessage = currentConfirmation?.msgParams?.siwe?.parsedMessage; + + if (!siweMessage) { + return null; + } + + const { + address, + chainId, + domain, + issuedAt, + nonce, + requestId, + statement, + resources, + version, + } = siweMessage; + const hexChainId = toHex(chainId); + const network = + (NETWORK_TO_NAME_MAP as Record)[hexChainId] ?? hexChainId; + + return ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + {requestId && ( + + + + )} + {resources && ( + + {resources.map((resource, index) => ( + + ))} + + )} + + ); +}; + +export default SIWESignInfo; diff --git a/ui/pages/confirmations/components/confirm/utils.test.ts b/ui/pages/confirmations/components/confirm/utils.test.ts index 2250d62fa62c..87e6307de7be 100644 --- a/ui/pages/confirmations/components/confirm/utils.test.ts +++ b/ui/pages/confirmations/components/confirm/utils.test.ts @@ -20,9 +20,9 @@ describe('getConfirmationSender()', () => { }); test("returns the sender address from a transaction if it's passed", () => { - const testCurrentConfirmation = - unapprovedPersonalSignMsg as SignatureRequestType; - const { from } = getConfirmationSender(testCurrentConfirmation); + const { from } = getConfirmationSender( + unapprovedPersonalSignMsg as SignatureRequestType, + ); expect(from).toEqual(PERSONAL_SIGN_SENDER_ADDRESS); }); diff --git a/ui/pages/confirmations/types/confirm.ts b/ui/pages/confirmations/types/confirm.ts index 70e173b03deb..a48e1d4984ba 100644 --- a/ui/pages/confirmations/types/confirm.ts +++ b/ui/pages/confirmations/types/confirm.ts @@ -28,6 +28,18 @@ export type SignatureRequestType = { version?: string; siwe?: { isSIWEMessage: boolean; + parsedMessage: null | { + domain: string; + address: string; + statement: string; + uri: string; + version: string; + chainId: number; + nonce: string; + issuedAt: string; + requestId?: string; + resources?: string[]; + }; }; }; type: TransactionType; diff --git a/ui/pages/confirmations/utils/date.test.ts b/ui/pages/confirmations/utils/date.test.ts new file mode 100644 index 000000000000..dc1fdd40454a --- /dev/null +++ b/ui/pages/confirmations/utils/date.test.ts @@ -0,0 +1,15 @@ +import { formatDate } from './date'; + +describe('date util', () => { + describe('formatDate', () => { + it('formats passed date string', () => { + expect(formatDate('2021-09-30T16:25:24.000Z')).toEqual( + '30 September 2021, 16:25', + ); + }); + + it('returns empty string if empty string is passed', () => { + expect(formatDate('')).toEqual(''); + }); + }); +}); diff --git a/ui/pages/confirmations/utils/date.ts b/ui/pages/confirmations/utils/date.ts new file mode 100644 index 000000000000..19ffa5d027d4 --- /dev/null +++ b/ui/pages/confirmations/utils/date.ts @@ -0,0 +1,12 @@ +import { DateTime } from 'luxon'; + +export const formatDate = (dateString: string) => { + if (!dateString) { + return dateString; + } + + return DateTime.fromISO(dateString) + .setLocale('en') + .setZone('utc') + .toFormat('dd LLLL yyyy, HH:mm'); +}; diff --git a/yarn.lock b/yarn.lock index 5f7759c93188..f5cd2d65be86 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9950,6 +9950,13 @@ __metadata: languageName: node linkType: hard +"@types/luxon@npm:^3.4.2": + version: 3.4.2 + resolution: "@types/luxon@npm:3.4.2" + checksum: 10/fd89566e3026559f2bc4ddcc1e70a2c16161905ed50be9473ec0cfbbbe919165041408c4f6e06c4bcf095445535052e2c099087c76b1b38e368127e618fc968d + languageName: node + linkType: hard + "@types/mdast@npm:^3.0.0": version: 3.0.10 resolution: "@types/mdast@npm:3.0.10" @@ -25023,6 +25030,7 @@ __metadata: "@types/gulp-sourcemaps": "npm:^0.0.35" "@types/he": "npm:^1" "@types/jest": "npm:^29.5.12" + "@types/luxon": "npm:^3.4.2" "@types/mocha": "npm:^10.0.3" "@types/node": "npm:^20" "@types/pify": "npm:^5.0.1"