Skip to content

Commit 5731de2

Browse files
authored
Merge pull request #1909 from oasisprotocol/lw/token-icons-origins
Display token icons and origins from metadata
2 parents d16e840 + 8176ab1 commit 5731de2

File tree

13 files changed

+95
-22
lines changed

13 files changed

+95
-22
lines changed

.changelog/1909.feature.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Display token icons and origins from metadata

src/app/components/Account/RuntimeAccountDetailsView.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { DashboardLink } from '../../pages/ParatimeDashboardPage/DashboardLink'
1414
import { AllTokenPrices } from '../../../coin-gecko/api'
1515
import { ContractCreatorInfo } from './ContractCreatorInfo'
1616
import { VerificationIcon } from '../ContractVerificationIcon'
17-
import { TokenLink } from '../Tokens/TokenLink'
17+
import { TokenLinkWithIcon } from '../Tokens/TokenLinkWithIcon'
1818
import { AccountAvatar } from '../AccountAvatar'
1919
import { RuntimeBalanceDisplay } from '../Balance/RuntimeBalanceDisplay'
2020
import { calculateFiatValue } from '../Balance/hooks'
@@ -89,7 +89,7 @@ export const RuntimeAccountDetailsView: FC<RuntimeAccountDetailsViewProps> = ({
8989
<>
9090
<dt>{t('common.token')}</dt>
9191
<dd>
92-
<TokenLink
92+
<TokenLinkWithIcon
9393
scope={account}
9494
address={token.eth_contract_addr || token.contract_addr}
9595
name={token.name}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { COLORS } from '../../../styles/theme/colors'
2+
3+
function extractTwoChars(strRaw: string) {
4+
const str = strRaw.trim().replace(/[^A-Za-z0-9 ]/g, '')
5+
if (str.length < 2) return str || ' '
6+
const first = str[0]
7+
const rest = str.slice(1)
8+
const lastDigitInSequence = rest.match(/\d+/)?.[0].at(-1)
9+
const firstCharAfterSpace = rest.match(/\s+./)?.[0].at(-1)
10+
const firstCapitalized = rest.match(/[A-Z]/)?.[0]
11+
const second = lastDigitInSequence || firstCharAfterSpace || firstCapitalized || rest[0]
12+
return first + second
13+
}
14+
15+
export const InitialsAvatar = ({ name, size }: { name: string; size: number }) => {
16+
return (
17+
<svg width={size} height={size} viewBox="0 0 100 100">
18+
<rect width="100" height="100" rx="50" fill={COLORS.brandDark} />
19+
<text fontSize="40" x="50" y="50" dominantBaseline="central" textAnchor="middle" fill={COLORS.white}>
20+
{extractTwoChars(name)}
21+
</text>
22+
</svg>
23+
)
24+
}

src/app/components/Tokens/TokenDetails.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { TextSkeleton } from '../Skeleton'
44
import { StyledDescriptionList } from '../StyledDescriptionList'
55
import { useScreenSize } from '../../hooks/useScreensize'
66
import { useTranslation } from 'react-i18next'
7-
import { TokenLink } from './TokenLink'
7+
import { TokenLinkWithIcon } from './TokenLinkWithIcon'
88
import { CopyToClipboard } from '../CopyToClipboard'
99
import { AccountLink } from '../Account/AccountLink'
1010
import { DashboardLink } from '../../pages/ParatimeDashboardPage/DashboardLink'
@@ -40,7 +40,7 @@ export const TokenDetails: FC<{
4040
)}
4141
<dt>{t('common.name')}</dt>
4242
<dd>
43-
<TokenLink
43+
<TokenLinkWithIcon
4444
scope={token}
4545
address={token.eth_contract_addr ?? token.contract_addr}
4646
name={token.name}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { FC } from 'react'
2+
3+
import { SearchScope } from '../../../types/searchScope'
4+
import { TokenLink } from './TokenLink'
5+
import { useAccountMetadata } from '../../hooks/useAccountMetadata'
6+
import Box from '@mui/material/Box'
7+
import { COLORS } from '../../../styles/theme/colors'
8+
import { InitialsAvatar } from '../AccountAvatar/InitialsAvatar'
9+
10+
export const TokenLinkWithIcon: FC<{
11+
scope: SearchScope
12+
address: string
13+
name: string | undefined
14+
highlightedPart?: string | undefined
15+
}> = ({ scope, address, name, highlightedPart }) => {
16+
const { metadata } = useAccountMetadata(scope, address)
17+
return (
18+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 4 }}>
19+
{metadata?.icon ? (
20+
<img src={metadata.icon} alt="" width={28} />
21+
) : (
22+
<InitialsAvatar name={metadata?.name || name || address.slice(2, 4)} size={28} />
23+
)}
24+
25+
<span>
26+
<TokenLink
27+
scope={scope}
28+
address={address}
29+
name={metadata?.name || name}
30+
highlightedPart={highlightedPart}
31+
/>
32+
<Box component="span" sx={{ color: COLORS.grayMedium }}>
33+
{metadata?.origin && ` (${metadata.origin})`}
34+
</Box>
35+
</span>
36+
</Box>
37+
)
38+
}

src/app/components/Tokens/TokenList.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { EvmToken, EvmTokenType } from '../../../oasis-nexus/api'
33
import { Table, TableCellAlign, TableColProps } from '../../components/Table'
44
import { TablePaginationProps } from '../Table/TablePagination'
55
import { AccountLink } from '../Account/AccountLink'
6-
import { TokenLink } from './TokenLink'
6+
import { TokenLinkWithIcon } from './TokenLinkWithIcon'
77
import { CopyToClipboard } from '../CopyToClipboard'
88
import { VerificationIcon, verificationIconBoxHeight } from '../ContractVerificationIcon'
99
import Box from '@mui/material/Box'
@@ -88,7 +88,7 @@ export const TokenList = (props: TokensProps) => {
8888
},
8989
{
9090
content: (
91-
<TokenLink
91+
<TokenLinkWithIcon
9292
scope={token}
9393
address={token.eth_contract_addr ?? token.contract_addr}
9494
name={token.name}

src/app/data/named-accounts.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ export type AccountMetadata = {
77
address: Address
88
name?: string
99
description?: string
10-
source: AccountMetadataSource
10+
icon?: string // Sanitized URL
11+
origin?: string // Origin of this token/account
12+
source: AccountMetadataSource // Origin of metadata. TODO: rename to e.g. metadata_source
1113
}
1214

1315
export type AccountMetadataInfo = {

src/app/data/oasis-account-names.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
import { hasTextMatch } from '../components/HighlightedText/text-matching'
2020
import * as externalLinks from '../utils/externalLinks'
2121
import { getOasisAddress } from '../utils/helpers'
22+
import { hasValidProtocol } from '../utils/url'
2223

2324
const dataSources: Record<Network, Partial<Record<Layer, string>>> = {
2425
[Network.mainnet]: {
@@ -55,6 +56,8 @@ const getOasisAccountsMetadata = async (network: Network, layer: Layer): Promise
5556
address: getOasisAddress(entry.Address),
5657
name: entry.Name,
5758
description: entry.Description,
59+
origin: entry.Origin,
60+
icon: entry.Icon && hasValidProtocol(entry.Icon) ? entry.Icon : undefined,
5861
}
5962
// Register the metadata in its native form
6063
list.push(metadata)

src/app/pages/NFTInstanceDashboardPage/InstanceDetailsCard.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { VerificationIcon } from '../../components/ContractVerificationIcon'
1111
import CardContent from '@mui/material/CardContent'
1212
import { TokenTypeTag } from '../../components/Tokens/TokenList'
1313
import { SearchScope } from '../../../types/searchScope'
14-
import { TokenLink } from '../../components/Tokens/TokenLink'
14+
import { TokenLinkWithIcon } from '../../components/Tokens/TokenLinkWithIcon'
1515
import { EvmNft } from 'oasis-nexus/api'
1616
import Box from '@mui/material/Box'
1717

@@ -66,7 +66,7 @@ export const InstanceDetailsCard: FC<InstanceDetailsCardProps> = ({
6666
<dd>{nft.id}</dd>
6767
<dt>{t('common.collection')} </dt>
6868
<dd>
69-
<TokenLink scope={scope} address={contractAddress} name={token?.name} />
69+
<TokenLinkWithIcon scope={scope} address={contractAddress} name={token?.name} />
7070
</dd>
7171
<dt>{t('common.type')} </dt>
7272
<dd>

src/app/pages/RuntimeAccountDetailsPage/AccountTokensCard.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { EvmTokenType, Layer } from '../../../oasis-nexus/api'
1111
import { AppErrors } from '../../../types/errors'
1212
import { LinkableCardLayout } from '../../components/LinkableCardLayout'
1313
import { LinkableDiv } from '../../components/PageLayout/LinkableDiv'
14-
import { TokenLink } from '../../components/Tokens/TokenLink'
14+
import { TokenLinkWithIcon } from '../../components/Tokens/TokenLinkWithIcon'
1515
import { AccountLink } from '../../components/Account/AccountLink'
1616
import {
1717
getTokenTypePluralDescription,
@@ -69,7 +69,7 @@ export const AccountTokensCard: FC<AccountTokensCardProps> = ({ scope, account,
6969
data: [
7070
{
7171
content: (
72-
<TokenLink
72+
<TokenLinkWithIcon
7373
scope={scope}
7474
address={item.token_contract_addr_eth ?? item.token_contract_addr}
7575
name={item.token_name || t('common.missing')}

0 commit comments

Comments
 (0)