mirror of
https://github.com/luxfi/explore.git
synced 2026-07-27 05:54:15 +00:00
UI/UX: Scam tokens tooltip (#3018)
* UI/UX: Scam tokens tooltip * review fixes
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
|
||||
<path fill="#E53E3E" d="M10 0c5.523 0 10 4.477 10 10s-4.477 10-10 10S0 15.523 0 10 4.477 0 10 0m0 1.6a8.4 8.4 0 1 0 0 16.8 8.4 8.4 0 0 0 0-16.8m6.895 9.41a.501.501 0 0 1 .21.98l-.154.03-1.547 2.68a.2.2 0 0 1-.345 0l-1.222-2.115-.152.022-1.293 3.02a.2.2 0 0 1-.359.02l-1.556-2.774q-.362.008-.725.003l-1.554 2.77a.2.2 0 0 1-.358-.019l-1.28-2.985-.184-.026L5.173 14.7a.2.2 0 0 1-.346 0l-1.516-2.626-.416-.085-.096-.031A.501.501 0 0 1 3.005 11l.1.01.992.207c4.824.957 7.78.867 12.798-.206M3.88 5.368a.43.43 0 0 1 .266.038L9.064 7.88a.426.426 0 0 1-.04.777l-1.246.472A2.42 2.42 0 0 1 4.83 8.094L3.59 6.002a.43.43 0 0 1 .055-.506.43.43 0 0 1 .235-.129m12.241 0a.424.424 0 0 1 .29.634l-1.24 2.093a2.43 2.43 0 0 1-2.951 1.035l-1.246-.472h.003a.428.428 0 0 1-.217-.611.43.43 0 0 1 .177-.166l4.919-2.474a.42.42 0 0 1 .265-.039"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 902 B |
@@ -27,6 +27,7 @@ export const token1: SearchResultToken = {
|
||||
exchange_rate: null,
|
||||
is_verified_via_admin_panel: true,
|
||||
is_smart_contract_verified: true,
|
||||
reputation: 'ok',
|
||||
};
|
||||
|
||||
export const token2: SearchResultToken = {
|
||||
@@ -42,6 +43,7 @@ export const token2: SearchResultToken = {
|
||||
exchange_rate: '1.11',
|
||||
is_verified_via_admin_panel: false,
|
||||
is_smart_contract_verified: false,
|
||||
reputation: 'ok',
|
||||
};
|
||||
|
||||
export const block1: SearchResultBlock = {
|
||||
|
||||
@@ -11,6 +11,7 @@ export const tokenInfo: TokenInfo = {
|
||||
type: 'ERC-20' as const,
|
||||
total_supply: '1235',
|
||||
icon_url: 'http://localhost:3000/token-icon.png',
|
||||
reputation: 'ok',
|
||||
};
|
||||
|
||||
export const tokenCounters: TokenCounters = {
|
||||
@@ -29,6 +30,7 @@ export const tokenInfoERC20a: TokenInfo<'ERC-20'> = {
|
||||
total_supply: '369000000000000000000000000',
|
||||
type: 'ERC-20' as const,
|
||||
icon_url: 'http://localhost:3000/token-icon.png',
|
||||
reputation: 'ok',
|
||||
};
|
||||
|
||||
export const tokenInfoERC20b: TokenInfo<'ERC-20'> = {
|
||||
@@ -42,6 +44,7 @@ export const tokenInfoERC20b: TokenInfo<'ERC-20'> = {
|
||||
total_supply: '900000000000000000000000000',
|
||||
type: 'ERC-20' as const,
|
||||
icon_url: null,
|
||||
reputation: 'ok',
|
||||
};
|
||||
|
||||
export const tokenInfoERC20c: TokenInfo<'ERC-20'> = {
|
||||
@@ -55,6 +58,7 @@ export const tokenInfoERC20c: TokenInfo<'ERC-20'> = {
|
||||
total_supply: '1000000000000000000000000',
|
||||
type: 'ERC-20' as const,
|
||||
icon_url: null,
|
||||
reputation: 'ok',
|
||||
};
|
||||
|
||||
export const tokenInfoERC20d: TokenInfo<'ERC-20'> = {
|
||||
@@ -68,6 +72,7 @@ export const tokenInfoERC20d: TokenInfo<'ERC-20'> = {
|
||||
total_supply: '2100000000000000000000000000',
|
||||
type: 'ERC-20' as const,
|
||||
icon_url: null,
|
||||
reputation: 'ok',
|
||||
};
|
||||
|
||||
export const tokenInfoERC20LongSymbol: TokenInfo<'ERC-20'> = {
|
||||
@@ -81,6 +86,7 @@ export const tokenInfoERC20LongSymbol: TokenInfo<'ERC-20'> = {
|
||||
total_supply: '2100000000000000000000000000',
|
||||
type: 'ERC-20' as const,
|
||||
icon_url: null,
|
||||
reputation: 'ok',
|
||||
};
|
||||
|
||||
export const tokenInfoERC721a: TokenInfo<'ERC-721'> = {
|
||||
@@ -94,6 +100,7 @@ export const tokenInfoERC721a: TokenInfo<'ERC-721'> = {
|
||||
total_supply: '105',
|
||||
type: 'ERC-721' as const,
|
||||
icon_url: null,
|
||||
reputation: 'ok',
|
||||
};
|
||||
|
||||
export const tokenInfoERC721b: TokenInfo<'ERC-721'> = {
|
||||
@@ -107,6 +114,7 @@ export const tokenInfoERC721b: TokenInfo<'ERC-721'> = {
|
||||
total_supply: null,
|
||||
type: 'ERC-721' as const,
|
||||
icon_url: null,
|
||||
reputation: 'ok',
|
||||
};
|
||||
|
||||
export const tokenInfoERC721c: TokenInfo<'ERC-721'> = {
|
||||
@@ -120,6 +128,7 @@ export const tokenInfoERC721c: TokenInfo<'ERC-721'> = {
|
||||
total_supply: null,
|
||||
type: 'ERC-721' as const,
|
||||
icon_url: null,
|
||||
reputation: 'ok',
|
||||
};
|
||||
|
||||
export const tokenInfoERC721LongSymbol: TokenInfo<'ERC-721'> = {
|
||||
@@ -133,6 +142,7 @@ export const tokenInfoERC721LongSymbol: TokenInfo<'ERC-721'> = {
|
||||
total_supply: null,
|
||||
type: 'ERC-721' as const,
|
||||
icon_url: null,
|
||||
reputation: 'ok',
|
||||
};
|
||||
|
||||
export const tokenInfoERC1155a: TokenInfo<'ERC-1155'> = {
|
||||
@@ -146,6 +156,7 @@ export const tokenInfoERC1155a: TokenInfo<'ERC-1155'> = {
|
||||
total_supply: '482',
|
||||
type: 'ERC-1155' as const,
|
||||
icon_url: null,
|
||||
reputation: 'ok',
|
||||
};
|
||||
|
||||
export const tokenInfoERC1155b: TokenInfo<'ERC-1155'> = {
|
||||
@@ -159,6 +170,7 @@ export const tokenInfoERC1155b: TokenInfo<'ERC-1155'> = {
|
||||
total_supply: '4943',
|
||||
type: 'ERC-1155' as const,
|
||||
icon_url: null,
|
||||
reputation: 'ok',
|
||||
};
|
||||
|
||||
export const tokenInfoERC1155WithoutName: TokenInfo<'ERC-1155'> = {
|
||||
@@ -172,6 +184,7 @@ export const tokenInfoERC1155WithoutName: TokenInfo<'ERC-1155'> = {
|
||||
total_supply: '482',
|
||||
type: 'ERC-1155' as const,
|
||||
icon_url: null,
|
||||
reputation: 'ok',
|
||||
};
|
||||
|
||||
export const tokenInfoERC404: TokenInfo<'ERC-404'> = {
|
||||
@@ -185,6 +198,7 @@ export const tokenInfoERC404: TokenInfo<'ERC-404'> = {
|
||||
symbol: 'O404',
|
||||
total_supply: '6482275000000000000',
|
||||
type: 'ERC-404' as const,
|
||||
reputation: 'ok',
|
||||
};
|
||||
|
||||
export const bridgedTokenA: TokenInfo<'ERC-20'> = {
|
||||
|
||||
@@ -37,6 +37,7 @@ export const erc20: TokenTransfer = {
|
||||
type: 'ERC-20',
|
||||
total_supply: '0',
|
||||
icon_url: null,
|
||||
reputation: 'ok',
|
||||
},
|
||||
total: {
|
||||
decimals: '18',
|
||||
@@ -85,6 +86,7 @@ export const erc721: TokenTransfer = {
|
||||
type: 'ERC-721',
|
||||
total_supply: '0',
|
||||
icon_url: null,
|
||||
reputation: 'ok',
|
||||
},
|
||||
total: {
|
||||
token_id: '875879856',
|
||||
@@ -133,6 +135,7 @@ export const erc1155A: TokenTransfer = {
|
||||
type: 'ERC-1155',
|
||||
total_supply: '0',
|
||||
icon_url: null,
|
||||
reputation: 'ok',
|
||||
},
|
||||
total: {
|
||||
token_id: '123',
|
||||
@@ -212,6 +215,7 @@ export const erc404A: TokenTransfer = {
|
||||
type: 'ERC-404',
|
||||
total_supply: '0',
|
||||
icon_url: null,
|
||||
reputation: 'ok',
|
||||
},
|
||||
total: {
|
||||
value: '42000000000000000000000000',
|
||||
|
||||
@@ -35,6 +35,7 @@ export const mintToken: TxStateChange = {
|
||||
total_supply: '10645',
|
||||
type: 'ERC-721',
|
||||
icon_url: null,
|
||||
reputation: 'ok',
|
||||
},
|
||||
type: 'token' as const,
|
||||
};
|
||||
@@ -74,6 +75,7 @@ export const receiveMintedToken: TxStateChange = {
|
||||
total_supply: '10645',
|
||||
type: 'ERC-721',
|
||||
icon_url: null,
|
||||
reputation: 'ok',
|
||||
},
|
||||
type: 'token' as const,
|
||||
};
|
||||
@@ -105,6 +107,7 @@ export const transfer1155Token: TxStateChange = {
|
||||
symbol: null,
|
||||
total_supply: null,
|
||||
type: 'ERC-1155',
|
||||
reputation: 'ok',
|
||||
},
|
||||
token_id: '1',
|
||||
type: 'token' as const,
|
||||
@@ -177,6 +180,7 @@ export const sendERC20Token: TxStateChange = {
|
||||
symbol: 'USDT',
|
||||
total_supply: '39030615894320966',
|
||||
type: 'ERC-20' as const,
|
||||
reputation: 'ok',
|
||||
},
|
||||
type: 'token' as const,
|
||||
};
|
||||
|
||||
@@ -311,6 +311,7 @@ export const stabilityTx: Transaction = {
|
||||
symbol: 'GAS',
|
||||
total_supply: '10000000000000000000000000',
|
||||
type: 'ERC-20',
|
||||
reputation: 'ok',
|
||||
},
|
||||
total_fee: '68762500000000',
|
||||
validator_address: {
|
||||
@@ -342,6 +343,7 @@ export const celoTxn: Transaction = {
|
||||
symbol: 'cUSD',
|
||||
total_supply: '7145754483836626799435133',
|
||||
type: 'ERC-20',
|
||||
reputation: 'ok',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -22,6 +22,7 @@ export const txInterpretation: TxInterpretationResponse = {
|
||||
total_supply: '210000000000000000000000000',
|
||||
exchange_rate: null,
|
||||
circulating_market_cap: null,
|
||||
reputation: 'ok',
|
||||
},
|
||||
},
|
||||
to_address: {
|
||||
|
||||
Vendored
+1
@@ -144,6 +144,7 @@
|
||||
| "rocket_xl"
|
||||
| "rocket"
|
||||
| "RPC"
|
||||
| "scam"
|
||||
| "scope"
|
||||
| "score/score-not-ok"
|
||||
| "score/score-ok"
|
||||
|
||||
@@ -15,6 +15,7 @@ export const SEARCH_RESULT_ITEM: SearchResultItem = {
|
||||
exchange_rate: '1.11',
|
||||
total_supply: null,
|
||||
token_type: 'ERC-20',
|
||||
reputation: 'ok',
|
||||
};
|
||||
|
||||
export const SEARCH_RESULT_NEXT_PAGE_PARAMS: SearchResult['next_page_params'] = {
|
||||
|
||||
@@ -27,6 +27,7 @@ export const TOKEN_INFO_ERC_20: TokenInfo<'ERC-20'> = {
|
||||
total_supply: '60000000000000000000000',
|
||||
type: 'ERC-20',
|
||||
icon_url: null,
|
||||
reputation: 'ok',
|
||||
};
|
||||
|
||||
export const TOKEN_INFO_ERC_721: TokenInfo<'ERC-721'> = {
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
import type * as bens from '@blockscout/bens-types';
|
||||
import type * as tac from '@blockscout/tac-operation-lifecycle-types';
|
||||
import type { TokenType } from 'types/api/token';
|
||||
import type { TokenReputation, TokenType } from 'types/api/token';
|
||||
|
||||
import type { AddressMetadataTagApi } from './addressMetadata';
|
||||
|
||||
@@ -36,6 +36,7 @@ export interface SearchResultToken {
|
||||
is_smart_contract_verified: boolean;
|
||||
filecoin_robust_address?: string | null;
|
||||
certified?: boolean;
|
||||
reputation: TokenReputation | null;
|
||||
}
|
||||
|
||||
type SearchResultEnsInfo = {
|
||||
|
||||
@@ -4,6 +4,8 @@ import type { AddressParam } from './addressParams';
|
||||
export type NFTTokenType = 'ERC-721' | 'ERC-1155' | 'ERC-404';
|
||||
export type TokenType = 'ERC-20' | NFTTokenType;
|
||||
|
||||
export type TokenReputation = 'ok' | 'scam';
|
||||
|
||||
export interface TokenInfo<T extends TokenType = TokenType> {
|
||||
address_hash: string;
|
||||
type: T;
|
||||
@@ -15,6 +17,7 @@ export interface TokenInfo<T extends TokenType = TokenType> {
|
||||
total_supply: string | null;
|
||||
icon_url: string | null;
|
||||
circulating_market_cap: string | null;
|
||||
reputation: TokenReputation | null;
|
||||
// bridged token fields
|
||||
is_bridged?: boolean | null;
|
||||
bridge_type?: string | null;
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
|
||||
import * as metadataMock from 'mocks/metadata/address';
|
||||
import { test, expect } from 'playwright/lib';
|
||||
|
||||
import AddressAlerts from './AddressAlerts';
|
||||
|
||||
test('base view', async({ render }) => {
|
||||
const component = await render(<AddressAlerts tags={ [ metadataMock.noteTag, metadataMock.noteTag2 ] }/>);
|
||||
|
||||
await expect(component).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test('with scam token', async({ render }) => {
|
||||
const component = await render(<AddressAlerts tags={ [ metadataMock.noteTag, metadataMock.noteTag2 ] } isScamToken={ true }/>);
|
||||
|
||||
await expect(component).toHaveScreenshot();
|
||||
});
|
||||
@@ -8,19 +8,25 @@ import { Alert } from 'toolkit/chakra/alert';
|
||||
|
||||
interface Props {
|
||||
tags: Array<AddressMetadataTagFormatted> | undefined;
|
||||
isScamToken?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const AddressMetadataAlert = ({ tags, className }: Props) => {
|
||||
const AddressAlerts = ({ tags, isScamToken, className }: Props) => {
|
||||
const noteTags = tags?.filter(({ tagType }) => tagType === 'note').filter(({ meta }) => meta?.data);
|
||||
|
||||
if (!noteTags?.length) {
|
||||
if (!noteTags?.length && !isScamToken) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex flexDir="column" gap={ 3 } className={ className }>
|
||||
{ noteTags.map((noteTag) => (
|
||||
{ isScamToken && (
|
||||
<Alert status="error">
|
||||
This token has been flagged as a potential scam.
|
||||
</Alert>
|
||||
) }
|
||||
{ noteTags?.map((noteTag) => (
|
||||
<Alert
|
||||
key={ noteTag.name }
|
||||
status={ noteTag.meta?.alertStatus as AlertProps['status'] ?? 'error' }
|
||||
@@ -44,4 +50,4 @@ const AddressMetadataAlert = ({ tags, className }: Props) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(chakra(AddressMetadataAlert));
|
||||
export default React.memo(chakra(AddressAlerts));
|
||||
@@ -1,12 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import * as metadataMock from 'mocks/metadata/address';
|
||||
import { test, expect } from 'playwright/lib';
|
||||
|
||||
import AddressMetadataAlert from './AddressMetadataAlert';
|
||||
|
||||
test('base view', async({ render }) => {
|
||||
const component = await render(<AddressMetadataAlert tags={ [ metadataMock.noteTag, metadataMock.noteTag2 ] }/>);
|
||||
|
||||
await expect(component).toHaveScreenshot();
|
||||
});
|
||||
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
@@ -126,6 +126,7 @@ const CrossChainTxsTableItem = ({ item, isLoading, animation, currencySymbol, cu
|
||||
icon_url: '',
|
||||
name: '',
|
||||
symbol: 'TBD',
|
||||
reputation: null,
|
||||
}}
|
||||
noCopy
|
||||
onlySymbol
|
||||
|
||||
@@ -41,8 +41,8 @@ import AddressUserOps from 'ui/address/AddressUserOps';
|
||||
import AddressWithdrawals from 'ui/address/AddressWithdrawals';
|
||||
import AddressClusters from 'ui/address/clusters/AddressClusters';
|
||||
import { CONTRACT_TAB_IDS } from 'ui/address/contract/utils';
|
||||
import AddressAlerts from 'ui/address/details/AddressAlerts';
|
||||
import AddressFavoriteButton from 'ui/address/details/AddressFavoriteButton';
|
||||
import AddressMetadataAlert from 'ui/address/details/AddressMetadataAlert';
|
||||
import AddressQrCode from 'ui/address/details/AddressQrCode';
|
||||
import AddressEnsDomains from 'ui/address/ensDomains/AddressEnsDomains';
|
||||
import SolidityscanReport from 'ui/address/SolidityscanReport';
|
||||
@@ -468,7 +468,7 @@ const AddressPageContent = () => {
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
{ !addressMetadataQuery.isPending &&
|
||||
<AddressMetadataAlert tags={ addressMetadataQuery.data?.addresses?.[hash.toLowerCase()]?.tags } mt="-4px" mb={ 6 }/> }
|
||||
<AddressAlerts tags={ addressMetadataQuery.data?.addresses?.[hash.toLowerCase()]?.tags } mt="-4px" mb={ 6 }/> }
|
||||
{ config.features.metasuites.isEnabled && <Box display="none" id="meta-suites__address" data-ready={ !isLoading }/> }
|
||||
<RoutedTabs tabs={ tabs } isLoading={ isTabsLoading }/>
|
||||
</>
|
||||
|
||||
@@ -92,6 +92,22 @@ test('bridged token', async({ render, page, createSocket, mockApiResponse, mockA
|
||||
});
|
||||
});
|
||||
|
||||
test('scam token', async({ render, page, createSocket, mockApiResponse, mockEnvs }) => {
|
||||
await mockEnvs([
|
||||
[ 'NEXT_PUBLIC_VIEWS_TOKEN_SCAM_TOGGLE_ENABLED', 'true' ],
|
||||
]);
|
||||
await mockApiResponse('general:token', { ...tokenInfo, reputation: 'scam' }, { pathParams: { hash } });
|
||||
const component = await render(<Token/>, { hooksConfig }, { withSocket: true });
|
||||
|
||||
const socket = await createSocket();
|
||||
await socketServer.joinChannel(socket, `tokens:${ hash }`);
|
||||
|
||||
await expect(component).toHaveScreenshot({
|
||||
mask: [ page.locator(pwConfig.adsBannerSelector) ],
|
||||
maskColor: pwConfig.maskColor,
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('mobile', () => {
|
||||
test.use({ viewport: devices['iPhone 13 Pro'].viewport });
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
@@ -29,6 +29,7 @@ const PoolInfo = ({ data, isPlaceholderData }: Props) => {
|
||||
name: data.base_token_symbol,
|
||||
symbol: data.base_token_symbol,
|
||||
icon_url: data.base_token_icon_url,
|
||||
reputation: null,
|
||||
}}
|
||||
isLoading={ isPlaceholderData }
|
||||
/>
|
||||
@@ -48,6 +49,7 @@ const PoolInfo = ({ data, isPlaceholderData }: Props) => {
|
||||
name: data.quote_token_symbol,
|
||||
symbol: data.quote_token_symbol,
|
||||
icon_url: data.quote_token_icon_url,
|
||||
reputation: null,
|
||||
}}
|
||||
isLoading={ isPlaceholderData }
|
||||
/>
|
||||
|
||||
@@ -83,6 +83,7 @@ const SearchResultListItem = ({ data, searchTerm, isLoading, addressFormat }: Pr
|
||||
</Link>
|
||||
{ data.certified && <ContractCertifiedLabel iconSize={ 4 } boxSize={ 4 } ml={ 1 }/> }
|
||||
{ data.is_verified_via_admin_panel && !data.certified && <IconSvg name="certified" boxSize={ 4 } ml={ 1 } color="green.500"/> }
|
||||
{ data.reputation && <TokenEntity.Reputation token={{ ...data, type: data.token_type }}/> }
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading, addressFormat }: P
|
||||
</Link>
|
||||
{ data.certified && <ContractCertifiedLabel iconSize={ 4 } boxSize={ 4 } ml={ 1 }/> }
|
||||
{ data.is_verified_via_admin_panel && !data.certified && <IconSvg name="certified" boxSize={ 4 } ml={ 1 } color="green.500"/> }
|
||||
{ data.reputation && <TokenEntity.Reputation token={{ ...data, type: data.token_type }}/> }
|
||||
</Flex>
|
||||
</TableCell>
|
||||
<TableCell verticalAlign="middle">
|
||||
|
||||
@@ -23,6 +23,7 @@ const DefaultView = () => {
|
||||
exchange_rate: null,
|
||||
total_supply: null,
|
||||
icon_url: 'https://example.com/logo.png',
|
||||
reputation: 'ok',
|
||||
};
|
||||
|
||||
const contentAfter = (
|
||||
|
||||
@@ -23,6 +23,7 @@ const LongNameAndManyTags = () => {
|
||||
symbol: 'BatcoiRing ding ding daa baa Baa aramba baa bom baa barooumba Wh-wha-what's going on-on? Ding, ding This is the Crazy Frog Ding, ding Bem, bem! Ring ding ding ding ding ding Ring ding ding ding bem bem bem Ring ding ding ding ding ding Ring ding ding ding',
|
||||
total_supply: '13747',
|
||||
type: 'ERC-721',
|
||||
reputation: 'ok',
|
||||
};
|
||||
|
||||
const contentAfter = (
|
||||
|
||||
@@ -50,6 +50,7 @@ const Icon = (props: IconProps) => {
|
||||
address_hash: props.pool.base_token_address,
|
||||
name: '',
|
||||
type: 'ERC-20',
|
||||
reputation: null,
|
||||
}}
|
||||
isLoading={ props.isLoading }
|
||||
/>
|
||||
@@ -70,6 +71,7 @@ const Icon = (props: IconProps) => {
|
||||
address_hash: props.pool.quote_token_address,
|
||||
name: '',
|
||||
type: 'ERC-20',
|
||||
reputation: null,
|
||||
}}
|
||||
isLoading={ props.isLoading }
|
||||
/>
|
||||
|
||||
@@ -40,6 +40,7 @@ test.describe('variant', () => {
|
||||
symbol: 'DUCK DUCK DUCK',
|
||||
address_hash: tokenMock.tokenInfo.address_hash,
|
||||
icon_url: LOGO_URL,
|
||||
reputation: 'ok',
|
||||
}}
|
||||
chain={ opSuperchainMock.chainDataA }
|
||||
/>,
|
||||
@@ -83,6 +84,7 @@ test('with logo, long name and symbol', async({ page, render }) => {
|
||||
symbol: 'DUCK DUCK DUCK',
|
||||
address_hash: tokenMock.tokenInfo.address_hash,
|
||||
icon_url: LOGO_URL,
|
||||
reputation: 'ok',
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
@@ -94,6 +96,34 @@ test('with logo, long name and symbol', async({ page, render }) => {
|
||||
await expect(page).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test('scam token', async({ page, render, mockEnvs }) => {
|
||||
await mockEnvs([
|
||||
[ 'NEXT_PUBLIC_VIEWS_TOKEN_SCAM_TOGGLE_ENABLED', 'true' ],
|
||||
]);
|
||||
|
||||
const LOGO_URL = 'https://example.com/logo.png';
|
||||
await page.route(LOGO_URL, (route) => {
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
path: './playwright/mocks/image_s.jpg',
|
||||
});
|
||||
});
|
||||
|
||||
await render(
|
||||
<TokenEntity
|
||||
token={{
|
||||
type: 'ERC-20',
|
||||
name: 'Duck Token',
|
||||
symbol: 'DUCK',
|
||||
address_hash: tokenMock.tokenInfo.address_hash,
|
||||
icon_url: LOGO_URL,
|
||||
reputation: 'scam',
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
await expect(page).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test('loading', async({ render }) => {
|
||||
const component = await render(
|
||||
<TokenEntity
|
||||
|
||||
@@ -5,12 +5,15 @@ import type { TokenInfo } from 'types/api/token';
|
||||
|
||||
import { route } from 'nextjs/routes';
|
||||
|
||||
import config from 'configs/app';
|
||||
import { useMultichainContext } from 'lib/contexts/multichain';
|
||||
import getChainTooltipText from 'lib/multichain/getChainTooltipText';
|
||||
import getIconUrl from 'lib/multichain/getIconUrl';
|
||||
import { Skeleton } from 'toolkit/chakra/skeleton';
|
||||
import { Tooltip } from 'toolkit/chakra/tooltip';
|
||||
import { TruncatedTextTooltip } from 'toolkit/components/truncation/TruncatedTextTooltip';
|
||||
import * as EntityBase from 'ui/shared/entities/base/components';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
import TokenLogoPlaceholder from 'ui/shared/TokenLogoPlaceholder';
|
||||
|
||||
import { distributeEntityProps, getIconProps } from '../base/utils';
|
||||
@@ -125,8 +128,20 @@ const Copy = (props: CopyProps) => {
|
||||
|
||||
const Container = EntityBase.Container;
|
||||
|
||||
export const Reputation = (props: Pick<EntityProps, 'token'>) => {
|
||||
if (config.UI.views.token.hideScamTokensEnabled && props.token.reputation === 'scam') {
|
||||
return (
|
||||
<Tooltip content="This token has been flagged as a potential scam. You enabled the display of flagged tokens in the explorer — proceed with caution.">
|
||||
<IconSvg name="scam" boxSize={ 5 } ml={ 2 }/>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export interface EntityProps extends EntityBase.EntityBaseProps {
|
||||
token: Pick<TokenInfo, 'address_hash' | 'icon_url' | 'name' | 'symbol' | 'type'>;
|
||||
token: Pick<TokenInfo, 'address_hash' | 'icon_url' | 'name' | 'symbol' | 'type' | 'reputation'>;
|
||||
noSymbol?: boolean;
|
||||
jointSymbol?: boolean;
|
||||
onlySymbol?: boolean;
|
||||
@@ -144,6 +159,7 @@ const TokenEntity = (props: EntityProps) => {
|
||||
{ props.noLink ? content : <Link { ...partsProps.link }>{ content }</Link> }
|
||||
<Symbol { ...partsProps.symbol }/>
|
||||
<Copy { ...partsProps.copy }/>
|
||||
<Reputation token={ props.token }/>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
@@ -32,6 +32,7 @@ const TOKEN = {
|
||||
total_supply: '76923002799740785',
|
||||
type: 'ERC-20' as const,
|
||||
volume_24h: '82069586622.4918',
|
||||
reputation: 'ok' as const,
|
||||
};
|
||||
|
||||
const LinkShowcase = () => {
|
||||
|
||||
@@ -53,6 +53,7 @@ const SearchBarSuggestToken = ({ data, isMobile, searchTerm, addressFormat }: It
|
||||
{ name }
|
||||
{ data.certified && certifiedIcon }
|
||||
{ data.is_verified_via_admin_panel && !data.certified && verifiedIcon }
|
||||
{ data.reputation && <TokenEntity.Reputation token={{ ...data, type: data.token_type }}/> }
|
||||
</Flex>
|
||||
<Grid templateColumns={ templateCols } alignItems="center" gap={ 2 }>
|
||||
<Flex alignItems="center" overflow="hidden">
|
||||
@@ -72,6 +73,7 @@ const SearchBarSuggestToken = ({ data, isMobile, searchTerm, addressFormat }: It
|
||||
{ name }
|
||||
{ data.certified && certifiedIcon }
|
||||
{ data.is_verified_via_admin_panel && !data.certified && verifiedIcon }
|
||||
{ data.reputation && <TokenEntity.Reputation token={{ ...data, type: data.token_type }}/> }
|
||||
</Flex>
|
||||
<Flex alignItems="center" overflow="hidden">
|
||||
{ address }
|
||||
|
||||
@@ -13,7 +13,7 @@ import useApiQuery from 'lib/api/useApiQuery';
|
||||
import { useMultichainContext } from 'lib/contexts/multichain';
|
||||
import { getTokenTypeName } from 'lib/token/tokenTypes';
|
||||
import { Tooltip } from 'toolkit/chakra/tooltip';
|
||||
import AddressMetadataAlert from 'ui/address/details/AddressMetadataAlert';
|
||||
import AddressAlerts from 'ui/address/details/AddressAlerts';
|
||||
import AddressQrCode from 'ui/address/details/AddressQrCode';
|
||||
import AccountActionsMenu from 'ui/shared/AccountActionsMenu/AccountActionsMenu';
|
||||
import AddressAddToWallet from 'ui/shared/address/AddressAddToWallet';
|
||||
@@ -92,6 +92,7 @@ const TokenPageTitle = ({ tokenQuery, addressQuery, hash }: Props) => {
|
||||
|
||||
const contentAfter = (
|
||||
<>
|
||||
{ tokenQuery.data && <TokenEntity.Reputation token={ tokenQuery.data }/> }
|
||||
{ verifiedInfoQuery.data?.tokenAddress && (
|
||||
<Tooltip content={ `Information on this token has been verified by ${ config.chain.name }` }>
|
||||
<IconSvg name="certified" color="green.500" boxSize={ 6 } cursor="pointer"/>
|
||||
@@ -144,8 +145,14 @@ const TokenPageTitle = ({ tokenQuery, addressQuery, hash }: Props) => {
|
||||
contentAfter={ contentAfter }
|
||||
secondRow={ secondRow }
|
||||
/>
|
||||
{ !addressMetadataQuery.isPending &&
|
||||
<AddressMetadataAlert tags={ addressMetadataQuery.data?.addresses?.[hash.toLowerCase()]?.tags } mt="-4px" mb={ 6 }/> }
|
||||
{ !addressMetadataQuery.isPending && (
|
||||
<AddressAlerts
|
||||
tags={ addressMetadataQuery.data?.addresses?.[hash.toLowerCase()]?.tags }
|
||||
isScamToken={ tokenQuery.data?.reputation === 'scam' }
|
||||
mt="-4px"
|
||||
mb={ 6 }
|
||||
/>
|
||||
) }
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -29,6 +29,7 @@ const NovesActionSnippet: FC<Props> = ({ item, isLoaded }) => {
|
||||
address_hash: action.nft?.address || action.token?.address || '',
|
||||
icon_url: '',
|
||||
type: action.nft ? 'ERC-721' as const : 'ERC-20' as const,
|
||||
reputation: null,
|
||||
};
|
||||
|
||||
return token;
|
||||
|
||||
@@ -44,6 +44,7 @@ const TxDetailsAction = ({ action }: Props) => {
|
||||
type: 'ERC-20' as const,
|
||||
symbol: null,
|
||||
icon_url: null,
|
||||
reputation: null,
|
||||
};
|
||||
const token1 = {
|
||||
address_hash: data.symbol1 === 'Ether' ? '' : data.address1,
|
||||
@@ -51,6 +52,7 @@ const TxDetailsAction = ({ action }: Props) => {
|
||||
type: 'ERC-20' as const,
|
||||
symbol: null,
|
||||
icon_url: null,
|
||||
reputation: null,
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -102,6 +104,7 @@ const TxDetailsAction = ({ action }: Props) => {
|
||||
type: 'ERC-20' as const,
|
||||
symbol: null,
|
||||
icon_url: null,
|
||||
reputation: null,
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -31,6 +31,7 @@ const ArbitrumL2TxnWithdrawalsValue = ({ data }: Props) => {
|
||||
total_supply: null,
|
||||
circulating_market_cap: null,
|
||||
icon_url: null,
|
||||
reputation: null,
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -56,6 +56,7 @@ const VerifiedAddressesListItem = ({ item, application, onAdd, onEdit, isLoading
|
||||
address_hash: application.tokenAddress,
|
||||
name: item.metadata.tokenName,
|
||||
symbol: '',
|
||||
reputation: null,
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -57,6 +57,7 @@ const VerifiedAddressesTableItem = ({ item, application, onAdd, onEdit, isLoadin
|
||||
address_hash: application.tokenAddress,
|
||||
name: item.metadata.tokenName,
|
||||
symbol: '',
|
||||
reputation: null,
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -21,6 +21,7 @@ const WatchListAddressItem = ({ item, isLoading }: { item: WatchlistAddress; isL
|
||||
symbol: '',
|
||||
address_hash: '',
|
||||
type: 'ERC-20' as const,
|
||||
reputation: null,
|
||||
}), [ ]);
|
||||
|
||||
const { usdBn: usdNative } = getCurrencyValue({ value: item.address_balance, accuracy: 2, accuracyUsd: 2, exchangeRate: item.exchange_rate });
|
||||
|
||||
@@ -112,6 +112,7 @@ const ZetaChainAssetFilter = ({ value = null, handleFilterChange }: Props) => {
|
||||
holders_count: null,
|
||||
exchange_rate: null,
|
||||
circulating_market_cap: null,
|
||||
reputation: null,
|
||||
} : token;
|
||||
setCurrentValue(tokenInfo);
|
||||
const filterParams = getFilterParamsFromValue(tokenInfo);
|
||||
|
||||
@@ -98,6 +98,7 @@ const ZetaChainFilterByColumn = ({ column, filters, columnName, handleFilterChan
|
||||
holders_count: null,
|
||||
exchange_rate: null,
|
||||
circulating_market_cap: null,
|
||||
reputation: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user