Skip to content

Commit 9feefd3

Browse files
authored
feat: SIWE signature related changes on personal signature page (#13329)
## **Description** SIWE signature related changes on personal signature page. ## **Related issues** Fixes: MetaMask/MetaMask-planning#4076 ## **Manual testing steps** 1. Go to test dapp 2. Submit SIWE and check page that renders ## **Screenshots/Recordings** <img width="403" alt="Screenshot 2025-02-03 at 7 07 24 PM" src="https://github.com/user-attachments/assets/0ee93c79-e611-4373-9d6d-6d91c5877fb3" /> <img width="402" alt="Screenshot 2025-02-04 at 5 38 15 PM" src="https://github.com/user-attachments/assets/6e3cd065-07ee-4062-afae-f576c08244ee" /> ## **Pre-merge author checklist** - [X] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [X] I've completed the PR template to the best of my ability - [X] I’ve included tests if applicable - [X] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [X] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.
1 parent 227e15f commit 9feefd3

File tree

12 files changed

+358
-39
lines changed

12 files changed

+358
-39
lines changed

app/components/Views/confirmations/components/Confirm/Info/PersonalSign/Message.tsx

Lines changed: 100 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
import React, { useMemo } from 'react';
2-
import { StyleSheet, Text } from 'react-native';
2+
import { StyleProp, StyleSheet, Text, TextStyle, View } from 'react-native';
33
import { hexToText } from '@metamask/controller-utils';
4+
import { numberToHex } from '@metamask/utils';
45

6+
import { strings } from '../../../../../../../../locales/i18n';
57
import { Theme } from '../../../../../../../util/theme/models';
68
import { fontStyles } from '../../../../../../../styles/common';
79
import { useStyles } from '../../../../../../../component-library/hooks';
810
import { sanitizeString } from '../../../../../../../util/string';
9-
import useApprovalRequest from '../../../../hooks/useApprovalRequest';
11+
import { getSIWEDetails, SIWEMessage } from '../../../../utils/signature';
12+
import { useSignatureRequest } from '../../../../hooks/useSignatureRequest';
13+
import Address from '../../../UI/InfoRow/InfoValue/Address';
14+
import DisplayURL from '../../../UI/InfoRow/InfoValue/DisplayURL';
15+
import InfoDate from '../../../UI/InfoRow/InfoValue/InfoDate';
16+
import InfoRow from '../../../UI/InfoRow';
17+
import Network from '../../../UI/InfoRow/InfoValue/Network';
1018
import SignatureMessageSection from '../../SignatureMessageSection';
1119

1220
const styleSheet = (params: { theme: Theme }) => {
@@ -19,23 +27,106 @@ const styleSheet = (params: { theme: Theme }) => {
1927
fontSize: 14,
2028
fontWeight: '400',
2129
},
30+
siweTos: {
31+
color: theme.colors.text.default,
32+
...fontStyles.normal,
33+
fontSize: 14,
34+
fontWeight: '400',
35+
paddingHorizontal: 8,
36+
marginBottom: 8,
37+
},
2238
});
2339
};
2440

41+
const DetailedSIWEMessage = ({
42+
parsedMessage,
43+
styles,
44+
}: {
45+
parsedMessage: SIWEMessage;
46+
styles: { siweTos: StyleProp<TextStyle> };
47+
}) => {
48+
const {
49+
uri,
50+
chainId,
51+
address,
52+
version,
53+
nonce,
54+
issuedAt,
55+
requestId,
56+
resources,
57+
} = parsedMessage;
58+
return (
59+
<View>
60+
<Text style={styles.siweTos}>{parsedMessage?.statement}</Text>
61+
<InfoRow label={strings('confirm.siwe_message.url')}>
62+
<DisplayURL url={uri} />
63+
</InfoRow>
64+
<InfoRow label={strings('confirm.siwe_message.network')}>
65+
<Network chainId={numberToHex(parseInt(chainId))} />
66+
</InfoRow>
67+
<InfoRow label={strings('confirm.siwe_message.account')}>
68+
<Address address={address} chainId={chainId} />
69+
</InfoRow>
70+
<InfoRow label={strings('confirm.siwe_message.version')}>
71+
{version}
72+
</InfoRow>
73+
<InfoRow label={strings('confirm.siwe_message.chain_id')}>
74+
{chainId.toString()}
75+
</InfoRow>
76+
<InfoRow label={strings('confirm.siwe_message.nonce')}>{nonce}</InfoRow>
77+
<InfoRow label={strings('confirm.siwe_message.issued')}>
78+
<InfoDate
79+
unixTimestamp={Math.floor(new Date(issuedAt).getTime() / 1000)}
80+
/>
81+
</InfoRow>
82+
{requestId && (
83+
<InfoRow label={strings('confirm.siwe_message.requestId')}>
84+
{requestId}
85+
</InfoRow>
86+
)}
87+
{resources && (
88+
<InfoRow label={strings('confirm.siwe_message.resources')}>
89+
{resources.map((resource, index) => (
90+
<Text key={`resource-${index}`}>{resource}</Text>
91+
))}
92+
</InfoRow>
93+
)}
94+
</View>
95+
);
96+
};
97+
2598
const Message = () => {
26-
const { approvalRequest } = useApprovalRequest();
99+
const signatureRequest = useSignatureRequest();
27100
const { styles } = useStyles(styleSheet, {});
28101

29-
const message = useMemo(
30-
() => sanitizeString(hexToText(approvalRequest?.requestData?.data)),
31-
[approvalRequest?.requestData?.data],
102+
const { isSIWEMessage, parsedMessage } = useMemo(
103+
() => getSIWEDetails(signatureRequest),
104+
[signatureRequest],
32105
);
33106

107+
const completeMessage = useMemo(() => {
108+
if (!signatureRequest?.messageParams?.data) {
109+
return '';
110+
}
111+
return sanitizeString(
112+
hexToText(signatureRequest?.messageParams?.data as string),
113+
);
114+
}, [signatureRequest?.messageParams?.data]);
115+
34116
return (
35117
<SignatureMessageSection
36-
messageCollapsed={message}
37-
messageExpanded={<Text style={styles.messageExpanded}>{message}</Text>}
38-
copyMessageText={message}
118+
messageCollapsed={
119+
isSIWEMessage ? parsedMessage?.statement : completeMessage
120+
}
121+
messageExpanded={
122+
isSIWEMessage ? (
123+
<DetailedSIWEMessage parsedMessage={parsedMessage} styles={styles} />
124+
) : (
125+
<Text style={styles.messageExpanded}>{completeMessage}</Text>
126+
)
127+
}
128+
copyMessageText={completeMessage}
129+
collapsedSectionAllowMultiline
39130
/>
40131
);
41132
};

app/components/Views/confirmations/components/Confirm/Info/PersonalSign/PersonalSign.test.tsx

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import React from 'react';
2+
import { fireEvent } from '@testing-library/react-native';
23

34
import renderWithProvider from '../../../../../../../util/test/renderWithProvider';
4-
import { personalSignatureConfirmationState } from '../../../../../../../util/test/confirm-data-helpers';
5+
import {
6+
personalSignatureConfirmationState,
7+
siweSignatureConfirmationState,
8+
} from '../../../../../../../util/test/confirm-data-helpers';
59
import PersonalSign from './PersonalSign';
610

711
describe('PersonalSign', () => {
@@ -14,4 +18,42 @@ describe('PersonalSign', () => {
1418
expect(getByText('Message')).toBeDefined();
1519
expect(getByText('Example `personal_sign` message')).toBeDefined();
1620
});
21+
22+
it('should render tos statement for SIWE', async () => {
23+
const { getByText, getAllByText } = renderWithProvider(<PersonalSign />, {
24+
state: siweSignatureConfirmationState,
25+
});
26+
expect(getByText('Message')).toBeDefined();
27+
expect(
28+
getByText(
29+
'I accept the MetaMask Terms of Service: https://community.metamask.io/tos',
30+
),
31+
).toBeDefined();
32+
fireEvent.press(getByText('Message'));
33+
expect(getAllByText('Message')).toHaveLength(2);
34+
expect(
35+
getAllByText(
36+
'I accept the MetaMask Terms of Service: https://community.metamask.io/tos',
37+
),
38+
).toHaveLength(2);
39+
expect(getByText('URL')).toBeDefined();
40+
expect(getAllByText('metamask.github.io')).toHaveLength(2);
41+
expect(getByText('Network')).toBeDefined();
42+
expect(getByText('Ethereum Mainnet')).toBeDefined();
43+
expect(getByText('Account')).toBeDefined();
44+
expect(getAllByText('0x8Eeee...73D12')).toBeDefined();
45+
expect(getByText('Version')).toBeDefined();
46+
expect(getAllByText('1')).toHaveLength(2);
47+
expect(getByText('Chain ID')).toBeDefined();
48+
expect(getByText('Nonce')).toBeDefined();
49+
expect(getByText('32891757')).toBeDefined();
50+
expect(getByText('Issued')).toBeDefined();
51+
expect(getByText('30 September 2021, 16:25')).toBeDefined();
52+
expect(getByText('Request ID')).toBeDefined();
53+
expect(getByText('12345')).toBeDefined();
54+
expect(getByText('Resources')).toBeDefined();
55+
expect(getByText('resource-1')).toBeDefined();
56+
expect(getByText('resource-2')).toBeDefined();
57+
expect(getByText('resource-3')).toBeDefined();
58+
});
1759
});

app/components/Views/confirmations/components/Confirm/Info/Shared/InfoRowOrigin/InfoRowOrigin.test.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import React from 'react';
22

33
import renderWithProvider from '../../../../../../../../util/test/renderWithProvider';
4-
import { typedSignV1ConfirmationState } from '../../../../../../../../util/test/confirm-data-helpers';
4+
import {
5+
siweSignatureConfirmationState,
6+
typedSignV1ConfirmationState,
7+
} from '../../../../../../../../util/test/confirm-data-helpers';
58
import InfoRowOrigin from './InfoRowOrigin';
69

710
describe('InfoRowOrigin', () => {
@@ -12,4 +15,12 @@ describe('InfoRowOrigin', () => {
1215
expect(getByText('Request from')).toBeDefined();
1316
expect(getByText('metamask.github.io')).toBeDefined();
1417
});
18+
19+
it('should display signing in with information for SIWE sign request', async () => {
20+
const { getByText } = renderWithProvider(<InfoRowOrigin />, {
21+
state: siweSignatureConfirmationState,
22+
});
23+
expect(getByText('Signing in with')).toBeDefined();
24+
expect(getByText('0x935E7...05477')).toBeDefined();
25+
});
1526
});

app/components/Views/confirmations/components/Confirm/Info/Shared/InfoRowOrigin/InfoRowOrigin.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
11
import React from 'react';
2+
import { Hex } from '@metamask/utils';
23

34
import { ConfirmationPageSectionsSelectorIDs } from '../../../../../../../../../e2e/selectors/Confirmation/ConfirmationView.selectors';
45
import { strings } from '../../../../../../../../../locales/i18n';
56
import useApprovalRequest from '../../../../../hooks/useApprovalRequest';
6-
import InfoSection from '../../../../UI/InfoRow/InfoSection';
7-
import InfoRow from '../../../../UI/InfoRow';
7+
import { getSIWEDetails } from '../../../../../utils/signature';
8+
import { useSignatureRequest } from '../../../../../hooks/useSignatureRequest';
9+
import Address from '../../../../UI/InfoRow/InfoValue/Address';
810
import DisplayURL from '../../../../UI/InfoRow/InfoValue/DisplayURL';
11+
import InfoRow from '../../../../UI/InfoRow';
12+
import InfoSection from '../../../../UI/InfoRow/InfoSection';
913

1014
const InfoRowOrigin = () => {
1115
const { approvalRequest } = useApprovalRequest();
16+
const signatureRequest = useSignatureRequest();
17+
const chainId = signatureRequest?.chainId as Hex;
18+
const { isSIWEMessage } = getSIWEDetails(signatureRequest);
19+
const fromAddress = signatureRequest?.messageParams?.from as string;
1220

1321
if (!approvalRequest) {
1422
return null;
@@ -24,6 +32,11 @@ const InfoRowOrigin = () => {
2432
>
2533
<DisplayURL url={approvalRequest.origin} />
2634
</InfoRow>
35+
{isSIWEMessage && (
36+
<InfoRow label={strings('confirm.signing_in_with')}>
37+
<Address address={fromAddress} chainId={chainId} />
38+
</InfoRow>
39+
)}
2740
</InfoSection>
2841
);
2942
};

app/components/Views/confirmations/components/Confirm/SignatureMessageSection/SignatureMessageSection.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@ interface SignatureMessageSectionProps {
1313
messageCollapsed?: ReactNode | string;
1414
messageExpanded: ReactNode;
1515
copyMessageText: string;
16+
collapsedSectionAllowMultiline?: boolean;
1617
}
1718

1819
const SignatureMessageSection = ({
1920
messageCollapsed,
2021
messageExpanded,
2122
copyMessageText,
23+
collapsedSectionAllowMultiline = false,
2224
}: SignatureMessageSectionProps) => {
2325
const { styles } = useStyles(styleSheet, {});
2426

@@ -30,7 +32,10 @@ const SignatureMessageSection = ({
3032
{messageCollapsed && (
3133
<View style={styles.message}>
3234
{typeof messageCollapsed === 'string' ? (
33-
<Text style={styles.description} numberOfLines={1}>
35+
<Text
36+
style={styles.description}
37+
numberOfLines={collapsedSectionAllowMultiline ? undefined : 1}
38+
>
3439
{messageCollapsed}
3540
</Text>
3641
) : (

app/components/Views/confirmations/components/Confirm/Title/Title.test.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import React from 'react';
22

33
import renderWithProvider from '../../../../../../util/test/renderWithProvider';
4-
import { personalSignatureConfirmationState } from '../../../../../../util/test/confirm-data-helpers';
4+
import {
5+
personalSignatureConfirmationState,
6+
siweSignatureConfirmationState,
7+
} from '../../../../../../util/test/confirm-data-helpers';
58
import Title from './Title';
69

710
describe('Title', () => {
@@ -14,4 +17,14 @@ describe('Title', () => {
1417
getByText('Review request details before you confirm.'),
1518
).toBeDefined();
1619
});
20+
21+
it('should render correct title and subtitle for personal siwe request', async () => {
22+
const { getByText } = renderWithProvider(<Title />, {
23+
state: siweSignatureConfirmationState,
24+
});
25+
expect(getByText('Sign-in request')).toBeDefined();
26+
expect(
27+
getByText('A site wants you to sign in to prove you own this account.'),
28+
).toBeDefined();
29+
});
1730
});

app/components/Views/confirmations/components/Confirm/Title/Title.tsx

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,32 @@
11
import React from 'react';
2+
import { SignatureRequest } from '@metamask/signature-controller';
23
import { Text, View } from 'react-native';
34
import { TransactionType } from '@metamask/transaction-controller';
45

56
import { strings } from '../../../../../../../locales/i18n';
67
import { useStyles } from '../../../../../../component-library/hooks';
78
import useApprovalRequest from '../../../hooks/useApprovalRequest';
9+
import { isSIWESignatureRequest } from '../../../utils/signature';
10+
import { useSignatureRequest } from '../../../hooks/useSignatureRequest';
811
import styleSheet from './Title.styles';
912

10-
const getTitleAndSubTitle = (confirmationType?: string) => {
13+
const getTitleAndSubTitle = (
14+
confirmationType?: string,
15+
signatureRequest?: SignatureRequest,
16+
) => {
1117
switch (confirmationType) {
12-
case TransactionType.personalSign:
18+
case TransactionType.personalSign: {
19+
if (isSIWESignatureRequest(signatureRequest)) {
20+
return {
21+
title: strings('confirm.title.signature_siwe'),
22+
subTitle: strings('confirm.sub_title.signature_siwe'),
23+
};
24+
}
25+
return {
26+
title: strings('confirm.title.signature'),
27+
subTitle: strings('confirm.sub_title.signature'),
28+
};
29+
}
1330
case TransactionType.signTypedData:
1431
return {
1532
title: strings('confirm.title.signature'),
@@ -22,9 +39,13 @@ const getTitleAndSubTitle = (confirmationType?: string) => {
2239

2340
const Title = () => {
2441
const { approvalRequest } = useApprovalRequest();
42+
const signatureRequest = useSignatureRequest();
2543
const { styles } = useStyles(styleSheet, {});
2644

27-
const { title, subTitle } = getTitleAndSubTitle(approvalRequest?.type);
45+
const { title, subTitle } = getTitleAndSubTitle(
46+
approvalRequest?.type,
47+
signatureRequest,
48+
);
2849

2950
return (
3051
<View style={styles.titleContainer}>

app/components/Views/confirmations/components/UI/InfoRow/InfoValue/Network/Network.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const Network = ({ chainId }: NetworkProps) => {
2727
variant={AvatarVariant.Network}
2828
name={networkName}
2929
imageSource={networkImage}
30-
size={AvatarSize.Sm}
30+
size={AvatarSize.Xs}
3131
/>
3232
<Text style={styles.value}>{networkName}</Text>
3333
</View>

app/components/Views/confirmations/components/UI/InfoRow/InfoValue/Network/__snapshots__/Network.test.tsx.snap

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ exports[`Network should match snapshot 1`] = `
1515
{
1616
"alignItems": "center",
1717
"backgroundColor": "#ffffff",
18-
"borderRadius": 12,
19-
"height": 24,
18+
"borderRadius": 8,
19+
"height": 16,
2020
"justifyContent": "center",
2121
"overflow": "hidden",
22-
"width": 24,
22+
"width": 16,
2323
}
2424
}
2525
>
@@ -29,8 +29,8 @@ exports[`Network should match snapshot 1`] = `
2929
source={1}
3030
style={
3131
{
32-
"height": 24,
33-
"width": 24,
32+
"height": 16,
33+
"width": 16,
3434
}
3535
}
3636
testID="network-avatar-image"

0 commit comments

Comments
 (0)