Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SignTypedData simulation #1162

Merged
merged 13 commits into from
Dec 18, 2023
50 changes: 49 additions & 1 deletion src/core/graphql/queries/metadata.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ fragment target on TransactionSimulationTarget {
query simulateTransactions(
$chainId: Int!
$transactions: [Transaction!]!
$domain: String!
$domain: String
) {
simulateTransactions(
chainID: $chainId
Expand Down Expand Up @@ -177,6 +177,7 @@ query simulateTransactions(
}
quantityAllowed
quantityAtRisk
expiration
}
meta {
to {
Expand All @@ -187,6 +188,53 @@ query simulateTransactions(
}
}

query simulateMessage(
$chainId: Int!
$address: String!
$message: Message!
$domain: String
) {
simulateMessage(
chainID: $chainId
address: $address
message: $message
domain: $domain
) {
scanning {
result
description
}
simulation {
in {
...change
}
out {
...change
}
approvals {
asset {
...asset
}
spender {
...target
}
quantityAllowed
quantityAtRisk
expiration
}
meta {
to {
...target
}
}
}
error {
message
type
}
}
}

mutation validatePointsReferralCode($address: String!, $referral: String) {
onboardPoints(address: $address, signature: "", referral: $referral) {
error {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,12 @@ const InfoRow = ({
>
<Inline alignVertical="center" space="12px" wrap={false}>
<Symbol size={14} symbol={symbol} weight="medium" color="labelTertiary" />
<Text color="labelTertiary" size="12pt" weight="semibold">
<Text
color="labelTertiary"
size="12pt"
weight="semibold"
whiteSpace="nowrap"
>
{label}
</Text>
</Inline>
Expand Down
154 changes: 87 additions & 67 deletions src/entries/popup/pages/messages/SignMessage/SignMessageInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { TransactionRequest } from '@ethersproject/providers';
import { motion } from 'framer-motion';
import { useMemo, useState } from 'react';
import { AnimatePresence, motion } from 'framer-motion';
import { useState } from 'react';

import { DAppStatus } from '~/core/graphql/__generated__/metadata';
import { i18n } from '~/core/languages';
Expand All @@ -10,16 +9,17 @@ import { ChainId } from '~/core/types/chains';
import { copy } from '~/core/utils/copy';
import { getSigningRequestDisplayDetails } from '~/core/utils/signMessages';
import { truncateString } from '~/core/utils/strings';
import { Box, Inline, Stack, Symbol, Text } from '~/design-system';
import { Box, Inline, Separator, Stack, Symbol, Text } from '~/design-system';
import { DappIcon } from '~/entries/popup/components/DappIcon/DappIcon';
import { useAppSession } from '~/entries/popup/hooks/useAppSession';

import { DappHostName, ThisDappIsLikelyMalicious } from '../DappScanStatus';
import { DappHostName, MaliciousRequestWarning } from '../DappScanStatus';
import { SimulationOverview } from '../Simulation';
import { CopyButton, TabContent, Tabs } from '../Tabs';
import {
SimulationError,
TransactionSimulation,
useSimulateTransaction,
useSimulateMessage,
} from '../useSimulateTransaction';

interface SignMessageProps {
Expand All @@ -28,25 +28,34 @@ interface SignMessageProps {

function Overview({
message,
typedData,
simulation,
status,
error,
}: {
message?: string;
message: string | undefined;
typedData: boolean;
simulation: TransactionSimulation | undefined;
status: 'loading' | 'error' | 'success';
error: SimulationError | null;
}) {
return (
<Stack space="16px">
{/* <Text size="12pt" weight="semibold" color="labelTertiary">
{i18n.t('simulation.title')}
</Text> */}
<Stack space="16px" paddingTop="14px" marginTop="-14px">
{typedData && (
<>
<Text size="12pt" weight="semibold" color="labelTertiary">
{i18n.t('simulation.title')}
</Text>

{/* <SimulationOverview
simulation={simulation}
status={status}
error={error}
/>
<SimulationOverview
simulation={simulation}
status={status}
error={error}
/>

<Separator color="separatorTertiary" /> */}
<Separator color="separatorTertiary" />
</>
)}

<Stack space="20px">
<Inline space="8px" alignVertical="center">
Expand All @@ -55,16 +64,24 @@ function Overview({
{i18n.t('simulation.signature.message')}
</Text>
</Inline>
<Text
testId="sign-message-text"
as="pre"
size="12pt"
weight="semibold"
color="labelTertiary"
whiteSpace="pre-wrap"
<Box
style={{ overflowX: 'scroll', overflowY: 'hidden' }}
paddingHorizontal="20px"
marginHorizontal="-20px"
paddingVertical="8px" // this is to not clip the ending of the message
marginVertical="-8px" // since overflowY is hidden
>
{message}
</Text>
<Text
testId="sign-message-text"
as="pre"
size="12pt"
weight="semibold"
color="labelTertiary"
whiteSpace="pre-wrap"
>
{message}
</Text>
</Box>
</Stack>
</Stack>
);
Expand All @@ -74,32 +91,26 @@ export const SignMessageInfo = ({ request }: SignMessageProps) => {
const dappUrl = request?.meta?.sender?.url || '';
const { data: dappMetadata } = useDappMetadata({ url: dappUrl });

const { message } = useMemo(() => {
const { message, typedData } = getSigningRequestDisplayDetails(request);
return { message, typedData };
}, [request]);
const { message, typedData } = getSigningRequestDisplayDetails(request);

const isScamDapp = dappMetadata?.status === DAppStatus.Scam;
const [expanded, setExpanded] = useState(false);

const { activeSession } = useAppSession({ host: dappMetadata?.appHost });

const txRequest = request?.params?.[0] as TransactionRequest;

const chainId = activeSession?.chainId || ChainId.mainnet;

const {
data: simulation,
status,
error,
isRefetching,
} = useSimulateTransaction({
} = useSimulateMessage({
chainId,
transaction: {
from: txRequest.from || '',
to: txRequest.to || '',
value: txRequest.value?.toString() || '0',
data: txRequest.data?.toString() || '',
address: activeSession?.address,
message: {
method: request.method,
params: (request.params || []) as string[],
},
domain: dappUrl,
});
Expand All @@ -121,34 +132,36 @@ export const SignMessageInfo = ({ request }: SignMessageProps) => {
gap="24px"
height="full"
>
<motion.div
style={{
maxHeight: expanded ? 0 : '100%',
overflow: expanded ? 'hidden' : 'unset',
paddingTop: expanded ? 0 : '20px',
opacity: expanded ? 0 : 1,
}}
>
<Stack space="16px" alignItems="center">
<DappIcon appLogo={dappMetadata?.appLogo} size="36px" />
<Stack space="12px">
<DappHostName
hostName={dappMetadata?.appHostName}
dappStatus={dappMetadata?.status}
/>
<Text
align="center"
size="14pt"
weight="bold"
color={isScamDapp ? 'red' : 'labelSecondary'}
>
{isScamDapp
? i18n.t('approve_request.dangerous_request')
: i18n.t('approve_request.message_signing_request')}
</Text>
</Stack>
</Stack>
</motion.div>
<AnimatePresence mode="popLayout">
{!expanded && (
<motion.div
style={{ paddingTop: 20 }}
initial={{ opacity: 0, scale: 0.9, y: -8 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.9, y: -8 }}
>
<Stack space="16px" alignItems="center">
<DappIcon appLogo={dappMetadata?.appLogo} size="36px" />
<Stack space="12px">
<DappHostName
hostName={dappMetadata?.appHostName}
dappStatus={dappMetadata?.status}
/>
<Text
align="center"
size="14pt"
weight="bold"
color={isScamDapp ? 'red' : 'labelSecondary'}
>
{isScamDapp
? i18n.t('approve_request.dangerous_request')
: i18n.t('approve_request.message_signing_request')}
</Text>
</Stack>
</Stack>
</motion.div>
)}
</AnimatePresence>

<Tabs
tabs={[tabLabel('overview')]}
Expand All @@ -158,6 +171,7 @@ export const SignMessageInfo = ({ request }: SignMessageProps) => {
<TabContent value={tabLabel('overview')}>
<Overview
message={message}
typedData={!!typedData}
simulation={simulation}
status={status === 'error' && isRefetching ? 'loading' : status}
error={error}
Expand All @@ -175,7 +189,13 @@ export const SignMessageInfo = ({ request }: SignMessageProps) => {
</TabContent>
</Tabs>

{!expanded && isScamDapp && <ThisDappIsLikelyMalicious />}
{!expanded && simulation && simulation.scanning.result !== 'OK' && (
<MaliciousRequestWarning
title={i18n.t('approve_request.malicious_transaction_warning.title')}
description={simulation.scanning.description}
symbol="exclamationmark.octagon.fill"
/>
)}
</Box>
);
};
35 changes: 24 additions & 11 deletions src/entries/popup/pages/messages/Simulation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { formatUnits } from '@ethersproject/units';
import { ReactNode } from 'react';

import { i18n } from '~/core/languages';
import { formatNumber } from '~/core/utils/formatNumber';
import { Inline, Stack, Symbol, Text } from '~/design-system';
import { createNumberFormatter } from '~/core/utils/formatNumber';
import { Inline, Stack, Symbol, Text, TextOverflow } from '~/design-system';
import { TextColor } from '~/design-system/styles/designTokens';

import { CoinIcon, NFTIcon } from '../../components/CoinIcon/CoinIcon';
Expand All @@ -30,6 +30,10 @@ export function SimulationNoChangesDetected() {
);
}

const { format: formatNumber } = createNumberFormatter({
notation: 'compact',
});

function SimulatedChangeRow({
asset,
quantity,
Expand All @@ -45,25 +49,34 @@ function SimulatedChangeRow({
label: string;
}) {
return (
<Inline space="24px" alignHorizontal="justify" alignVertical="center">
<Inline space="12px" alignVertical="center">
<Inline
wrap={false}
space="24px"
alignHorizontal="justify"
alignVertical="center"
>
<Inline wrap={false} space="12px" alignVertical="center">
{symbol}
<Text size="14pt" weight="bold" color="label">
{label}
</Text>
</Inline>
<Inline space="6px" alignVertical="center">
<Inline wrap={false} space="6px" alignVertical="center">
{asset?.type === 'nft' ? (
<NFTIcon asset={asset} size={16} />
) : (
<CoinIcon asset={asset} size={14} />
)}
<Text size="14pt" weight="bold" color={color}>
{quantity === 'UNLIMITED'
? i18n.t('approvals.unlimited')
: formatNumber(formatUnits(quantity, asset.decimals))}{' '}
{asset.symbol}
</Text>
<Inline wrap={false} space="4px" alignVertical="center">
<TextOverflow size="14pt" weight="bold" color={color}>
{quantity === 'UNLIMITED' || +quantity > 999e12 // say unlimited if more than 999T
? i18n.t('approvals.unlimited')
: formatNumber(formatUnits(quantity, asset.decimals))}{' '}
</TextOverflow>
<Text size="14pt" weight="bold" color={color}>
{asset.symbol}
</Text>
</Inline>
</Inline>
</Inline>
);
Expand Down
Loading
Loading