Skip to content

Commit

Permalink
feat: permit signature copy changes (#24975)
Browse files Browse the repository at this point in the history
  • Loading branch information
jpuri authored Jun 12, 2024
1 parent 32af889 commit 85cf430
Show file tree
Hide file tree
Showing 9 changed files with 127 additions and 43 deletions.
14 changes: 10 additions & 4 deletions app/_locales/en/messages.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ exports[`PermitSimulation renders component correctly 1`] = `
class="mm-box mm-text mm-text--body-md mm-box--color-inherit"
style="white-space: pre-wrap;"
>
This transaction gives permission to withdraw your tokens
You're giving the spender permission to spend this many tokens from your account.
</p>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ describe('TypedSignInfo', () => {
expect(getByText('Estimated changes')).toBeDefined();
});

it('displays "Approving to" for permit signature type', () => {
it('displays "Spender" for permit signature type', () => {
const state = {
...mockState,
confirm: {
Expand All @@ -85,6 +85,6 @@ describe('TypedSignInfo', () => {
};
const mockStore = configureMockStore([])(state);
const { getByText } = renderWithProvider(<TypedSignInfo />, mockStore);
expect(getByText('Approving to')).toBeDefined();
expect(getByText('Spender')).toBeDefined();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ import {
BackgroundColor,
BorderRadius,
} from '../../../../../../helpers/constants/design-system';
import { EIP712_PRIMARY_TYPE_PERMIT } from '../../../../constants';
import { SignatureRequestType } from '../../../../types/confirm';
import { parseTypedDataMessage } from '../../../../utils';
import {
isPermitSignatureRequest,
parseTypedDataMessage,
} from '../../../../utils';
import { ConfirmInfoRowTypedSignData } from '../../row/typed-sign-data/typedSignData';
import { PermitSimulation } from './permit-simulation';

Expand All @@ -33,23 +35,24 @@ const TypedSignInfo: React.FC = () => {

const {
domain: { verifyingContract },
primaryType,
message: { spender },
} = parseTypedDataMessage(currentConfirmation.msgParams.data as string);

const isPermit = isPermitSignatureRequest(currentConfirmation);

return (
<>
{primaryType === EIP712_PRIMARY_TYPE_PERMIT && <PermitSimulation />}
{isPermit && <PermitSimulation />}
<Box
backgroundColor={BackgroundColor.backgroundDefault}
borderRadius={BorderRadius.MD}
marginBottom={4}
padding={0}
>
{primaryType === EIP712_PRIMARY_TYPE_PERMIT && (
{isPermit && (
<>
<Box padding={2}>
<ConfirmInfoRow label={t('approvingTo')}>
<ConfirmInfoRow label={t('spender')}>
<ConfirmInfoRowAddress address={spender} />
</ConfirmInfoRow>
</Box>
Expand Down
19 changes: 18 additions & 1 deletion ui/pages/confirmations/components/confirm/title/title.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import React from 'react';
import configureMockStore from 'redux-mock-store';
import { TransactionType } from '@metamask/transaction-controller';

import {
permitSignatureMsg,
unapprovedTypedSignMsgV4,
} from '../../../../../../test/data/confirmations/typed_sign';
import { renderWithProvider } from '../../../../../../test/lib/render-helpers';
import { Confirmation } from '../../../types/confirm';
import { Severity } from '../../../../../helpers/constants/design-system';
Expand Down Expand Up @@ -38,9 +43,21 @@ describe('ConfirmTitle', () => {
).toBeInTheDocument();
});

it('should render the title and description for a permit signature', () => {
const mockStore = configureMockStore([])(
genMockState(permitSignatureMsg as Confirmation),
);
const { getByText } = renderWithProvider(<ConfirmTitle />, mockStore);

expect(getByText('Spending cap request')).toBeInTheDocument();
expect(
getByText('This site wants permission to spend your tokens.'),
).toBeInTheDocument();
});

it('should render the title and description for typed signature', () => {
const mockStore = configureMockStore([])(
genMockState({ type: TransactionType.signTypedData }),
genMockState(unapprovedTypedSignMsgV4 as Confirmation),
);
const { getByText } = renderWithProvider(<ConfirmTitle />, mockStore);

Expand Down
70 changes: 41 additions & 29 deletions ui/pages/confirmations/components/confirm/title/title.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import {
} from '../../../../../helpers/constants/design-system';
import { useI18nContext } from '../../../../../hooks/useI18nContext';
import { currentConfirmationSelector } from '../../../../../selectors';
import { Confirmation } from '../../../types/confirm';
import useAlerts from '../../../../../hooks/useAlerts';
import { getHighestSeverity } from '../../../../../components/app/alert-system/utils';
import GeneralAlert from '../../../../../components/app/alert-system/general-alert/general-alert';
import { Confirmation, SignatureRequestType } from '../../../types/confirm';
import { isPermitSignatureRequest } from '../../../utils';

function ConfirmBannerAlert({ ownerId }: { ownerId: string }) {
const t = useI18nContext();
Expand Down Expand Up @@ -49,45 +50,56 @@ function ConfirmBannerAlert({ ownerId }: { ownerId: string }) {
);
}

type IntlFunction = (str: string) => string;

const getTitle = (t: IntlFunction, confirmation?: Confirmation) => {
switch (confirmation?.type) {
case TransactionType.contractInteraction:
return t('confirmTitleTransaction');
case TransactionType.personalSign:
return t('confirmTitleSignature');
case TransactionType.signTypedData:
return isPermitSignatureRequest(confirmation as SignatureRequestType)
? t('confirmTitlePermitSignature')
: t('confirmTitleSignature');
default:
return '';
}
};

const getDescription = (t: IntlFunction, confirmation?: Confirmation) => {
switch (confirmation?.type) {
case TransactionType.contractInteraction:
return t('confirmTitleDescContractInteractionTransaction');
case TransactionType.personalSign:
return t('confirmTitleDescSignature');
case TransactionType.signTypedData:
return isPermitSignatureRequest(confirmation as SignatureRequestType)
? t('confirmTitleDescPermitSignature')
: t('confirmTitleDescSignature');
default:
return '';
}
};

const ConfirmTitle: React.FC = memo(() => {
const t = useI18nContext();
const currentConfirmation = useSelector(
currentConfirmationSelector,
) as Confirmation;
const currentConfirmation = useSelector(currentConfirmationSelector);

const typeToTitleTKey: Partial<Record<TransactionType, string>> = useMemo(
() => ({
[TransactionType.personalSign]: t('confirmTitleSignature'),
[TransactionType.signTypedData]: t('confirmTitleSignature'),
[TransactionType.contractInteraction]: t('confirmTitleTransaction'),
}),
[],
const title = useMemo(
() => getTitle(t as IntlFunction, currentConfirmation),
[currentConfirmation],
);

const typeToDescTKey: Partial<Record<TransactionType, string>> = useMemo(
() => ({
[TransactionType.personalSign]: t('confirmTitleDescSignature'),
[TransactionType.signTypedData]: t('confirmTitleDescSignature'),
[TransactionType.contractInteraction]: t(
'confirmTitleDescContractInteractionTransaction',
),
}),
[],
const description = useMemo(
() => getDescription(t as IntlFunction, currentConfirmation),
[currentConfirmation],
);

if (!currentConfirmation) {
return null;
}

const title =
typeToTitleTKey[
currentConfirmation.type || TransactionType.contractInteraction
];
const description =
typeToDescTKey[
currentConfirmation.type || TransactionType.contractInteraction
];

return (
<>
<ConfirmBannerAlert ownerId={currentConfirmation.id} />
Expand Down
6 changes: 6 additions & 0 deletions ui/pages/confirmations/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,9 @@ import { CHAINLIST_CHAIN_IDS_MAP } from '../../../../shared/constants/network';
export const EIP712_PRIMARY_TYPE_PERMIT = 'Permit';

export const IGNORE_GAS_LIMIT_CHAIN_IDS = [CHAINLIST_CHAIN_IDS_MAP.MANTLE];

export const TYPED_SIGNATURE_VERSIONS = {
V1: 'V1',
V3: 'V3',
V4: 'V4',
};
21 changes: 21 additions & 0 deletions ui/pages/confirmations/utils/confirm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ import { ApprovalType } from '@metamask/controller-utils';
import { TransactionType } from '@metamask/transaction-controller';

import {
permitSignatureMsg,
unapprovedTypedSignMsgV4,
} from '../../../../test/data/confirmations/typed_sign';
import { SignatureRequestType } from '../types/confirm';
import {
isPermitSignatureRequest,
isSignatureApprovalRequest,
isSignatureTransactionType,
parseSanitizeTypedDataMessage,
Expand Down Expand Up @@ -71,4 +77,19 @@ describe('confirm util', () => {
expect(result).toStrictEqual(false);
});
});

describe('isPermitSignatureRequest', () => {
it('returns true for permit signature requests', () => {
const result = isPermitSignatureRequest(
permitSignatureMsg as SignatureRequestType,
);
expect(result).toStrictEqual(true);
});
it('returns false for request not of type permit signature', () => {
const result = isPermitSignatureRequest(
unapprovedTypedSignMsgV4 as SignatureRequestType,
);
expect(result).toStrictEqual(false);
});
});
});
19 changes: 19 additions & 0 deletions ui/pages/confirmations/utils/confirm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import { Json } from '@metamask/utils';

import { sanitizeMessage } from '../../../helpers/utils/util';
import { SignatureRequestType } from '../types/confirm';
import {
EIP712_PRIMARY_TYPE_PERMIT,
TYPED_SIGNATURE_VERSIONS,
} from '../constants';

export const REDESIGN_APPROVAL_TYPES = [
ApprovalType.EthSignTypedData,
Expand Down Expand Up @@ -47,3 +51,18 @@ export const parseSanitizeTypedDataMessage = (dataToParse: string) => {

export const isSIWESignatureRequest = (request: SignatureRequestType) =>
request.msgParams?.siwe?.isSIWEMessage;

export const isPermitSignatureRequest = (request: SignatureRequestType) => {
if (
!request ||
!isSignatureTransactionType(request) ||
request.type !== 'eth_signTypedData' ||
request.msgParams?.version?.toUpperCase() === TYPED_SIGNATURE_VERSIONS.V1
) {
return false;
}
const { primaryType } = parseTypedDataMessage(
request.msgParams?.data as string,
);
return primaryType === EIP712_PRIMARY_TYPE_PERMIT;
};

0 comments on commit 85cf430

Please sign in to comment.