Skip to content

Commit

Permalink
feat(suite): mark potentially phishy zero-value transactions
Browse files Browse the repository at this point in the history
  • Loading branch information
dahaca committed Jan 20, 2023
1 parent 1ba2f96 commit 2af10cf
Show file tree
Hide file tree
Showing 14 changed files with 256 additions and 102 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import styled from 'styled-components';
import { Tooltip, Icon } from '@trezor/components';
import { Tooltip, Icon, IconType } from '@trezor/components';

const InlineTooltip = styled(Tooltip)`
display: inline-block;
Expand All @@ -9,11 +9,12 @@ const InlineTooltip = styled(Tooltip)`

type TooltipSymbolProps = {
content: React.ReactNode;
icon?: IconType;
};

const TooltipSymbol = ({ content }: TooltipSymbolProps) => (
<InlineTooltip content={content}>
<Icon icon="QUESTION" size={16} />
const TooltipSymbol = ({ content, icon = 'QUESTION' }: TooltipSymbolProps) => (
<InlineTooltip content={content} maxWidth={250}>
<Icon icon={icon} size={16} />
</InlineTooltip>
);

Expand Down
195 changes: 110 additions & 85 deletions packages/suite/src/components/suite/modals/TransactionDetail/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import React, { useState } from 'react';
import styled from 'styled-components';
import { Translation, Modal } from '@suite-components';
import { variables, Button } from '@trezor/components';
import { HELP_CENTER_ZERO_VALUE_ATTACKS } from '@trezor/urls';
import { getIsZeroValuePhishing } from '@suite-common/suite-utils';
import { Translation, Modal, TrezorLink } from '@suite-components';
import { WalletAccountTransaction } from '@wallet-types';
import { BasicDetails } from './components/BasicDetails';
import { AdvancedDetails, TabID } from './components/AdvancedDetails';
Expand All @@ -24,9 +26,18 @@ const StyledModal = styled(Modal)`
}
`;

const Wrapper = styled.div`
width: 100%;
flex-direction: column;
const PhishingBanner = styled.div`
margin-bottom: 6px;
padding: 6px 0;
border-radius: 8px;
background: ${({ theme }) => theme.BG_RED};
color: ${({ theme }) => theme.TYPE_WHITE};
font-size: ${variables.FONT_SIZE.SMALL};
font-weight: ${variables.FONT_WEIGHT.MEDIUM};
`;

const HelpLink = styled(TrezorLink)`
color: ${({ theme }) => theme.TYPE_WHITE};
`;

const SectionActions = styled.div`
Expand Down Expand Up @@ -94,105 +105,119 @@ export const TransactionDetail = ({ tx, rbfForm, onCancel }: TransactionDetailPr
const accountKey = getAccountKey(tx.descriptor, tx.symbol, tx.deviceState);
const account = useSelector(state => selectAccountByKey(state, accountKey));
const network = account && getAccountNetwork(account);
const isZeroValuePhishing = getIsZeroValuePhishing(tx);

return (
<StyledModal
isCancelable
onCancel={onCancel}
heading={<Translation id="TR_TRANSACTION_DETAILS" />}
>
<Wrapper>
<BasicDetails
explorerUrl={blockchain.explorer.tx}
tx={tx}
network={network!}
confirmations={confirmations}
/>

<SectionActions>
{network?.features?.includes('rbf') && tx.rbfParams && (
<>
{section === 'CHANGE_FEE' && (
// Show back button and section title when bumping fee/finalizing txs
{isZeroValuePhishing && (
<PhishingBanner>
<Translation
id="TR_ZERO_PHISHING_BANNER"
values={{
a: chunks => (
<HelpLink href={HELP_CENTER_ZERO_VALUE_ATTACKS} variant="underline">
{chunks}
</HelpLink>
),
}}
/>
</PhishingBanner>
)}

<BasicDetails
explorerUrl={blockchain.explorer.tx}
tx={tx}
network={network!}
confirmations={confirmations}
/>

<SectionActions>
{network?.features?.includes('rbf') && tx.rbfParams && (
<>
{section === 'CHANGE_FEE' && (
// Show back button and section title when bumping fee/finalizing txs
<>
<Col>
<Button
variant="tertiary"
onClick={() => {
setSection('DETAILS');
setFinalize(false);
setTab(undefined);
}}
icon="ARROW_LEFT"
>
<Translation id="TR_BACK" />
</Button>
</Col>

<Middle>
<SectionTitle>
<Translation
id={finalize ? 'TR_FINALIZE_TX' : 'TR_REPLACE_TX'}
/>
</SectionTitle>
</Middle>
</>
)}

<Right>
{section === 'DETAILS' && (
// change fee and finalize tx buttons visible only in details
<>
<Col>
<Button
variant="tertiary"
onClick={() => {
setSection('CHANGE_FEE');
setTab(undefined);
}}
>
<Translation id="TR_BUMP_FEE" />
</Button>

{network?.networkType === 'bitcoin' && (
// finalize button only possible in BTC rbf
<Button
variant="tertiary"
onClick={() => {
setSection('DETAILS');
setFinalize(false);
setFinalize(true);
setSection('CHANGE_FEE');
setTab(undefined);
}}
icon="ARROW_LEFT"
>
<Translation id="TR_BACK" />
<Translation id="TR_FINALIZE_TX" />
</Button>
</Col>

<Middle>
<SectionTitle>
<Translation
id={finalize ? 'TR_FINALIZE_TX' : 'TR_REPLACE_TX'}
/>
</SectionTitle>
</Middle>
)}
</>
)}

<Right>
{section === 'DETAILS' && (
// change fee and finalize tx buttons visible only in details
<>
<Button
variant="tertiary"
onClick={() => {
setSection('CHANGE_FEE');
setTab(undefined);
}}
>
<Translation id="TR_BUMP_FEE" />
</Button>

{network?.networkType === 'bitcoin' && (
// finalize button only possible in BTC rbf
<Button
variant="tertiary"
onClick={() => {
setFinalize(true);
setSection('CHANGE_FEE');
setTab(undefined);
}}
>
<Translation id="TR_FINALIZE_TX" />
</Button>
)}
</>
)}
</Right>
</>
)}
</SectionActions>

{section === 'CHANGE_FEE' ? (
<ChangeFee
tx={tx}
finalize={finalize}
chainedTxs={chainedTxs}
showChained={() => {
setSection('DETAILS');
setTab('chained');
}}
/>
) : (
<AdvancedDetails
explorerUrl={blockchain.explorer.tx}
defaultTab={tab}
network={network!}
tx={tx}
chainedTxs={chainedTxs}
/>
</Right>
</>
)}
</Wrapper>
</SectionActions>

{section === 'CHANGE_FEE' ? (
<ChangeFee
tx={tx}
finalize={finalize}
chainedTxs={chainedTxs}
showChained={() => {
setSection('DETAILS');
setTab('chained');
}}
/>
) : (
<AdvancedDetails
explorerUrl={blockchain.explorer.tx}
defaultTab={tab}
network={network!}
tx={tx}
chainedTxs={chainedTxs}
/>
)}
</StyledModal>
);
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import styled from 'styled-components';
import { variables } from '@trezor/components';
import { getIsZeroValuePhishing } from '@suite-common/suite-utils';
import { FiatValue, Translation, MetadataLabeling, FormattedCryptoAmount } from '@suite-components';
import { ArrayElement } from '@trezor/type-utils';
import {
Expand Down Expand Up @@ -42,15 +43,21 @@ interface TokenTransferProps {
export const TokenTransfer = ({
transfer,
transaction,

...baseLayoutProps
}: TokenTransferProps) => {
const operation = getTxOperation(transfer);
const isZeroValuePhishing = getIsZeroValuePhishing(transaction);

return (
<BaseTargetLayout
{...baseLayoutProps}
addressLabel={<TokenTransferAddressLabel transfer={transfer} type={transaction.type} />}
addressLabel={
<TokenTransferAddressLabel
isZeroValuePhishing={isZeroValuePhishing}
transfer={transfer}
type={transaction.type}
/>
}
amount={
!baseLayoutProps.singleRowLayout && (
<StyledCryptoAmount
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ArrayElement } from '@trezor/type-utils';
import { Translation, AddressLabeling } from '@suite-components';
import { AccountMetadata } from '@suite-types/metadata';

const TruncatedSpan = styled.span`
const TruncatedSpan = styled.span<{ isBlurred?: boolean }>`
overflow: hidden;
text-overflow: ellipsis;
`;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
import React from 'react';
import styled from 'styled-components';
import { ArrayElement } from '@trezor/type-utils';
import { Translation, AddressLabeling } from '@suite-components';
import { WalletAccountTransaction } from '@wallet-types';
import { ArrayElement } from '@trezor/type-utils';

const BlurWrapper = styled.span<{ isBlurred: boolean }>`
filter: ${({ isBlurred }) => isBlurred && 'blur(2px)'};
`;

interface TokenTransferAddressLabelProps {
transfer: ArrayElement<WalletAccountTransaction['tokens']>;
type: WalletAccountTransaction['type'];
isZeroValuePhishing: boolean;
}

export const TokenTransferAddressLabel = ({ transfer, type }: TokenTransferAddressLabelProps) => {
export const TokenTransferAddressLabel = ({
transfer,
type,
isZeroValuePhishing,
}: TokenTransferAddressLabelProps) => {
if (type === 'self') {
return <Translation id="TR_SENT_TO_SELF" />;
}
if (type === 'sent') {
return <AddressLabeling address={transfer.to} />;
}

return <>{transfer.to}</>;
return <BlurWrapper isBlurred={isZeroValuePhishing}>{transfer.to}</BlurWrapper>;
};
Loading

0 comments on commit 2af10cf

Please sign in to comment.