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

TW-1187: Mark scam tokens #1091

Merged
merged 8 commits into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions public/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2119,6 +2119,9 @@
"minimumReceived": {
"message": "Minimum received"
},
"scam": {
"message": "Scam"
},
"inputOutputAssetsCannotBeSame": {
"message": "Input and output assets cannot be the same"
},
Expand Down Expand Up @@ -2621,6 +2624,15 @@
"readMore": {
"message": "Read more by the "
},
"deleteScamTokenConfirmTitle": {
"message": "Be cautious!"
},
"deleteScamTokenConfirmDescription": {
"message": "This token may be a scam. We strongly advise removing it from your token list to safeguard against the risk of losing funds."
},
"scamTokenAlert": {
"message": "This token may be a SCAM!"
},
"deleteTokenConfirm": {
"message": "Are you sure you want to delete this token?"
},
Expand Down
10 changes: 5 additions & 5 deletions src/app/WithDataLoading.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { FC, useEffect } from 'react';

import { useDispatch } from 'react-redux';

import { dispatch } from 'app/store';
import { loadTokensScamlistActions } from 'app/store/assets/actions';
import { loadSwapDexesAction, loadSwapTokensAction } from 'app/store/swap/actions';

import { useAdvertisingLoading } from './hooks/use-advertising.hook';
Expand All @@ -19,6 +19,8 @@ import { useUserIdSync } from './hooks/use-user-id-sync';
export const WithDataLoading: FC<PropsWithChildren> = ({ children }) => {
useAssetsMigrations();

useEffect(() => void dispatch(loadTokensScamlistActions.submit()), []);

useAssetsLoading();
useMetadataLoading();
useMetadataRefresh();
Expand All @@ -29,12 +31,10 @@ export const WithDataLoading: FC<PropsWithChildren> = ({ children }) => {
useAdvertisingLoading();
useTokensApyLoading();

const dispatch = useDispatch();

useEffect(() => {
dispatch(loadSwapDexesAction.submit());
dispatch(loadSwapTokensAction.submit());
}, [dispatch]);
}, []);

useStorageAnalytics();
useUserIdSync();
Expand Down
9 changes: 9 additions & 0 deletions src/app/pages/Home/Home.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import React, { memo, useLayoutEffect } from 'react';

import { isDefined } from '@rnw-community/shared';

import { useAppEnv } from 'app/env';
import PageLayout from 'app/layouts/PageLayout';
import { useMainnetTokensScamlistSelector } from 'app/store/assets/selectors';
import { setAnotherSelector, setTestID } from 'lib/analytics';
import { TEZ_TOKEN_SLUG } from 'lib/assets';
import { useAssetMetadata, getAssetSymbol } from 'lib/metadata';
Expand All @@ -15,6 +18,7 @@ import { ActionButtonsBar } from './ActionButtonsBar';
import { ContentSection } from './ContentSection';
import EditableTitle from './OtherComponents/EditableTitle';
import MainBanner from './OtherComponents/MainBanner';
import { ScamTokenAlert } from './OtherComponents/ScamTokenAlert';
import { TokenPageSelectors } from './OtherComponents/TokenPage.selectors';

type Props = {
Expand All @@ -27,6 +31,9 @@ const Home = memo<Props>(({ assetSlug }) => {
const { publicKeyHash } = useAccount();
const { search } = useLocation();

const mainnetTokensScamSlugsRecord = useMainnetTokensScamlistSelector();
const showScamTokenAlert = isDefined(assetSlug) && mainnetTokensScamSlugsRecord[assetSlug];

const assetMetadata = useAssetMetadata(assetSlug || TEZ_TOKEN_SLUG);
const assetSymbol = getAssetSymbol(assetMetadata);

Expand Down Expand Up @@ -63,6 +70,8 @@ const Home = memo<Props>(({ assetSlug }) => {
</div>
)}

{showScamTokenAlert && <ScamTokenAlert />}

<div className="flex flex-col items-center mb-6">
<MainBanner accountPkh={publicKeyHash} assetSlug={assetSlug} />

Expand Down
1 change: 1 addition & 0 deletions src/app/pages/Home/OtherComponents/Assets.selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export enum AssetsSelectors {
dropdownHideZeroBalancesCheckbox = 'Assets (Manage Dropdown)/Hide Zero Balances Checkbox',
assetItemButton = 'Assets/Asset Item Button',
assetItemApyButton = 'Assets/Asset Item Apy Button',
assetItemScamButton = 'Assets/Asset Item Scam Button',
assetItemCryptoBalanceButton = 'Assets/Asset Item Crypto Balance Button',
assetItemFiatBalanceButton = 'Assets/Asset Item Fiat Balance Button',
assetItemDelegateButton = 'Assets/Asset Item Delegate Button',
Expand Down
16 changes: 16 additions & 0 deletions src/app/pages/Home/OtherComponents/ScamTokenAlert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React, { memo } from 'react';

import { T } from 'lib/i18n';
import { AlertTriangleIcon } from 'lib/icons';

export const ScamTokenAlert = memo(() => (
<div
className="w-full max-w-sm mx-auto py-3 px-4 rounded-md border border-red-700 mb-4 flex flex-row items-center"
style={{ backgroundColor: '#FFEFEF' }}
>
<AlertTriangleIcon className="h-4 w-4 stroke-current text-red-700" />
<p className="ml-2 text-red-700 font-normal text-xs">
<T id="scamTokenAlert" />
</p>
</div>
));
49 changes: 46 additions & 3 deletions src/app/pages/Home/OtherComponents/Tokens/Tokens.module.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
.apyTag {
background: #4299e1;
.tagBase {
border-radius: 4px;
font-family: Inter, Helvetica, Arial, Verdana, Tahoma, sans-serif;
font-style: normal;
Expand All @@ -9,10 +8,54 @@
color: #ffffff;
}

.apyTag:hover {
.delegateTag {
background: #4299e1;
}

.delegateTag:hover {
background-color: #68ADE7;
}

.kusdTag {
background: #3EBD93;
}

.kusdTag:hover {
background-color: #65CAA9;
}

.tzbtcTag {
background: #1373E4;
}

.tzbtcTag:hover {
background-color: #428FE9;
}

.usdtTag {
background: #009393;
}

.usdtTag:hover {
background-color: #52AF95;
}

.youvesTag {
background: #143A3A;
}

.youvesTag:hover {
background-color: #4F6B6B;
}

.scamTag {
background: #C53030;
}

.scamTag:hover {
background-color: #D15A59;
}

.tokenInfoWidth {
max-width: 19rem;
}
Expand Down
7 changes: 5 additions & 2 deletions src/app/pages/Home/OtherComponents/Tokens/Tokens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { useLoadPartnersPromo } from 'app/hooks/use-load-partners-promo';
import { useTokensListingLogic } from 'app/hooks/use-tokens-listing-logic';
import { ReactComponent as EditingIcon } from 'app/icons/editing.svg';
import { ReactComponent as SearchIcon } from 'app/icons/search.svg';
import { useAreAssetsLoading } from 'app/store/assets/selectors';
import { useAreAssetsLoading, useMainnetTokensScamlistSelector } from 'app/store/assets/selectors';
import { ButtonForManageDropdown } from 'app/templates/ManageDropdown';
import { PartnersPromotion, PartnersPromotionVariant } from 'app/templates/partners-promotion';
import SearchAssetField from 'app/templates/SearchAssetField';
Expand Down Expand Up @@ -56,6 +56,8 @@ export const TokensTab = memo(() => {
[chainId]
);

const mainnetTokensScamSlugsRecord = useMainnetTokensScamlistSelector();

const { filteredAssets, searchValue, setSearchValue } = useTokensListingLogic(
slugs,
isZeroBalancesHidden,
Expand All @@ -76,6 +78,7 @@ export const TokensTab = memo(() => {
key={assetSlug}
publicKeyHash={publicKeyHash}
assetSlug={assetSlug}
scam={mainnetTokensScamSlugsRecord[assetSlug]}
lendihop marked this conversation as resolved.
Show resolved Hide resolved
active={activeAssetSlug ? assetSlug === activeAssetSlug : false}
/>
));
Expand All @@ -96,7 +99,7 @@ export const TokensTab = memo(() => {
}

return tokensJsx;
}, [filteredAssets, activeAssetSlug, publicKeyHash]);
}, [filteredAssets, activeAssetSlug, publicKeyHash, mainnetTokensScamSlugsRecord]);

useLoadPartnersPromo(OptimalPromoVariantEnum.Token);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ interface Props {
publicKeyHash: string;
assetSlug: string;
active: boolean;
scam?: boolean;
}

export const ListItem = memo<Props>(({ publicKeyHash, assetSlug, active }) => {
export const ListItem = memo<Props>(({ publicKeyHash, assetSlug, active, scam }) => {
const { value: balance = ZERO, assetMetadata: metadata } = useBalance(assetSlug, publicKeyHash);

const apyInfo = useTokenApyInfo(assetSlug);
Expand Down Expand Up @@ -57,7 +58,7 @@ export const ListItem = memo<Props>(({ publicKeyHash, assetSlug, active }) => {
<div className="flex justify-between w-full mb-1">
<div className="flex items-center flex-initial">
<div className={styles['tokenSymbol']}>{assetSymbol}</div>
<TokenTag assetSlug={assetSlug} assetSymbol={assetSymbol} apyInfo={apyInfo} />
<TokenTag assetSlug={assetSlug} assetSymbol={assetSymbol} apyInfo={apyInfo} scam={scam} />
</div>
<CryptoBalance
value={balance}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React, { FC, useState, useMemo } from 'react';
import React, { FC, useMemo } from 'react';

import BigNumber from 'bignumber.js';
import classNames from 'clsx';

import { Button } from 'app/atoms/Button';
import type { TokenApyInfo } from 'app/hooks/use-token-apy.hook';
import { KNOWN_TOKENS_SLUGS, TOKENS_BRAND_COLORS } from 'lib/assets/known-tokens';
import { KNOWN_TOKENS_SLUGS } from 'lib/assets/known-tokens';
import { isTruthy, openLink } from 'lib/utils';

import { AssetsSelectors } from '../../../Assets.selectors';
Expand All @@ -20,11 +20,17 @@ interface Props {
const APR = 'APR';
const APY = 'APY';
const YOUVES_TOKENS_WITH_APR = [KNOWN_TOKENS_SLUGS.UUSD, KNOWN_TOKENS_SLUGS.UBTC, KNOWN_TOKENS_SLUGS.YOU];
const TAGS_CLASSNAME_RECORD: Record<string, string> = {
[KNOWN_TOKENS_SLUGS.KUSD]: modStyles.kusdTag,
[KNOWN_TOKENS_SLUGS.TZBTC]: modStyles.tzbtcTag,
[KNOWN_TOKENS_SLUGS.USDT]: modStyles.usdtTag,
[KNOWN_TOKENS_SLUGS.UUSD]: modStyles.youvesTag,
[KNOWN_TOKENS_SLUGS.UBTC]: modStyles.youvesTag,
[KNOWN_TOKENS_SLUGS.YOU]: modStyles.youvesTag
};

export const TokenApyTag: FC<Props> = ({ slug, symbol, apyInfo }) => {
const [hovered, setHovered] = useState(false);

const colors = useMemo(() => TOKENS_BRAND_COLORS[slug], [slug]);
const tokenClassName = useMemo(() => TAGS_CLASSNAME_RECORD[slug], [slug]);

const label = useMemo(() => (YOUVES_TOKENS_WITH_APR.includes(slug) ? APR : APY), [slug]);

Expand All @@ -41,12 +47,9 @@ export const TokenApyTag: FC<Props> = ({ slug, symbol, apyInfo }) => {
e.stopPropagation();
openLink(link);
}}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
testID={AssetsSelectors.assetItemApyButton}
testIDProperties={{ slug, symbol, apyRate: rate }}
className={classNames('ml-2 px-2 py-1', modStyles['apyTag'])}
style={{ backgroundColor: hovered ? colors.bgHover : colors.bg }}
className={classNames('ml-2 px-2 py-1', modStyles.tagBase, tokenClassName)}
>
{label}: {displayRate}%
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const DelegateTezosTag = memo(() => {
() => (
<Button
onClick={handleTagClick}
className={classNames('inline-flex items-center px-1.5 ml-2 py-1', modStyles['apyTag'])}
className={classNames('inline-flex items-center px-1.5 ml-2 py-1', modStyles.tagBase, modStyles.delegateTag)}
testID={AssetsSelectors.assetItemApyButton}
>
APY: 5.6%
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React, { memo, useCallback } from 'react';

import clsx from 'clsx';

import { Button } from 'app/atoms/Button';
import { AssetsSelectors } from 'app/pages/Home/OtherComponents/Assets.selectors';
import { dispatch } from 'app/store';
import { setTokenStatusAction } from 'app/store/assets/actions';
import { t, T } from 'lib/i18n';
import { useAccount, useChainId } from 'lib/temple/front';
import { useConfirm } from 'lib/ui/dialog';

import modStyles from '../../Tokens.module.css';

interface Props {
assetSlug: string;
}

export const ScamTag = memo<Props>(({ assetSlug }) => {
const chainId = useChainId(true)!;
const { publicKeyHash } = useAccount();

const confirm = useConfirm();

const removeToken = useCallback(
async (slug: string) => {
try {
const confirmed = await confirm({
title: t('deleteScamTokenConfirmTitle'),
titleClassName: 'font-bold',
description: t('deleteScamTokenConfirmDescription'),
comfirmButtonText: t('delete')
});

if (confirmed)
dispatch(
setTokenStatusAction({
account: publicKeyHash,
chainId,
slug,
status: 'removed'
})
);
} catch (err: any) {
console.error(err);
alert(err.message);
}
},
[chainId, publicKeyHash, confirm]
);

const handleClick = useCallback(
(e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
e.stopPropagation();
removeToken(assetSlug);
},
[assetSlug, removeToken]
);

return (
<Button
onClick={handleClick}
className={clsx('uppercase ml-2 px-2 py-1', modStyles.tagBase, modStyles.scamTag)}
testID={AssetsSelectors.assetItemScamButton}
>
<T id="scam" />
</Button>
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,20 @@ import { isTruthy } from 'lib/utils';

import { TokenApyTag } from './ApyTag';
import { DelegateTezosTag } from './DelegateTag';
import { ScamTag } from './ScamTag';

interface TokenTagProps {
assetSlug: string;
assetSymbol: string;
apyInfo?: TokenApyInfo;
scam?: boolean;
}

export const TokenTag: React.FC<TokenTagProps> = ({ assetSlug, assetSymbol, apyInfo }) => {
export const TokenTag: React.FC<TokenTagProps> = ({ assetSlug, assetSymbol, apyInfo, scam }) => {
if (isTezAsset(assetSlug)) return <DelegateTezosTag />;

if (isTruthy(scam)) return <ScamTag assetSlug={assetSlug} />;

if (isTruthy(apyInfo)) return <TokenApyTag slug={assetSlug} symbol={assetSymbol} apyInfo={apyInfo} />;

return null;
Expand Down
Loading
Loading