fix: use shortenAddress for mini portfolio addresses (#6774)
* fix: use shortenAddress for mini portfolio addresses * Update src/utils/addresses.ts Co-authored-by: Jordan Frankfurt <jordanwfrankfurt@gmail.com> --------- Co-authored-by: Jordan Frankfurt <jordanwfrankfurt@gmail.com>
This commit is contained in:
parent
6528fd136e
commit
f27bba9ffa
@ -24,8 +24,8 @@ import { useAppDispatch } from 'state/hooks'
|
||||
import { updateSelectedWallet } from 'state/user/reducer'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { CopyHelper, ExternalLink, ThemedText } from 'theme'
|
||||
import { shortenAddress } from 'utils'
|
||||
|
||||
import { shortenAddress } from '../../nft/utils/address'
|
||||
import { useCloseModal, useFiatOnrampAvailability, useOpenModal, useToggleModal } from '../../state/application/hooks'
|
||||
import { ApplicationModal } from '../../state/application/reducer'
|
||||
import { useUserHasAvailableClaim, useUserUnclaimedAmount } from '../../state/claim/hooks'
|
||||
@ -244,12 +244,12 @@ export default function AuthenticatedHeader({ account, openSettings }: { account
|
||||
{account && (
|
||||
<AccountNamesWrapper>
|
||||
<ThemedText.SubHeader>
|
||||
<CopyText toCopy={ENSName ?? account}>{ENSName ?? shortenAddress(account, 4, 4)}</CopyText>
|
||||
<CopyText toCopy={ENSName ?? account}>{ENSName ?? shortenAddress(account)}</CopyText>
|
||||
</ThemedText.SubHeader>
|
||||
{/* Displays smaller view of account if ENS name was rendered above */}
|
||||
{ENSName && (
|
||||
<ThemedText.BodySmall color="textTertiary">
|
||||
<CopyText toCopy={account}>{shortenAddress(account, 4, 4)}</CopyText>
|
||||
<CopyText toCopy={account}>{shortenAddress(account)}</CopyText>
|
||||
</ThemedText.BodySmall>
|
||||
)}
|
||||
</AccountNamesWrapper>
|
||||
|
@ -7,6 +7,7 @@ import { TransactionStatus } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import useENSName from 'hooks/useENSName'
|
||||
import styled from 'styled-components/macro'
|
||||
import { EllipsisStyle, ThemedText } from 'theme'
|
||||
import { shortenAddress } from 'utils'
|
||||
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
|
||||
|
||||
import { PortfolioLogo } from '../PortfolioLogo'
|
||||
@ -52,7 +53,7 @@ export function ActivityRow({
|
||||
descriptor={
|
||||
<ActivityRowDescriptor color="textSecondary">
|
||||
{descriptor}
|
||||
{ENSName ?? otherAccount}
|
||||
{ENSName ?? shortenAddress(otherAccount)}
|
||||
</ActivityRowDescriptor>
|
||||
}
|
||||
right={
|
||||
|
@ -1,7 +1,6 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { escapeRegExp } from '../../utils'
|
||||
import { escapeRegExp } from 'utils'
|
||||
|
||||
const StyledInput = styled.input<{ error?: boolean; fontSize?: string; align?: string }>`
|
||||
color: ${({ error, theme }) => (error ? theme.accentFailure : theme.textPrimary)};
|
||||
|
@ -15,10 +15,10 @@ import { useCallback, useMemo } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { colors } from 'theme/colors'
|
||||
import { flexRowNoWrap } from 'theme/styles'
|
||||
import { shortenAddress } from 'utils'
|
||||
|
||||
import { isTransactionRecent, useAllTransactions } from '../../state/transactions/hooks'
|
||||
import { TransactionDetails } from '../../state/transactions/types'
|
||||
import { shortenAddress } from '../../utils'
|
||||
import { ButtonSecondary } from '../Button'
|
||||
import StatusIcon from '../Identicon/StatusIcon'
|
||||
import { RowBetween } from '../Row'
|
||||
|
@ -5,6 +5,7 @@ import { useWeb3React } from '@web3-react/core'
|
||||
import { useState } from 'react'
|
||||
import { Text } from 'rebass'
|
||||
import styled from 'styled-components/macro'
|
||||
import { shortenAddress } from 'utils'
|
||||
|
||||
import Circle from '../../assets/images/blue-loader.svg'
|
||||
import tokenLogo from '../../assets/images/token-logo.png'
|
||||
@ -12,7 +13,6 @@ import useENS from '../../hooks/useENS'
|
||||
import { useClaimCallback, useUserHasAvailableClaim, useUserUnclaimedAmount } from '../../state/claim/hooks'
|
||||
import { useIsTransactionPending } from '../../state/transactions/hooks'
|
||||
import { CloseIcon, CustomLightSpinner, ExternalLink, ThemedText, UniTokenAnimated } from '../../theme'
|
||||
import { shortenAddress } from '../../utils'
|
||||
import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink'
|
||||
import AddressInputPanel from '../AddressInputPanel'
|
||||
import { ButtonPrimary } from '../Button'
|
||||
|
@ -32,8 +32,7 @@ import { WRAPPED_NATIVE_CURRENCY } from 'constants/tokens'
|
||||
import { useMemo } from 'react'
|
||||
import { NonfungiblePositionManager, Quoter, QuoterV2, TickLens, UniswapInterfaceMulticall } from 'types/v3'
|
||||
import { V3Migrator } from 'types/v3/V3Migrator'
|
||||
|
||||
import { getContract } from '../utils'
|
||||
import { getContract } from 'utils'
|
||||
|
||||
const { abi: IUniswapV2PairABI } = IUniswapV2PairJson
|
||||
const { abi: IUniswapV2Router02ABI } = IUniswapV2Router02Json
|
||||
|
@ -25,7 +25,6 @@ import {
|
||||
TokenRarity,
|
||||
} from 'nft/types'
|
||||
import { getMarketplaceIcon } from 'nft/utils'
|
||||
import { shortenAddress } from 'nft/utils/address'
|
||||
import { buildActivityAsset } from 'nft/utils/buildActivityAsset'
|
||||
import { formatEth } from 'nft/utils/currency'
|
||||
import { getTimeDifference } from 'nft/utils/date'
|
||||
@ -33,6 +32,7 @@ import { putCommas } from 'nft/utils/putCommas'
|
||||
import { MouseEvent, ReactNode, useMemo, useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ExternalLink } from 'theme'
|
||||
import { shortenAddress } from 'utils'
|
||||
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
|
||||
|
||||
import * as styles from './Activity.css'
|
||||
@ -165,7 +165,7 @@ export const AddressCell = ({ address, desktopLBreakpoint, chainId }: AddressCel
|
||||
href={getExplorerLink(chainId ?? ChainId.MAINNET, address ?? '', ExplorerDataType.ADDRESS)}
|
||||
style={{ textDecoration: 'none' }}
|
||||
>
|
||||
<Box onClick={(e) => e.stopPropagation()}>{address ? shortenAddress(address, 2, 4) : '-'}</Box>
|
||||
<Box onClick={(e) => e.stopPropagation()}>{address ? shortenAddress(address, 2) : '-'}</Box>
|
||||
</AddressLink>
|
||||
</Column>
|
||||
)
|
||||
|
@ -4,11 +4,11 @@ import { LoadingBubble } from 'components/Tokens/loading'
|
||||
import { EventCell } from 'nft/components/collection/ActivityCells'
|
||||
import { ActivityEvent } from 'nft/types'
|
||||
import { getMarketplaceIcon } from 'nft/utils'
|
||||
import { shortenAddress } from 'nft/utils/address'
|
||||
import { formatEth } from 'nft/utils/currency'
|
||||
import { getTimeDifference } from 'nft/utils/date'
|
||||
import { ReactNode } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { shortenAddress } from 'utils'
|
||||
|
||||
const TR = styled.tr`
|
||||
border-bottom: ${({ theme }) => `1px solid ${theme.backgroundOutline}`};
|
||||
@ -177,7 +177,7 @@ const AssetActivity = ({ events }: { events?: ActivityEvent[] }) => {
|
||||
<TD>
|
||||
{fromAddress && (
|
||||
<Link href={`https://etherscan.io/address/${fromAddress}`} target="_blank" rel="noopener noreferrer">
|
||||
{shortenAddress(fromAddress, 2, 4)}
|
||||
{shortenAddress(fromAddress, 2)}
|
||||
</Link>
|
||||
)}
|
||||
</TD>
|
||||
@ -185,7 +185,7 @@ const AssetActivity = ({ events }: { events?: ActivityEvent[] }) => {
|
||||
<TD>
|
||||
{toAddress && (
|
||||
<Link href={`https://etherscan.io/address/${toAddress}`} target="_blank" rel="noopener noreferrer">
|
||||
{shortenAddress(toAddress, 2, 4)}
|
||||
{shortenAddress(toAddress, 2)}
|
||||
</Link>
|
||||
)}
|
||||
</TD>
|
||||
|
@ -10,7 +10,6 @@ import { AssetPriceDetails } from 'nft/components/details/AssetPriceDetails'
|
||||
import { Center } from 'nft/components/Flex'
|
||||
import { themeVars, vars } from 'nft/css/sprinkles.css'
|
||||
import { ActivityEventType, CollectionInfoForAsset, GenieAsset } from 'nft/types'
|
||||
import { shortenAddress } from 'nft/utils/address'
|
||||
import { formatEth } from 'nft/utils/currency'
|
||||
import { isAudio } from 'nft/utils/isAudio'
|
||||
import { isVideo } from 'nft/utils/isVideo'
|
||||
@ -20,6 +19,7 @@ import InfiniteScroll from 'react-infinite-scroll-component'
|
||||
import { Link as RouterLink } from 'react-router-dom'
|
||||
import styled from 'styled-components/macro'
|
||||
import { useIsDarkMode } from 'theme/components/ThemeToggle'
|
||||
import { shortenAddress } from 'utils/addresses'
|
||||
|
||||
import AssetActivity, { LoadingAssetActivity } from './AssetActivity'
|
||||
import * as styles from './AssetDetails.css'
|
||||
@ -427,7 +427,7 @@ export const AssetDetails = ({ asset, collection }: AssetDetailsProps) => {
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{shortenAddress(asset.creator.address, 2, 4)}
|
||||
{shortenAddress(asset.creator.address, 2)}
|
||||
</AddressTextLink>
|
||||
)}
|
||||
|
||||
|
@ -16,13 +16,13 @@ import {
|
||||
timeLeft,
|
||||
useUsdPrice,
|
||||
} from 'nft/utils'
|
||||
import { shortenAddress } from 'nft/utils/address'
|
||||
import { useMemo } from 'react'
|
||||
import { Upload } from 'react-feather'
|
||||
import { useQuery } from 'react-query'
|
||||
import { Link, useNavigate } from 'react-router-dom'
|
||||
import styled, { css, useTheme } from 'styled-components/macro'
|
||||
import { ExternalLink, ThemedText } from 'theme'
|
||||
import { shortenAddress } from 'utils/addresses'
|
||||
|
||||
const TWITTER_WIDTH = 560
|
||||
const TWITTER_HEIGHT = 480
|
||||
@ -425,7 +425,7 @@ export const AssetPriceDetails = ({ asset, collection }: AssetPriceDetailsProps)
|
||||
{asset.tokenType === 'ERC1155' ? (
|
||||
''
|
||||
) : (
|
||||
<span> {isOwner ? 'You' : asset.ownerAddress && shortenAddress(asset.ownerAddress, 2, 4)}</span>
|
||||
<span> {isOwner ? 'You' : asset.ownerAddress && shortenAddress(asset.ownerAddress, 2)}</span>
|
||||
)}
|
||||
</OwnerText>
|
||||
</OwnerInformationContainer>
|
||||
|
@ -2,10 +2,10 @@ import { OpacityHoverState } from 'components/Common'
|
||||
import useCopyClipboard from 'hooks/useCopyClipboard'
|
||||
import { CollectionInfoForAsset, GenieAsset } from 'nft/types'
|
||||
import { putCommas } from 'nft/utils'
|
||||
import { shortenAddress } from 'nft/utils/address'
|
||||
import { useCallback } from 'react'
|
||||
import { Copy } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import { shortenAddress } from 'utils'
|
||||
|
||||
const Details = styled.div`
|
||||
display: grid;
|
||||
@ -80,7 +80,7 @@ const DetailsContainer = ({ asset, collection }: { asset: GenieAsset; collection
|
||||
header="Contract address"
|
||||
body={
|
||||
<Center onClick={copy}>
|
||||
{shortenAddress(address, 2, 4)} <CopyIcon size={13} />
|
||||
{shortenAddress(address, 2)} <CopyIcon size={13} />
|
||||
</Center>
|
||||
}
|
||||
/>
|
||||
@ -97,7 +97,7 @@ const DetailsContainer = ({ asset, collection }: { asset: GenieAsset; collection
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
{shortenAddress(creator.address, 2, 4)}
|
||||
{shortenAddress(creator.address, 2)}
|
||||
</CreatorLink>
|
||||
)
|
||||
}
|
||||
|
@ -1,15 +0,0 @@
|
||||
import { isAddress } from '@ethersproject/address'
|
||||
|
||||
/**
|
||||
* Shortens an Ethereum address by N characters
|
||||
* @param address blockchain address
|
||||
* @param charsStart amount of character to shorten (from both ends / in the beginning)
|
||||
* @param charsEnd amount of characters to shorten in the end
|
||||
* @returns formatted string
|
||||
*/
|
||||
export function shortenAddress(address: string, charsStart = 4, charsEnd?: number): string {
|
||||
const parsed = isAddress(address)
|
||||
if (!parsed) return ''
|
||||
|
||||
return `${address.substring(0, charsStart + 2)}...${address.substring(42 - (charsEnd || charsStart))}`
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { isAddress, shortenAddress } from '.'
|
||||
import { isAddress, shortenAddress } from './addresses'
|
||||
|
||||
describe('utils', () => {
|
||||
describe('#isAddress', () => {
|
||||
@ -18,14 +18,15 @@ describe('utils', () => {
|
||||
it('succeeds even without prefix', () => {
|
||||
expect(isAddress('f164fc0ec4e93095b804a4795bbe1e041497b92a')).toBe('0xf164fC0Ec4E93095b804a4795bBe1e041497b92a')
|
||||
})
|
||||
|
||||
it('fails if too long', () => {
|
||||
expect(isAddress('f164fc0ec4e93095b804a4795bbe1e041497b92a0')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#shortenAddress', () => {
|
||||
it('throws on invalid address', () => {
|
||||
expect(() => shortenAddress('abc')).toThrow("Invalid 'address'")
|
||||
it('doesnt throw on invalid address', () => {
|
||||
expect(shortenAddress('abc123')).toEqual('')
|
||||
})
|
||||
|
||||
it('truncates middle characters', () => {
|
||||
@ -39,5 +40,16 @@ describe('utils', () => {
|
||||
it('renders checksummed address', () => {
|
||||
expect(shortenAddress('0x2E1b342132A67Ea578e4E3B814bae2107dc254CC'.toLowerCase())).toBe('0x2E1b...54CC')
|
||||
})
|
||||
|
||||
it('allows undefined', () => {
|
||||
expect(shortenAddress()).toBe('')
|
||||
})
|
||||
|
||||
it('allows custom amounts of start/end chars', () => {
|
||||
expect(shortenAddress('0x2E1b342132A67Ea578e4E3B814bae2107dc254CC', 2)).toBe('0x2E...54CC')
|
||||
expect(shortenAddress('0x2E1b342132A67Ea578e4E3B814bae2107dc254CC', 6)).toBe('0x2E1b34...54CC')
|
||||
expect(shortenAddress('0x2E1b342132A67Ea578e4E3B814bae2107dc254CC', 2, 2)).toBe('0x2E...CC')
|
||||
expect(shortenAddress('0x2E1b342132A67Ea578e4E3B814bae2107dc254CC', 2, 6)).toBe('0x2E...c254CC')
|
||||
})
|
||||
})
|
||||
})
|
43
src/utils/addresses.ts
Normal file
43
src/utils/addresses.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { getAddress } from '@ethersproject/address'
|
||||
|
||||
// returns the checksummed address if the address is valid, otherwise returns false
|
||||
export function isAddress(value: any): string | false {
|
||||
try {
|
||||
// Alphabetical letters must be made lowercase for getAddress to work.
|
||||
// See documentation here: https://docs.ethers.io/v5/api/utils/address/
|
||||
return getAddress(value.toLowerCase())
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Shortens an Ethereum address
|
||||
export function shortenAddress(address = '', charsStart = 4, charsEnd = 4): string {
|
||||
const parsed = isAddress(address)
|
||||
if (!parsed) return ''
|
||||
return ellipseAddressAdd0x(parsed, charsStart, charsEnd)
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorten an address and add 0x to the start if missing
|
||||
* @param targetAddress
|
||||
* @param charsStart amount of character to shorten (from both ends / in the beginning)
|
||||
* @param charsEnd amount of characters to shorten in the end
|
||||
* @returns formatted string
|
||||
*/
|
||||
function ellipseAddressAdd0x(targetAddress: string, charsStart = 4, charsEnd = 4): string {
|
||||
const hasPrefix = targetAddress.startsWith('0x')
|
||||
const prefix = hasPrefix ? '' : '0x'
|
||||
return ellipseMiddle(prefix + targetAddress, charsStart + 2, charsEnd)
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorten a string with "..." in the middle
|
||||
* @param target
|
||||
* @param charsStart amount of character to shorten (from both ends / in the beginning)
|
||||
* @param charsEnd amount of characters to shorten in the end
|
||||
* @returns formatted string
|
||||
*/
|
||||
function ellipseMiddle(target: string, charsStart = 4, charsEnd = 4): string {
|
||||
return `${target.slice(0, charsStart)}...${target.slice(target.length - charsEnd)}`
|
||||
}
|
3
src/utils/escapeRegExp.ts
Normal file
3
src/utils/escapeRegExp.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export function escapeRegExp(string: string): string {
|
||||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // $& means the whole matched string
|
||||
}
|
23
src/utils/getContract.ts
Normal file
23
src/utils/getContract.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { AddressZero } from '@ethersproject/constants'
|
||||
import { Contract } from '@ethersproject/contracts'
|
||||
import { JsonRpcProvider, JsonRpcSigner } from '@ethersproject/providers'
|
||||
|
||||
import { isAddress } from './addresses'
|
||||
|
||||
// account is optional
|
||||
|
||||
export function getContract(address: string, ABI: any, provider: JsonRpcProvider, account?: string): Contract {
|
||||
if (!isAddress(address) || address === AddressZero) {
|
||||
throw Error(`Invalid 'address' parameter '${address}'.`)
|
||||
}
|
||||
|
||||
return new Contract(address, ABI, getProviderOrSigner(provider, account) as any)
|
||||
}
|
||||
// account is not optional
|
||||
function getSigner(provider: JsonRpcProvider, account: string): JsonRpcSigner {
|
||||
return provider.getSigner(account).connectUnchecked()
|
||||
}
|
||||
// account is optional
|
||||
function getProviderOrSigner(provider: JsonRpcProvider, account?: string): JsonRpcProvider | JsonRpcSigner {
|
||||
return account ? getSigner(provider, account) : provider
|
||||
}
|
@ -1,47 +1,3 @@
|
||||
import { getAddress } from '@ethersproject/address'
|
||||
import { AddressZero } from '@ethersproject/constants'
|
||||
import { Contract } from '@ethersproject/contracts'
|
||||
import type { JsonRpcProvider, JsonRpcSigner } from '@ethersproject/providers'
|
||||
|
||||
// returns the checksummed address if the address is valid, otherwise returns false
|
||||
export function isAddress(value: any): string | false {
|
||||
try {
|
||||
// Alphabetical letters must be made lowercase for getAddress to work.
|
||||
// See documentation here: https://docs.ethers.io/v5/api/utils/address/
|
||||
return getAddress(value.toLowerCase())
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// shorten the checksummed version of the input address to have 0x + 4 characters at start and end
|
||||
export function shortenAddress(address: string, chars = 4): string {
|
||||
const parsed = isAddress(address)
|
||||
if (!parsed) {
|
||||
throw Error(`Invalid 'address' parameter '${address}'.`)
|
||||
}
|
||||
return `${parsed.substring(0, chars + 2)}...${parsed.substring(42 - chars)}`
|
||||
}
|
||||
|
||||
// account is not optional
|
||||
function getSigner(provider: JsonRpcProvider, account: string): JsonRpcSigner {
|
||||
return provider.getSigner(account).connectUnchecked()
|
||||
}
|
||||
|
||||
// account is optional
|
||||
function getProviderOrSigner(provider: JsonRpcProvider, account?: string): JsonRpcProvider | JsonRpcSigner {
|
||||
return account ? getSigner(provider, account) : provider
|
||||
}
|
||||
|
||||
// account is optional
|
||||
export function getContract(address: string, ABI: any, provider: JsonRpcProvider, account?: string): Contract {
|
||||
if (!isAddress(address) || address === AddressZero) {
|
||||
throw Error(`Invalid 'address' parameter '${address}'.`)
|
||||
}
|
||||
|
||||
return new Contract(address, ABI, getProviderOrSigner(provider, account) as any)
|
||||
}
|
||||
|
||||
export function escapeRegExp(string: string): string {
|
||||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // $& means the whole matched string
|
||||
}
|
||||
export * from './addresses'
|
||||
export * from './escapeRegExp'
|
||||
export * from './getContract'
|
||||
|
Loading…
Reference in New Issue
Block a user