Skip to content

Commit

Permalink
scam dapp connection warning (#1051)
Browse files Browse the repository at this point in the history
  • Loading branch information
greg-schrammel authored Oct 17, 2023
1 parent c9364ab commit 93d8f23
Show file tree
Hide file tree
Showing 17 changed files with 432 additions and 220 deletions.
3 changes: 3 additions & 0 deletions src/core/resources/metadata/dapp.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useQuery } from '@tanstack/react-query';

import { metadataClient } from '~/core/graphql';
import { DAppStatus } from '~/core/graphql/__generated__/metadata';
import {
QueryFunctionArgs,
createQueryKey,
Expand All @@ -23,6 +24,7 @@ export interface DappMetadata {
appShortName: string;
appLogo?: string;
timestamp?: number;
status?: DAppStatus;
}

// ///////////////////////////////////////////////
Expand Down Expand Up @@ -76,6 +78,7 @@ async function fetchDappMetadata({
appName,
appShortName,
appLogo: response?.dApp?.iconURL,
status: response.dApp?.status,
};
return dappMetadata;
}
Expand Down
2 changes: 1 addition & 1 deletion src/design-system/styles/core.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ const boxBaseProperties = defineProperties({
bottom: positionSpace,
cursor: cursorOpts,
display: ['none', 'flex', 'block', 'inline'],
flexDirection: ['row', 'column'],
flexDirection: ['row', 'column', 'column-reverse'],
flexWrap: ['wrap'],
flexBasis: ['0'],
flexGrow: ['0', '1'],
Expand Down
2 changes: 2 additions & 0 deletions src/design-system/styles/designTokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,8 @@ export const symbolNames = selectSymbolNames(
'creditcard.fill',
'building.columns',
'paintbrush.pointed.fill',
'exclamationmark.octagon.fill',
'network.badge.shield.half.filled',
'safari.fill',
);
export type SymbolName = (typeof symbolNames)[number];
Expand Down
54 changes: 54 additions & 0 deletions src/design-system/symbols/generated/index.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useMemo } from 'react';

import { DAppStatus } from '~/core/graphql/__generated__/metadata';
import { i18n } from '~/core/languages';
import { useConnectedToHardhatStore } from '~/core/state/currentSettings/connectedToHardhat';
import { ChainId } from '~/core/types/chains';
Expand All @@ -13,9 +14,11 @@ import { useNativeAsset } from '../useNativeAsset';
export const useApproveAppRequestValidations = ({
chainId,
selectedGas,
dappStatus,
}: {
chainId: ChainId;
selectedGas?: GasFeeParams | GasFeeLegacyParams;
dappStatus?: DAppStatus;
}) => {
const { connectedToHardhat } = useConnectedToHardhatStore();
const chainIdToUse = connectedToHardhat ? ChainId.mainnet : chainId;
Expand All @@ -30,12 +33,16 @@ export const useApproveAppRequestValidations = ({
}, [nativeAsset?.balance?.amount, selectedGas?.gasFee?.amount]);

const buttonLabel = useMemo(() => {
if (dappStatus === DAppStatus.Scam)
return i18n.t('approve_request.send_transaction_anyway');

if (!enoughNativeAssetForGas)
return i18n.t('approve_request.insufficient_native_asset_for_gas', {
symbol: getChain({ chainId: chainIdToUse }).nativeCurrency.name,
});

return i18n.t('approve_request.send_transaction');
}, [chainIdToUse, enoughNativeAssetForGas]);
}, [chainIdToUse, enoughNativeAssetForGas, dappStatus]);

return {
enoughNativeAssetForGas:
Expand Down
28 changes: 23 additions & 5 deletions src/entries/popup/pages/messages/BottomActions/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Address, useBalance } from 'wagmi';

import { analytics } from '~/analytics';
import { event } from '~/analytics/event';
import { DAppStatus } from '~/core/graphql/__generated__/metadata';
import { i18n } from '~/core/languages';
import { shortcuts } from '~/core/references/shortcuts';
import { useCurrentAddressStore } from '~/core/state';
Expand Down Expand Up @@ -344,28 +345,40 @@ export const AcceptRequestButton = ({
label,
waitingForDevice,
loading = false,
dappStatus,
}: {
autoFocus?: boolean;
disabled?: boolean;
onClick: () => void;
label: string;
waitingForDevice?: boolean;
loading?: boolean;
dappStatus?: DAppStatus;
}) => {
const isScamDapp = dappStatus === DAppStatus.Scam;
const isVerifiedDapp = dappStatus === DAppStatus.Verified;

const buttonVariant = isScamDapp ? 'transparent' : 'flat';
const color = isVerifiedDapp ? 'blue' : 'accent';

return (
<Button
autoFocus={autoFocus}
emoji={waitingForDevice ? '👀' : undefined}
color={waitingForDevice ? 'label' : 'accent'}
color={waitingForDevice ? 'label' : color}
height="44px"
width="full"
onClick={(!waitingForDevice && onClick) || undefined}
testId="accept-request-button"
variant={waitingForDevice || disabled ? 'disabled' : 'flat'}
variant={waitingForDevice || disabled ? 'disabled' : buttonVariant}
disabled={disabled}
tabIndex={0}
>
<TextOverflow weight="bold" size="16pt" color="label">
<TextOverflow
weight="bold"
size="16pt"
color={isScamDapp ? 'red' : 'label'}
>
{loading ? <Spinner size={16} color="label" /> : label}
</TextOverflow>
</Button>
Expand All @@ -376,11 +389,15 @@ export const RejectRequestButton = ({
autoFocus,
onClick,
label,
dappStatus,
}: {
autoFocus?: boolean;
onClick: () => void;
label: string;
dappStatus?: DAppStatus;
}) => {
const isScamDapp = dappStatus === DAppStatus.Scam;

const { trackShortcut } = useKeyboardAnalytics();
useKeyboardShortcut({
handler: (e: KeyboardEvent) => {
Expand All @@ -395,15 +412,16 @@ export const RejectRequestButton = ({
}
},
});

return (
<Button
autoFocus={autoFocus}
color={'labelSecondary'}
color={isScamDapp ? 'red' : 'labelSecondary'}
height="44px"
width="full"
onClick={onClick}
testId="reject-request-button"
variant={'transparent'}
variant={isScamDapp ? 'flat' : 'transparent'}
tabIndex={0}
>
{label}
Expand Down
73 changes: 73 additions & 0 deletions src/entries/popup/pages/messages/DappScanStatus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { DAppStatus } from '~/core/graphql/__generated__/metadata';
import { i18n } from '~/core/languages';
import { Box, Inline, Stack, Symbol, Text } from '~/design-system';

export function ThisDappIsLikelyMalicious() {
return (
<Box
display="flex"
flexDirection="row"
alignItems="center"
paddingHorizontal="20px"
paddingVertical="16px"
gap="12px"
borderColor="separatorTertiary"
borderRadius="20px"
borderWidth="2px"
>
<Symbol
symbol="exclamationmark.octagon.fill"
size={20}
weight="heavy"
color="red"
/>
<Stack space="8px">
<Text size="14pt" weight="bold">
{i18n.t('approve_request.malicious_warning.title')}
</Text>
<Text color="labelTertiary" size="12pt" weight="semibold">
{i18n.t('approve_request.malicious_warning.message')}
</Text>
</Stack>
</Box>
);
}

const VerifiedBadge = () => (
<Symbol size={17} symbol="checkmark.seal.fill" weight="bold" color="blue" />
);
const ScamBadge = () => (
<Symbol
size={17}
symbol="network.badge.shield.half.filled"
weight="bold"
color="red"
/>
);

const getStatusBadge = (status: DAppStatus | undefined) => {
if (status === DAppStatus.Scam)
return { badge: <ScamBadge />, color: 'red' } as const;
if (status === DAppStatus.Verified)
return { badge: <VerifiedBadge />, color: 'blue' } as const;

return { badge: null, color: 'labelSecondary' } as const;
};

export function DappHostName({
hostName,
dappStatus,
}: {
hostName?: string;
dappStatus?: DAppStatus;
}) {
const { badge, color } = getStatusBadge(dappStatus);
return (
<Inline space="6px" alignVertical="center" alignHorizontal="center">
{badge}
<Text align="center" color={color} size="20pt" weight="bold">
{hostName}
</Text>
</Inline>
);
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React from 'react';
import { Address } from 'wagmi';

import { DAppStatus } from '~/core/graphql/__generated__/metadata';
import { i18n } from '~/core/languages';
import { ChainId } from '~/core/types/chains';
import { Box, Column, Columns, Row, Rows, Stack } from '~/design-system';
import { Box, Column, Columns, Stack } from '~/design-system';

import {
AcceptRequestButton,
Expand All @@ -21,6 +21,7 @@ export const RequestAccountsActions = ({
onRejectRequest,
appName,
loading = false,
dappStatus,
}: {
appName?: string;
selectedWallet: Address;
Expand All @@ -30,7 +31,9 @@ export const RequestAccountsActions = ({
onAcceptRequest: () => void;
onRejectRequest: () => void;
loading?: boolean;
dappStatus?: DAppStatus;
}) => {
const isScamDapp = dappStatus === DAppStatus.Scam;
return (
<Box padding="20px">
<Stack space="24px">
Expand All @@ -48,22 +51,28 @@ export const RequestAccountsActions = ({
/>
</Column>
</Columns>
<Rows space="8px">
<Row>
<AcceptRequestButton
autoFocus
onClick={onAcceptRequest}
label={i18n.t('approve_request.connect', { appName })}
loading={loading}
/>
</Row>
<Row>
<RejectRequestButton
onClick={onRejectRequest}
label={i18n.t('common_actions.cancel')}
/>
</Row>
</Rows>
<Stack
space="8px"
flexDirection={isScamDapp ? 'column-reverse' : 'column'}
>
<AcceptRequestButton
dappStatus={dappStatus}
onClick={onAcceptRequest}
autoFocus={!isScamDapp}
label={
isScamDapp
? i18n.t('approve_request.connect_anyway')
: i18n.t('approve_request.connect', { appName })
}
loading={loading}
/>
<RejectRequestButton
dappStatus={dappStatus}
autoFocus={isScamDapp}
onClick={onRejectRequest}
label={i18n.t('common_actions.cancel')}
/>
</Stack>
</Stack>
</Box>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,32 @@
import React from 'react';

import { DAppStatus } from '~/core/graphql/__generated__/metadata';
import { i18n } from '~/core/languages';
import { Box, Inline, Separator, Stack, Text } from '~/design-system';
import { Bleed, Box, Inline, Separator, Stack, Text } from '~/design-system';
import { TextInline } from '~/design-system/docs/components/TextInline';
import { DappIcon } from '~/entries/popup/components/DappIcon/DappIcon';

import { DappHostName, ThisDappIsLikelyMalicious } from '../DappScanStatus';

export const RequestAccountsInfo = ({
appHostName,
appName,
appLogo,
dappStatus,
}: {
appHostName?: string;
appName?: string;
appLogo?: string;
dappStatus?: DAppStatus;
}) => {
const isScamDapp = dappStatus === DAppStatus.Scam;

return (
<Box
style={{
paddingLeft: 50,
paddingRight: 50,
paddingTop: 64,
paddingBottom: 42,
height: 398,
paddingBottom: isScamDapp ? 20 : 42,
}}
paddingHorizontal="50px"
paddingTop="64px"
background="surfacePrimaryElevatedSecondary"
>
<Stack space="32px">
Expand All @@ -41,21 +46,29 @@ export const RequestAccountsInfo = ({
{i18n.t('approve_request.wallet_info_title')}
</Text>

<Text align="center" color="accent" size="20pt" weight="bold">
{appHostName}
</Text>
<DappHostName hostName={appHostName} dappStatus={dappStatus} />
</Stack>
<Inline alignHorizontal="center">
<Box style={{ width: '186px' }}>
<Separator color="separatorTertiary" />
</Box>
</Inline>

<Text align="center" color="labelTertiary" size="14pt" weight="regular">
{i18n.t('approve_request.wallet_info_description', {
appName,
})}
</Text>
<Box style={{ width: '186px' }} marginVertical="-4px">
<Separator color="separatorTertiary" />
</Box>

{isScamDapp ? (
<Bleed horizontal="30px">
<ThisDappIsLikelyMalicious />
</Bleed>
) : (
<Text
align="center"
color="labelTertiary"
size="14pt"
weight="regular"
>
{i18n.t('approve_request.wallet_info_description', {
appName,
})}
</Text>
)}
</Stack>
</Box>
);
Expand Down
Loading

0 comments on commit 93d8f23

Please sign in to comment.