diff --git a/ui/components/app/assets/asset-list/native-token/native-token.tsx b/ui/components/app/assets/asset-list/native-token/native-token.tsx index cf0191b3de66..19a3858fe670 100644 --- a/ui/components/app/assets/asset-list/native-token/native-token.tsx +++ b/ui/components/app/assets/asset-list/native-token/native-token.tsx @@ -52,6 +52,7 @@ const NativeToken = ({ onClickAsset }: AssetListProps) => { isNativeCurrency isStakeable={isStakeable} showPercentage + chainId={chainId} /> ); }; diff --git a/ui/components/app/assets/nfts/nft-details/nft-details.tsx b/ui/components/app/assets/nfts/nft-details/nft-details.tsx index 5ee8525cc98b..e3e88137e116 100644 --- a/ui/components/app/assets/nfts/nft-details/nft-details.tsx +++ b/ui/components/app/assets/nfts/nft-details/nft-details.tsx @@ -19,12 +19,9 @@ import { } from '../../../../../helpers/constants/design-system'; import { useI18nContext } from '../../../../../hooks/useI18nContext'; import { shortenAddress } from '../../../../../helpers/utils/util'; -import { getNftImageAlt } from '../../../../../helpers/utils/nfts'; import { - getCurrentChainId, getCurrentCurrency, getCurrentNetwork, - getIpfsGateway, } from '../../../../../selectors'; import { ASSET_ROUTE, @@ -72,7 +69,7 @@ import { Numeric } from '../../../../../../shared/modules/Numeric'; // TODO: Remove restricted import // eslint-disable-next-line import/no-restricted-paths import { addUrlProtocolPrefix } from '../../../../../../app/scripts/lib/util'; -import useGetAssetImageUrl from '../../../../../hooks/useGetAssetImageUrl'; +import { getMultichainCurrentChainId } from '../../../../../selectors/multichain'; import NftDetailInformationRow from './nft-detail-information-row'; import NftDetailInformationFrame from './nft-detail-information-frame'; import NftDetailDescription from './nft-detail-description'; @@ -81,8 +78,6 @@ const MAX_TOKEN_ID_LENGTH = 15; export default function NftDetails({ nft }: { nft: Nft }) { const { - image, - imageOriginal, name, description, address, @@ -99,22 +94,14 @@ export default function NftDetails({ nft }: { nft: Nft }) { const t = useI18nContext(); const history = useHistory(); const dispatch = useDispatch(); - const ipfsGateway = useSelector(getIpfsGateway); - const currentNetwork = useSelector(getCurrentChainId); - const currentChain = useSelector(getCurrentNetwork); + const chainId = useSelector(getMultichainCurrentChainId); + const currentNetwork = useSelector(getCurrentNetwork); const trackEvent = useContext(MetaMetricsContext); const currency = useSelector(getCurrentCurrency); const selectedNativeConversionRate = useSelector(getConversionRate); const [addressCopied, handleAddressCopy] = useCopyToClipboard(); - const nftImageAlt = getNftImageAlt(nft); - const nftSrcUrl = imageOriginal ?? image; - const isIpfsURL = nftSrcUrl?.startsWith('ipfs:'); - const isImageHosted = - image?.startsWith('https:') || image?.startsWith('http:'); - const nftImageURL = useGetAssetImageUrl(imageOriginal ?? image, ipfsGateway); - const hasFloorAskPrice = Boolean( collection?.floorAsk?.price?.amount?.usd && collection?.floorAsk?.price?.amount?.native, @@ -142,8 +129,8 @@ export default function NftDetails({ nft }: { nft: Nft }) { topBid?.price?.amount?.native, collection?.topBid?.price?.amount?.native, ); - const currentChainSymbol = currentChain.ticker; - return `${topBidValue}${currentChainSymbol}`; + const currentNetworkSymbol = currentNetwork.ticker; + return `${topBidValue}${currentNetworkSymbol}`; } // return the one that is available const topBidValue = @@ -152,8 +139,8 @@ export default function NftDetails({ nft }: { nft: Nft }) { if (!topBidValue) { return undefined; } - const currentChainSymbol = currentChain.ticker; - return `${topBidValue}${currentChainSymbol}`; + const currentNetworkSymbol = currentNetwork.ticker; + return `${topBidValue}${currentNetworkSymbol}`; }; const getTopBidSourceDomain = () => { @@ -165,8 +152,6 @@ export default function NftDetails({ nft }: { nft: Nft }) { ); }; - const { chainId } = currentChain; - useEffect(() => { trackEvent({ event: MetaMetricsEventName.NftDetailsOpened, @@ -197,7 +182,7 @@ export default function NftDetails({ nft }: { nft: Nft }) { tokenId: tokenId.toString(), asset_type: AssetType.NFT, token_standard: standard, - chain_id: currentNetwork, + chain_id: chainId, isSuccessful: isSuccessfulEvent, }, }); @@ -213,7 +198,7 @@ export default function NftDetails({ nft }: { nft: Nft }) { }, [nft, prevNft]); const getOpenSeaLink = () => { - switch (currentNetwork) { + switch (chainId) { case CHAIN_IDS.MAINNET: return `https://opensea.io/assets/ethereum/${address}/${tokenId}`; case CHAIN_IDS.POLYGON: @@ -343,13 +328,7 @@ export default function NftDetails({ nft }: { nft: Nft }) { > diff --git a/ui/components/app/assets/nfts/nft-details/nft-full-image.tsx b/ui/components/app/assets/nfts/nft-details/nft-full-image.tsx index 42b4ddacbf95..0706173f47ba 100644 --- a/ui/components/app/assets/nfts/nft-details/nft-full-image.tsx +++ b/ui/components/app/assets/nfts/nft-details/nft-full-image.tsx @@ -1,8 +1,6 @@ import React, { useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; import { useHistory, useParams } from 'react-router-dom'; -import { getNftImageAlt } from '../../../../../helpers/utils/nfts'; -import { getCurrentNetwork, getIpfsGateway } from '../../../../../selectors'; import { Box, @@ -22,7 +20,7 @@ import { } from '../../../../../helpers/constants/design-system'; import { useI18nContext } from '../../../../../hooks/useI18nContext'; import { ASSET_ROUTE } from '../../../../../helpers/constants/routes'; -import useGetAssetImageUrl from '../../../../../hooks/useGetAssetImageUrl'; +import { getMultichainCurrentChainId } from '../../../../../selectors/multichain'; export default function NftFullImage() { const t = useI18nContext(); @@ -34,16 +32,9 @@ export default function NftFullImage() { isEqualCaseInsensitive(address, asset) && id === tokenId.toString(), ); - const { image, imageOriginal, name, tokenId } = nft; + // TODO: Remove this when NFTs have a native chainId on their objects + const chainId = useSelector(getMultichainCurrentChainId); - const ipfsGateway = useSelector(getIpfsGateway); - const currentChain = useSelector(getCurrentNetwork); - const nftImageURL = useGetAssetImageUrl(imageOriginal ?? image, ipfsGateway); - - const nftImageAlt = getNftImageAlt(nft); - const nftSrcUrl = imageOriginal ?? image; - const isIpfsURL = nftSrcUrl?.startsWith('ipfs:'); - const isImageHosted = image?.startsWith('https:'); const history = useHistory(); const [visible, setVisible] = useState(false); @@ -79,13 +70,7 @@ export default function NftFullImage() { > diff --git a/ui/components/app/assets/nfts/nfts-items/nfts-items.js b/ui/components/app/assets/nfts/nfts-items/nfts-items.js index 6e9e45e0b54a..08b84f0402b1 100644 --- a/ui/components/app/assets/nfts/nfts-items/nfts-items.js +++ b/ui/components/app/assets/nfts/nfts-items/nfts-items.js @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useState } from 'react'; +import React, { useContext, useEffect } from 'react'; import PropTypes from 'prop-types'; import { useDispatch, useSelector } from 'react-redux'; import { useHistory } from 'react-router-dom'; @@ -20,17 +20,14 @@ import { ENVIRONMENT_TYPE_POPUP } from '../../../../../../shared/constants/app'; // eslint-disable-next-line import/no-restricted-paths import { getEnvironmentType } from '../../../../../../app/scripts/lib/util'; import { - getCurrentChainId, getIpfsGateway, getSelectedInternalAccount, - getCurrentNetwork, } from '../../../../../selectors'; import { ASSET_ROUTE, SEND_ROUTE, } from '../../../../../helpers/constants/routes'; import { getAssetImageURL } from '../../../../../helpers/utils/util'; -import { getNftImageAlt } from '../../../../../helpers/utils/nfts'; import { updateNftDropDownState } from '../../../../../store/actions'; import { usePrevious } from '../../../../../hooks/usePrevious'; import { getNftsDropdownState } from '../../../../../ducks/metamask/metamask'; @@ -47,7 +44,7 @@ import { MetaMetricsEventCategory, MetaMetricsEventName, } from '../../../../../../shared/constants/metametrics'; -import { isEqualCaseInsensitive } from '../../../../../../shared/modules/string-utils'; +import { getMultichainCurrentChainId } from '../../../../../selectors/multichain'; import { CollectionImageComponent } from './collection-image.component'; const width = (isModal) => { @@ -77,13 +74,10 @@ export default function NftsItems({ const nftsDropdownState = useSelector(getNftsDropdownState); const previousCollectionKeys = usePrevious(collectionsKeys); const { address: selectedAddress } = useSelector(getSelectedInternalAccount); - const chainId = useSelector(getCurrentChainId); - const currentChain = useSelector(getCurrentNetwork); + const chainId = useSelector(getMultichainCurrentChainId); const t = useI18nContext(); const ipfsGateway = useSelector(getIpfsGateway); - const [updatedNfts, setUpdatedNfts] = useState([]); - const trackEvent = useContext(MetaMetricsContext); const sendAnalytics = useSelector(getSendAnalyticProperties); @@ -146,8 +140,7 @@ export default function NftsItems({ } } } - const settled = await Promise.all(promisesArr); - setUpdatedNfts(settled); + await Promise.all(promisesArr); }; modifyItems(); @@ -203,19 +196,6 @@ export default function NftsItems({ if (!nfts.length) { return null; } - const getSource = (isImageHosted, nft) => { - if (!isImageHosted) { - const found = updatedNfts.find( - (elm) => - elm.tokenId === nft.tokenId && - isEqualCaseInsensitive(elm.address, nft.address), - ); - if (found) { - return found.ipfsImageUpdated; - } - } - return nft.image; - }; const isExpanded = nftsDropdownState[selectedAddress]?.[chainId]?.[key]; return ( @@ -262,19 +242,8 @@ export default function NftsItems({ {isExpanded ? ( {nfts.map((nft, i) => { - const { image, address, tokenId, name, imageOriginal, tokenURI } = - nft; - const nftImageAlt = getNftImageAlt(nft); - const isImageHosted = - image?.startsWith('https:') || image?.startsWith('http:'); - - const source = getSource(isImageHosted, nft); + const { address, tokenId } = nft; - const isIpfsURL = ( - imageOriginal ?? - image ?? - tokenURI - )?.startsWith('ipfs:'); const handleImageClick = () => { if (isModal) { return onSendNft(nft); @@ -289,14 +258,8 @@ export default function NftsItems({ className="nfts-items__item-wrapper" > {showTokenId ? {`${t('id')}: ${tokenId}`} : null} diff --git a/ui/components/app/assets/token-cell/token-cell.test.tsx b/ui/components/app/assets/token-cell/token-cell.test.tsx index 882c80964d5b..2736c352b7ea 100644 --- a/ui/components/app/assets/token-cell/token-cell.test.tsx +++ b/ui/components/app/assets/token-cell/token-cell.test.tsx @@ -13,6 +13,7 @@ import { import { useIsOriginalTokenSymbol } from '../../../../hooks/useIsOriginalTokenSymbol'; import { getIntlLocale } from '../../../../ducks/locale/locale'; +import { CHAIN_IDS } from '../../../../../shared/constants/network'; import TokenCell from '.'; jest.mock('react-redux', () => { @@ -65,6 +66,7 @@ describe('Token Cell', () => { address: '0xAddress', iconUrl: './images/test_1_image.svg', aggregators: [], + chainId: CHAIN_IDS.MAINNET, }, '0xAnotherToken': { name: 'TEST', @@ -74,6 +76,7 @@ describe('Token Cell', () => { address: '0xANoTherToKen', iconUrl: './images/test_image.svg', aggregators: [], + chainId: CHAIN_IDS.MAINNET, }, }; @@ -85,6 +88,7 @@ describe('Token Cell', () => { string: '5.000', currentCurrency: 'usd', image: '', + chainId: CHAIN_IDS.MAINNET, onClick: jest.fn(), }; @@ -94,6 +98,7 @@ describe('Token Cell', () => { string: '5000000', currentCurrency: 'usd', image: '', + chainId: CHAIN_IDS.MAINNET, onClick: jest.fn(), }; const useSelectorMock = useSelector; diff --git a/ui/components/app/assets/token-cell/token-cell.tsx b/ui/components/app/assets/token-cell/token-cell.tsx index 5f5b43d6c098..c6dd9100c319 100644 --- a/ui/components/app/assets/token-cell/token-cell.tsx +++ b/ui/components/app/assets/token-cell/token-cell.tsx @@ -12,6 +12,7 @@ type TokenCellProps = { symbol: string; string?: string; image: string; + chainId: string; onClick?: (arg: string) => void; }; @@ -20,6 +21,7 @@ export default function TokenCell({ image, symbol, string, + chainId, onClick, }: TokenCellProps) { const tokenList = useSelector(getTokenList); @@ -32,11 +34,12 @@ export default function TokenCell({ const tokenImage = tokenData?.iconUrl || image; const formattedFiat = useTokenFiatAmount(address, string, symbol, {}, false); const locale = useSelector(getIntlLocale); - const primary = new Intl.NumberFormat(locale, { - minimumSignificantDigits: 1, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - }).format(string.toString()); + const primary = + new Intl.NumberFormat(locale, { + minimumSignificantDigits: 1, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + }).format(string.toString()) || '0'; const isOriginalTokenSymbol = useIsOriginalTokenSymbol(address, symbol); @@ -45,11 +48,12 @@ export default function TokenCell({ onClick={onClick ? () => onClick(address) : undefined} tokenSymbol={symbol} tokenImage={tokenImage} - primary={`${primary || 0}`} + primary={primary} secondary={isOriginalTokenSymbol ? formattedFiat : null} title={title} isOriginalTokenSymbol={isOriginalTokenSymbol} address={address} + chainId={chainId} showPercentage /> ); diff --git a/ui/components/app/assets/token-list/token-list.tsx b/ui/components/app/assets/token-list/token-list.tsx index 8a107b154fb9..8a3b283e7837 100644 --- a/ui/components/app/assets/token-list/token-list.tsx +++ b/ui/components/app/assets/token-list/token-list.tsx @@ -9,6 +9,7 @@ import { JustifyContent, } from '../../../../helpers/constants/design-system'; import { TokenWithBalance } from '../asset-list/asset-list'; +import { getMultichainCurrentChainId } from '../../../../selectors/multichain'; import { sortAssets } from '../util/sort'; import { getPreferences, @@ -30,6 +31,7 @@ export default function TokenList({ nativeToken, }: TokenListProps) { const t = useI18nContext(); + const chainId = useSelector(getMultichainCurrentChainId); const { tokenSortConfig } = useSelector(getPreferences); const selectedAccount = useSelector(getSelectedAccount); const conversionRate = useSelector(getConversionRate); @@ -86,6 +88,7 @@ export default function TokenList({ ); diff --git a/ui/components/multichain/asset-picker-amount/asset-picker-modal/Asset.tsx b/ui/components/multichain/asset-picker-amount/asset-picker-modal/Asset.tsx index f384ef8fd96f..a0bce08bd129 100644 --- a/ui/components/multichain/asset-picker-amount/asset-picker-modal/Asset.tsx +++ b/ui/components/multichain/asset-picker-amount/asset-picker-modal/Asset.tsx @@ -7,6 +7,7 @@ import { TokenListItem } from '../../token-list-item'; import { isEqualCaseInsensitive } from '../../../../../shared/modules/string-utils'; import { formatAmount } from '../../../../pages/confirmations/components/simulation-details/formatAmount'; import { getIntlLocale } from '../../../../ducks/locale/locale'; +import { getMultichainCurrentChainId } from '../../../../selectors/multichain'; import { AssetWithDisplayData, ERC20Asset } from './types'; type AssetProps = AssetWithDisplayData & { @@ -21,6 +22,7 @@ export default function Asset({ tooltipText, }: AssetProps) { const locale = useSelector(getIntlLocale); + const chainId = useSelector(getMultichainCurrentChainId); const tokenList = useSelector(getTokenList); const tokenData = address @@ -56,6 +58,7 @@ export default function Asset({ title={title} tooltipText={tooltipText} isPrimaryTokenSymbolHidden + chainId={chainId} /> ); } diff --git a/ui/components/multichain/asset-picker-amount/asset-picker-modal/AssetList.test.tsx b/ui/components/multichain/asset-picker-amount/asset-picker-modal/AssetList.test.tsx index c4e4ba0ceb29..c3d9129b773b 100644 --- a/ui/components/multichain/asset-picker-amount/asset-picker-modal/AssetList.test.tsx +++ b/ui/components/multichain/asset-picker-amount/asset-picker-modal/AssetList.test.tsx @@ -173,7 +173,7 @@ describe('AssetList', () => { ); expect(screen.getAllByTestId('asset-list-item')[0]).toHaveClass( - 'multichain-asset-picker-list-item--disabled', + 'multichain-asset-picker-list-items--disabled', ); }); }); diff --git a/ui/components/multichain/asset-picker-amount/asset-picker-modal/AssetList.tsx b/ui/components/multichain/asset-picker-amount/asset-picker-modal/AssetList.tsx index fa071740b51d..612f6d3263a0 100644 --- a/ui/components/multichain/asset-picker-amount/asset-picker-modal/AssetList.tsx +++ b/ui/components/multichain/asset-picker-amount/asset-picker-modal/AssetList.tsx @@ -17,6 +17,7 @@ import { FlexWrap, } from '../../../../helpers/constants/design-system'; import { TokenListItem } from '../..'; +import { getMultichainCurrentChainId } from '../../../../selectors/multichain'; import AssetComponent from './Asset'; import { AssetWithDisplayData, ERC20Asset, NativeAsset } from './types'; @@ -45,6 +46,7 @@ export default function AssetList({ const nativeCurrency = useSelector(getNativeCurrency); const balanceValue = useSelector(getSelectedAccountCachedBalance); const currentCurrency = useSelector(getCurrentCurrency); + const chainId = useSelector(getMultichainCurrentChainId); const [primaryCurrencyValue] = useCurrencyDisplay(balanceValue, { currency: currentCurrency, @@ -73,9 +75,9 @@ export default function AssetList({ ? BackgroundColor.primaryMuted : BackgroundColor.transparent } - className={classnames('multichain-asset-picker-list-item', { - 'multichain-asset-picker-list-item--selected': isSelected, - 'multichain-asset-picker-list-item--disabled': isDisabled, + className={classnames('multichain-asset-picker-list-items', { + 'multichain-asset-picker-list-items--selected': isSelected, + 'multichain-asset-picker-list-items--disabled': isDisabled, })} data-testid="asset-list-item" onClick={() => { @@ -85,13 +87,6 @@ export default function AssetList({ handleAssetChange(token); }} > - {isSelected ? ( - - ) : null} - + {isSelected ? ( + + ) : null} + {token.type === AssetType.native ? ( ) : ( { +export const NftItem = ({ nft, onClick, clickable, badgeWrapperClassname }) => { const testNetworkBackgroundColor = useSelector(getTestNetworkBackgroundColor); const isIpfsEnabled = useSelector(getIpfsGateway); const openSeaEnabled = useSelector(getOpenSeaEnabled); + const { image, imageOriginal, name, tokenId } = nft; + + const ipfsGateway = useSelector(getIpfsGateway); + const currentChain = useSelector(getCurrentNetwork); + const nftImageURL = useGetAssetImageUrl(imageOriginal ?? image, ipfsGateway); + + const nftImageAlt = getNftImageAlt(nft); + const nftSrcUrl = imageOriginal ?? image; + const isIpfsURL = nftSrcUrl?.startsWith('ipfs:'); + const isImageHosted = image?.startsWith('https:'); + const src = isImageHosted ? image : nftImageURL; + const alt = image ? nftImageAlt : ''; + const networkName = currentChain.nickname ?? ''; + const networkSrc = currentChain.rpcPrefs?.imageUrl; + const ipfsImageIsRenderable = isIpfsEnabled && isIpfsURL && src; const openseaImageIsRenderable = openSeaEnabled && src && !isIpfsURL; @@ -105,27 +112,7 @@ NftItem.propTypes = { /** * NFT media source */ - src: PropTypes.string, - /** - * Alt text for the NFT - */ - alt: PropTypes.string.isRequired, - /** - * The NFT's name - */ - name: PropTypes.string, - /** - * Name of the network the NFT lives on - */ - networkName: PropTypes.string.isRequired, - /** - * Image that represents the network - */ - networkSrc: PropTypes.string, - /** - * Token ID of the NFT - */ - tokenId: PropTypes.string.isRequired, + nft: PropTypes.object, /** * Executes when the NFT is clicked */ @@ -137,6 +124,5 @@ NftItem.propTypes = { /** * Whether the src url resolve to ipfs */ - isIpfsURL: PropTypes.bool, badgeWrapperClassname: PropTypes.string, }; diff --git a/ui/components/multichain/nft-item/nft-item.stories.js b/ui/components/multichain/nft-item/nft-item.stories.js index 16b60bae47ff..229239bdd21e 100644 --- a/ui/components/multichain/nft-item/nft-item.stories.js +++ b/ui/components/multichain/nft-item/nft-item.stories.js @@ -27,12 +27,15 @@ export default { }, }, args: { - alt: 'Join Archer and his 6,969 frens as they take a trip further down the rabbit hole in search of a world with vibrant art, great vibes, and psychedelic tales.', - name: 'Monkey Trip #2422', - src: 'https://i.seadn.io/gcs/files/878e670c38e0f02e58bf730c51c30d0c.jpg', - networkName: 'Ethereum Mainnet', - networkSrc: './images/eth_logo.svg', - tokenId: '2422', + nft: { + alt: 'Join Archer and his 6,969 frens as they take a trip further down the rabbit hole in search of a world with vibrant art, great vibes, and psychedelic tales.', + name: 'Monkey Trip #2422', + image: + 'https://i.seadn.io/gcs/files/878e670c38e0f02e58bf730c51c30d0c.jpg', + networkName: 'Ethereum Mainnet', + networkSrc: './images/eth_logo.svg', + tokenId: '2422', + }, }, }; diff --git a/ui/components/multichain/nft-item/nft-item.test.js b/ui/components/multichain/nft-item/nft-item.test.js index 090b8d21b27a..79536ae9ee19 100644 --- a/ui/components/multichain/nft-item/nft-item.test.js +++ b/ui/components/multichain/nft-item/nft-item.test.js @@ -1,9 +1,10 @@ import React from 'react'; -import { fireEvent } from '@testing-library/react'; +import { fireEvent, waitFor } from '@testing-library/react'; import configureStore from '../../../store/store'; import '@testing-library/jest-dom/extend-expect'; import mockState from '../../../../test/data/mock-state.json'; import { renderWithProvider } from '../../../../test/lib/render-helpers'; +import { TokenStandard } from '../../../../shared/constants/transaction'; import { NftItem } from '.'; const store = configureStore(mockState); @@ -22,38 +23,51 @@ describe('NftItem component', () => { })); describe('render', () => { const props = { - alt: 'Test Alt', - backgroundColor: 'red', - name: 'Test NFT', - src: 'test-src', - networkName: 'Test Network', - networkSrc: 'test-network-src', - tokenId: '1', onClick: jest.fn(), - nftImageURL: '', + nft: { + address: '0xAddress', + chainId: '0xaa36a7', + image: 'http://test-src', + name: 'Test NFT', + standard: TokenStandard.ERC721, + tokenId: 'NFT ID', + }, }; - it('renders correctly with an image source', () => { + it('renders correctly with an image source', async () => { const { getByTestId } = renderWithProvider(, store); - expect(getByTestId('nft-item')).toBeInTheDocument(); - expect(getByTestId('nft-network-badge')).toBeInTheDocument(); - expect(getByTestId('nft-image')).toBeInTheDocument(); - expect(getByTestId('nft-image')).toHaveAttribute('src', 'test-src'); + await waitFor(() => { + expect(getByTestId('nft-item')).toBeInTheDocument(); + expect(getByTestId('nft-network-badge')).toBeInTheDocument(); + expect(getByTestId('nft-image')).toBeInTheDocument(); + expect(getByTestId('nft-image')).toHaveAttribute( + 'src', + 'http://test-src', + ); + }); }); - it('renders correctly with default image when both ipfs and display Media is off and no image is provided', () => { + it('renders correctly with default image when both ipfs and display Media is off and no image is provided', async () => { const { getByTestId, queryByTestId } = renderWithProvider( , noDisplayMediaStore, ); - - expect(queryByTestId('nft-image')).not.toBeInTheDocument(); - expect(getByTestId('nft-default-image')).toBeInTheDocument(); + await waitFor(() => { + expect(queryByTestId('nft-image')).not.toBeInTheDocument(); + expect(getByTestId('nft-default-image')).toBeInTheDocument(); + }); }); - it('calls onClick when the NFT image is clicked', () => { - const { getByTestId } = renderWithProvider(, store); + it('calls onClick when the NFT image is clicked', async () => { + const { getByTestId, queryByTestId } = renderWithProvider( + , + store, + ); + + await waitFor(() => { + expect(queryByTestId('nft-image')).toBeInTheDocument(); + }); fireEvent.click(getByTestId('nft-image')); expect(props.onClick).toHaveBeenCalled(); diff --git a/ui/components/multichain/notification-detail-nft/notification-detail-nft.stories.tsx b/ui/components/multichain/notification-detail-nft/notification-detail-nft.stories.tsx index 2ade81a7cb2f..8a8c464b7c41 100644 --- a/ui/components/multichain/notification-detail-nft/notification-detail-nft.stories.tsx +++ b/ui/components/multichain/notification-detail-nft/notification-detail-nft.stories.tsx @@ -4,6 +4,7 @@ import { NotificationDetailNft, NotificationDetailNftProps, } from './notification-detail-nft'; +import { TokenStandard } from '../../../../shared/constants/transaction'; export default { title: @@ -17,11 +18,13 @@ const Template = (args: NotificationDetailNftProps) => ( export const DefaultStory = Template.bind({}); DefaultStory.args = { - tokenSrc: - 'https://i.seadn.io/s/raw/files/a96f90ec8ebf55a2300c66a0c46d6a16.png?w=500&auto=format', - networkSrc: - 'https://token.api.cx.metamask.io/assets/nativeCurrencyLogos/ethereum.svg', - tokenId: 'NFT ID', - tokenName: 'NFT Name', - networkName: 'Ethereum', + nft: { + address: '0xAddress', + chainId: '0x1', + image: + 'https://i.seadn.io/s/raw/files/a96f90ec8ebf55a2300c66a0c46d6a16.png?w=500&auto=format', + name: 'NFT Name', + standard: TokenStandard.ERC1155, + tokenId: 'NFT ID', + }, }; diff --git a/ui/components/multichain/notification-detail-nft/notification-detail-nft.test.tsx b/ui/components/multichain/notification-detail-nft/notification-detail-nft.test.tsx index 231d32431d87..9676dee4f13b 100644 --- a/ui/components/multichain/notification-detail-nft/notification-detail-nft.test.tsx +++ b/ui/components/multichain/notification-detail-nft/notification-detail-nft.test.tsx @@ -3,25 +3,26 @@ import { screen } from '@testing-library/react'; import { renderWithProvider } from '../../../../test/lib/render-helpers'; import configureStore from '../../../store/store'; import mockState from '../../../../test/data/mock-state.json'; +import { TokenStandard } from '../../../../shared/constants/transaction'; import { NotificationDetailNft } from './notification-detail-nft'; describe('NotificationDetailNft', () => { const defaultProps = { - tokenSrc: + address: '0xAddress', + chainId: '0xaa36a7', + image: 'https://i.seadn.io/s/raw/files/a96f90ec8ebf55a2300c66a0c46d6a16.png?w=500&auto=format', - networkSrc: - 'https://token.api.cx.metamask.io/assets/nativeCurrencyLogos/ethereum.svg', + name: 'NFT Name', + standard: TokenStandard.ERC1155, tokenId: 'NFT ID', - tokenName: 'NFT Name', - networkName: 'Ethereum', }; it('renders the NFT image', () => { const store = configureStore(mockState); - renderWithProvider(, store); + renderWithProvider(, store); const images = screen.getAllByRole('img'); const nftImage = images.find( - (img) => img.getAttribute('src') === defaultProps.tokenSrc, + (img) => img.getAttribute('src') === defaultProps.image, ); expect(nftImage).toBeInTheDocument(); }); diff --git a/ui/components/multichain/notification-detail-nft/notification-detail-nft.tsx b/ui/components/multichain/notification-detail-nft/notification-detail-nft.tsx index 881deb00702e..3188f41abb19 100644 --- a/ui/components/multichain/notification-detail-nft/notification-detail-nft.tsx +++ b/ui/components/multichain/notification-detail-nft/notification-detail-nft.tsx @@ -6,32 +6,28 @@ import { Display, JustifyContent, } from '../../../helpers/constants/design-system'; +import { TokenStandard } from '../../../../shared/constants/transaction'; export type NotificationDetailNftProps = { - networkName: string; - networkSrc: string; - tokenName: string; - tokenId: string; - tokenSrc: string; + nft: { + address: string; + chainId: string; + image: string; + name: string; + standard: TokenStandard; + tokenId: string; + }; }; /** * A component that renders a notification detail for an NFT. * * @param props - The component props. - * @param props.networkSrc - The URL of the badge icon. - * @param props.tokenId - The ID of the NFT. - * @param props.tokenName - The name of the NFT. - * @param props.tokenSrc - The URL of the NFT icon. - * @param props.networkName - The name of the network. + * @param props.nft - NFT properties. * @returns The NotificationDetailNft component. */ export const NotificationDetailNft: FC = ({ - networkSrc, - tokenId, - tokenName, - tokenSrc, - networkName, + nft, }) => ( = ({ justifyContent={JustifyContent.center} > - + ); diff --git a/ui/components/multichain/token-list-item/token-list-item.stories.js b/ui/components/multichain/token-list-item/token-list-item.stories.js index bd5d7849bbec..6f6486fbba1a 100644 --- a/ui/components/multichain/token-list-item/token-list-item.stories.js +++ b/ui/components/multichain/token-list-item/token-list-item.stories.js @@ -39,6 +39,7 @@ export default { tokenSymbol: CURRENCY_SYMBOLS.ETH, title: 'Ethereum', isOriginalTokenSymbol: true, + chainId: CHAIN_IDS.MAINNET, }, }; diff --git a/ui/components/multichain/token-list-item/token-list-item.test.tsx b/ui/components/multichain/token-list-item/token-list-item.test.tsx index 73c352910b34..0b56d69be1da 100644 --- a/ui/components/multichain/token-list-item/token-list-item.test.tsx +++ b/ui/components/multichain/token-list-item/token-list-item.test.tsx @@ -69,6 +69,7 @@ describe('TokenListItem', () => { onClick: jest.fn(), tokenImage: '', title: '', + chainId: CHAIN_IDS.MAINNET, }; it('should render correctly', () => { const store = configureMockStore()(state); @@ -99,6 +100,7 @@ describe('TokenListItem', () => { isOriginalTokenSymbol: false, tokenImage: '', title: '', + chainId: CHAIN_IDS.MAINNET, }; const { getByText } = renderWithProvider( , @@ -117,6 +119,7 @@ describe('TokenListItem', () => { tokenImage: '', title: '', tokenSymbol: 'SCAM_TOKEN', + chainId: CHAIN_IDS.MAINNET, }; const { getByTestId, getByText } = renderWithProvider( , @@ -144,6 +147,7 @@ describe('TokenListItem', () => { tokenImage: '', title: '', tokenSymbol: 'SCAM_TOKEN', + chainId: CHAIN_IDS.MAINNET, }; const { getByTestId, getByText } = renderWithProvider( , @@ -171,6 +175,7 @@ describe('TokenListItem', () => { isOriginalTokenSymbol: false, tokenImage: '', title: '', + chainId: CHAIN_IDS.MAINNET, }; const { getByText } = renderWithProvider( diff --git a/ui/components/multichain/token-list-item/token-list-item.tsx b/ui/components/multichain/token-list-item/token-list-item.tsx index 0c3c46114541..b214b257de7d 100644 --- a/ui/components/multichain/token-list-item/token-list-item.tsx +++ b/ui/components/multichain/token-list-item/token-list-item.tsx @@ -44,7 +44,6 @@ import { getDataCollectionForMarketing, } from '../../../selectors'; import { - getMultichainCurrentChainId, getMultichainCurrentNetwork, getMultichainIsEvm, } from '../../../selectors/multichain'; @@ -81,6 +80,7 @@ type TokenListItemProps = { isStakeable?: boolean; address?: string | null; showPercentage?: boolean; + chainId: string; isPrimaryTokenSymbolHidden?: boolean; }; @@ -99,11 +99,11 @@ export const TokenListItem = ({ isStakeable = false, address = null, showPercentage = false, + chainId, }: TokenListItemProps) => { const t = useI18nContext(); const isEvm = useSelector(getMultichainIsEvm); const trackEvent = useContext(MetaMetricsContext); - const chainId = useSelector(getMultichainCurrentChainId); const metaMetricsId = useSelector(getMetaMetricsId); const isMetaMetricsEnabled = useSelector(getParticipateInMetaMetrics); const isMarketingEnabled = useSelector(getDataCollectionForMarketing); diff --git a/ui/pages/asset/asset.tsx b/ui/pages/asset/asset.tsx index 933c39872265..2c2c1ee7729e 100644 --- a/ui/pages/asset/asset.tsx +++ b/ui/pages/asset/asset.tsx @@ -9,6 +9,7 @@ import { getTokens, } from '../../ducks/metamask/metamask'; import { DEFAULT_ROUTE } from '../../helpers/constants/routes'; +import { getMultichainCurrentChainId } from '../../selectors/multichain'; import NativeAsset from './components/native-asset'; import TokenAsset from './components/token-asset'; @@ -18,6 +19,10 @@ const Asset = () => { const nativeCurrency = useSelector(getNativeCurrency); const tokens = useSelector(getTokens); const nfts = useSelector(getNfts); + + // TODO: Remove this when NFTs have a native chainId on their objects + const chainId = useSelector(getMultichainCurrentChainId); + const { asset, id } = useParams<{ asset: string; id: string }>(); const token = tokens.find(({ address }: { address: string }) => @@ -38,7 +43,7 @@ const Asset = () => { let content; if (nft) { - content = ; + content = ; } else if (token) { content = ; } else if (asset === nativeCurrency) { diff --git a/ui/pages/asset/components/asset-page.tsx b/ui/pages/asset/components/asset-page.tsx index c70b60169edb..900a6c8f9e0a 100644 --- a/ui/pages/asset/components/asset-page.tsx +++ b/ui/pages/asset/components/asset-page.tsx @@ -196,6 +196,7 @@ const AssetPage = ({ tokenImage={image} isOriginalTokenSymbol={asset.isOriginalNativeSymbol} isNativeCurrency={true} + chainId={chainId} /> ) : ( )} = { type: 'body_onchain_notification', Image: ({ notification }) => { const chainId = decimalToHex(notification.chain_id); - const { nativeCurrencyLogo, nativeCurrencyName } = - getNetworkDetailsByChainId(`0x${chainId}` as keyof typeof CHAIN_IDS); return ( ); }, diff --git a/ui/pages/notifications/notification-components/erc721-sent-received/erc721-sent-received.tsx b/ui/pages/notifications/notification-components/erc721-sent-received/erc721-sent-received.tsx index 7626691d2027..2506733362d9 100644 --- a/ui/pages/notifications/notification-components/erc721-sent-received/erc721-sent-received.tsx +++ b/ui/pages/notifications/notification-components/erc721-sent-received/erc721-sent-received.tsx @@ -38,6 +38,7 @@ import { BadgeWrapperPosition, IconName, } from '../../../../components/component-library'; +import { TokenStandard } from '../../../../../shared/constants/transaction'; const { TRIGGER_TYPES } = NotificationServicesController.Constants; @@ -112,15 +113,16 @@ export const components: NotificationComponent = { type: 'body_onchain_notification', Image: ({ notification }) => { const chainId = decimalToHex(notification.chain_id); - const { nativeCurrencyLogo, nativeCurrencyName } = - getNetworkDetailsByChainId(`0x${chainId}` as keyof typeof CHAIN_IDS); return ( ); }, diff --git a/ui/pages/swaps/searchable-item-list/__snapshots__/searchable-item-list.test.js.snap b/ui/pages/swaps/searchable-item-list/__snapshots__/searchable-item-list.test.js.snap index f5e215c9fa5e..dbbcfa33fea1 100644 --- a/ui/pages/swaps/searchable-item-list/__snapshots__/searchable-item-list.test.js.snap +++ b/ui/pages/swaps/searchable-item-list/__snapshots__/searchable-item-list.test.js.snap @@ -43,13 +43,30 @@ exports[`SearchableItemList renders the component with initial props 2`] = ` tabindex="0" >
- primaryLabel +
+ primaryLabel +
+
+
+ Ethereum Mainnet logo +
+
- primaryLabel +
+ primaryLabel +
+
+
+ Ethereum Mainnet logo +
+
{iconUrl || primaryLabel ? ( - + + } + marginRight={4} + > + + ) : null} {!(iconUrl || primaryLabel) && identiconAddress ? (