diff --git a/ui/components/app/assets/nfts/nft-details/nft-details.stories.js b/ui/components/app/assets/nfts/nft-details/nft-details.stories.js
index 76554d40d43e..a20eadb7b303 100644
--- a/ui/components/app/assets/nfts/nft-details/nft-details.stories.js
+++ b/ui/components/app/assets/nfts/nft-details/nft-details.stories.js
@@ -1,5 +1,5 @@
import React from 'react';
-import NftDetails from './nft-details';
+import { NftDetailsComponent } from './nft-details';
const nft = {
name: 'Catnip Spicywright',
@@ -18,6 +18,8 @@ const nft = {
},
};
+const nftChainId = '0x1';
+
export default {
title: 'Components/App/NftsDetail',
@@ -28,17 +30,18 @@ export default {
},
args: {
nft,
+ nftChainId,
},
};
export const DefaultStory = (args) => {
- return ;
+ return ;
};
DefaultStory.storyName = 'Default';
export const NoImage = (args) => {
- return ;
+ return ;
};
NoImage.args = {
diff --git a/ui/components/app/assets/nfts/nft-details/nft-details.test.js b/ui/components/app/assets/nfts/nft-details/nft-details.test.js
index e378fb4bb2bf..c93a294ab5a3 100644
--- a/ui/components/app/assets/nfts/nft-details/nft-details.test.js
+++ b/ui/components/app/assets/nfts/nft-details/nft-details.test.js
@@ -1,5 +1,6 @@
import { fireEvent, waitFor } from '@testing-library/react';
import React from 'react';
+import { useParams } from 'react-router-dom';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import copyToClipboard from 'copy-to-clipboard';
@@ -39,6 +40,7 @@ jest.mock('react-router-dom', () => ({
useHistory: () => ({
push: mockHistoryPush,
}),
+ useParams: jest.fn(),
}));
jest.mock('../../../../../ducks/send/index.js', () => ({
@@ -72,6 +74,7 @@ describe('NFT Details', () => {
});
it('should match minimal props and state snapshot', async () => {
+ useParams.mockReturnValue({ chainId: CHAIN_IDS.GOERLI });
getAssetImageURL.mockResolvedValue(
'https://bafybeiclzx7zfjvuiuwobn5ip3ogc236bjqfjzoblumf4pau4ep6dqramu.ipfs.dweb.link',
);
@@ -88,6 +91,7 @@ describe('NFT Details', () => {
});
it(`should route to '/' route when the back button is clicked`, () => {
+ useParams.mockReturnValue({ chainId: CHAIN_IDS.MAINNET });
const { queryByTestId } = renderWithProvider(
,
mockStore,
@@ -101,6 +105,7 @@ describe('NFT Details', () => {
});
it(`should call removeAndIgnoreNFT with proper nft details and route to '/' when removing nft`, async () => {
+ useParams.mockReturnValue({ chainId: CHAIN_IDS.MAINNET });
const { queryByTestId } = renderWithProvider(
,
mockStore,
@@ -122,6 +127,7 @@ describe('NFT Details', () => {
});
it(`should call setRemoveNftMessage with error when removeAndIgnoreNft fails and route to '/'`, async () => {
+ useParams.mockReturnValue({ chainId: CHAIN_IDS.MAINNET });
const { queryByTestId } = renderWithProvider(
,
mockStore,
@@ -145,6 +151,7 @@ describe('NFT Details', () => {
});
it('should copy nft address', async () => {
+ useParams.mockReturnValue({ chainId: CHAIN_IDS.MAINNET });
const { queryByTestId } = renderWithProvider(
,
mockStore,
@@ -157,6 +164,7 @@ describe('NFT Details', () => {
});
it('should navigate to draft transaction send route with ERC721 data', async () => {
+ useParams.mockReturnValue({ chainId: CHAIN_IDS.MAINNET });
const nftProps = {
nft: nfts[5],
};
@@ -180,6 +188,7 @@ describe('NFT Details', () => {
});
it('should not render send button if isCurrentlyOwned is false', () => {
+ useParams.mockReturnValue({ chainId: CHAIN_IDS.MAINNET });
const sixthNftProps = {
nft: nfts[6],
};
@@ -195,6 +204,7 @@ describe('NFT Details', () => {
});
it('should render send button if it is an ERC1155', () => {
+ useParams.mockReturnValue({ chainId: CHAIN_IDS.MAINNET });
const nftProps = {
nft: nfts[1],
};
@@ -211,6 +221,7 @@ describe('NFT Details', () => {
describe(`Alternative Networks' OpenSea Links`, () => {
it('should open opeasea link with goeli testnet chainId', async () => {
+ useParams.mockReturnValue({ chainId: CHAIN_IDS.GOERLI });
global.platform = { openTab: jest.fn() };
const { queryByTestId } = renderWithProvider(
@@ -234,6 +245,7 @@ describe('NFT Details', () => {
});
it('should open tab to mainnet opensea url with nft info', async () => {
+ useParams.mockReturnValue({ chainId: CHAIN_IDS.MAINNET });
global.platform = { openTab: jest.fn() };
const mainnetState = {
@@ -266,11 +278,15 @@ describe('NFT Details', () => {
});
it('should open tab to polygon opensea url with nft info', async () => {
+ useParams.mockReturnValue({ chainId: CHAIN_IDS.POLYGON });
const polygonState = {
...mockState,
metamask: {
...mockState.metamask,
- ...mockNetworkState({ chainId: CHAIN_IDS.POLYGON }),
+ ...mockNetworkState({
+ chainId: CHAIN_IDS.POLYGON,
+ nickname: 'polygon',
+ }),
},
};
const polygonMockStore = configureMockStore([thunk])(polygonState);
@@ -296,11 +312,15 @@ describe('NFT Details', () => {
});
it('should open tab to sepolia opensea url with nft info', async () => {
+ useParams.mockReturnValue({ chainId: CHAIN_IDS.SEPOLIA });
const sepoliaState = {
...mockState,
metamask: {
...mockState.metamask,
- ...mockNetworkState({ chainId: CHAIN_IDS.SEPOLIA }),
+ ...mockNetworkState({
+ chainId: CHAIN_IDS.SEPOLIA,
+ nickname: 'sepolia',
+ }),
},
};
const sepoliaMockStore = configureMockStore([thunk])(sepoliaState);
@@ -326,6 +346,7 @@ describe('NFT Details', () => {
});
it('should not render opensea redirect button', async () => {
+ useParams.mockReturnValue({ chainId: '0x99' });
const randomNetworkState = {
...mockState,
metamask: {
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 a0de608a0e56..28e7ea9f0f95 100644
--- a/ui/components/app/assets/nfts/nft-details/nft-details.tsx
+++ b/ui/components/app/assets/nfts/nft-details/nft-details.tsx
@@ -1,9 +1,10 @@
import React, { useEffect, useContext } from 'react';
import { useDispatch, useSelector } from 'react-redux';
-import { useHistory } from 'react-router-dom';
+import { useHistory, useParams } from 'react-router-dom';
import { isEqual } from 'lodash';
import { getTokenTrackerLink, getAccountLink } from '@metamask/etherscan-link';
import { Nft } from '@metamask/assets-controllers';
+import { Hex } from '@metamask/utils';
import {
TextColor,
IconColor,
@@ -19,8 +20,15 @@ import {
import { useI18nContext } from '../../../../../hooks/useI18nContext';
import { shortenAddress } from '../../../../../helpers/utils/util';
import { getNftImageAlt } from '../../../../../helpers/utils/nfts';
-import { getCurrentChainId } from '../../../../../../shared/modules/selectors/networks';
-import { getCurrentNetwork, getIpfsGateway } from '../../../../../selectors';
+import {
+ getCurrentChainId,
+ getNetworkConfigurationsByChainId,
+} from '../../../../../../shared/modules/selectors/networks';
+import {
+ getCurrentNetwork,
+ getIpfsGateway,
+ getNetworkConfigurationIdByChainId,
+} from '../../../../../selectors';
import {
ASSET_ROUTE,
DEFAULT_ROUTE,
@@ -31,6 +39,8 @@ import {
removeAndIgnoreNft,
setRemoveNftMessage,
setNewNftAddedMessage,
+ setActiveNetworkWithError,
+ setSwitchedNetworkDetails,
} from '../../../../../store/actions';
import { CHAIN_IDS } from '../../../../../../shared/constants/network';
import NftOptions from '../nft-options/nft-options';
@@ -71,13 +81,20 @@ import { Numeric } from '../../../../../../shared/modules/Numeric';
// eslint-disable-next-line import/no-restricted-paths
import { addUrlProtocolPrefix } from '../../../../../../app/scripts/lib/util';
import useGetAssetImageUrl from '../../../../../hooks/useGetAssetImageUrl';
+import { getImageForChainId } from '../../../../../selectors/multichain';
import NftDetailInformationRow from './nft-detail-information-row';
import NftDetailInformationFrame from './nft-detail-information-frame';
import NftDetailDescription from './nft-detail-description';
const MAX_TOKEN_ID_LENGTH = 15;
-export default function NftDetails({ nft }: { nft: Nft }) {
+export function NftDetailsComponent({
+ nft,
+ nftChainId,
+}: {
+ nft: Nft;
+ nftChainId: string;
+}) {
const {
image,
imageOriginal,
@@ -104,6 +121,14 @@ export default function NftDetails({ nft }: { nft: Nft }) {
const currency = useSelector(getCurrentCurrency);
const selectedNativeConversionRate = useSelector(getConversionRate);
+ const nftNetworkConfigs = useSelector(getNetworkConfigurationsByChainId);
+ const nftChainNetwork = nftNetworkConfigs[nftChainId as Hex];
+ const nftChainImage = getImageForChainId(nftChainId as string);
+ const networks = useSelector(getNetworkConfigurationIdByChainId) as Record<
+ string,
+ string
+ >;
+
const [addressCopied, handleAddressCopy] = useCopyToClipboard();
const nftImageAlt = getNftImageAlt(nft);
@@ -240,7 +265,28 @@ export default function NftDetails({ nft }: { nft: Nft }) {
const sendDisabled =
standard !== TokenStandard.ERC721 && standard !== TokenStandard.ERC1155;
+ const setCorrectChain = async () => {
+ // If we aren't presently on the chain of the nft, change to it
+ if (nftChainId !== currentChain.chainId) {
+ try {
+ const networkConfigurationId = networks[nftChainId as Hex];
+ await dispatch(setActiveNetworkWithError(networkConfigurationId));
+ await dispatch(
+ setSwitchedNetworkDetails({
+ networkClientId: networkConfigurationId,
+ }),
+ );
+ } catch (err) {
+ console.error(`Failed to switch chains for NFT.
+ Target chainId: ${nftChainId}, Current chainId: ${currentChain.chainId}.
+ ${err}`);
+ throw err;
+ }
+ }
+ };
+
const onSend = async () => {
+ await setCorrectChain();
await dispatch(
startNewDraftTransaction({
type: AssetType.NFT,
@@ -350,8 +396,8 @@ export default function NftDetails({ nft }: { nft: Nft }) {
);
}
+
+function NftDetails({ nft }: { nft: Nft }) {
+ const { chainId } = useParams();
+
+ return ;
+}
+
+export default NftDetails;