diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index 0cceb966d5db..e04e4efcd46a 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -591,28 +591,24 @@ export default class MetamaskController extends EventEmitter {
state: initState.TokenListController,
});
- this.assetsContractController = new AssetsContractController(
- {
- chainId: getCurrentChainId({ metamask: this.networkController.state }),
- onPreferencesStateChange: (listener) =>
- this.preferencesController.store.subscribe(listener),
- onNetworkDidChange: (cb) =>
- networkControllerMessenger.subscribe(
- 'NetworkController:networkDidChange',
- () => {
- const networkState = this.networkController.state;
- return cb(networkState);
- },
- ),
- getNetworkClientById: this.networkController.getNetworkClientById.bind(
- this.networkController,
- ),
- },
- {
- provider: this.provider,
- },
- initState.AssetsContractController,
- );
+ const assetsContractControllerMessenger =
+ this.controllerMessenger.getRestricted({
+ name: 'AssetsContractController',
+ allowedActions: [
+ 'NetworkController:getNetworkClientById',
+ 'NetworkController:getNetworkConfigurationByNetworkClientId',
+ 'NetworkController:getSelectedNetworkClient',
+ 'NetworkController:getState',
+ ],
+ allowedEvents: [
+ 'PreferencesController:stateChange',
+ 'NetworkController:networkDidChange',
+ ],
+ });
+ this.assetsContractController = new AssetsContractController({
+ messenger: assetsContractControllerMessenger,
+ chainId: getCurrentChainId({ metamask: this.networkController.state }),
+ });
const tokensControllerMessenger = this.controllerMessenger.getRestricted({
name: 'TokensController',
@@ -648,32 +644,18 @@ export default class MetamaskController extends EventEmitter {
`${this.networkController.name}:getNetworkClientById`,
'AccountsController:getSelectedAccount',
'AccountsController:getAccount',
+ 'AssetsContractController:getERC721AssetName',
+ 'AssetsContractController:getERC721AssetSymbol',
+ 'AssetsContractController:getERC721TokenURI',
+ 'AssetsContractController:getERC721OwnerOf',
+ 'AssetsContractController:getERC1155BalanceOf',
+ 'AssetsContractController:getERC1155TokenURI',
],
});
this.nftController = new NftController({
state: initState.NftController,
messenger: nftControllerMessenger,
chainId: getCurrentChainId({ metamask: this.networkController.state }),
- getERC721AssetName: this.assetsContractController.getERC721AssetName.bind(
- this.assetsContractController,
- ),
- getERC721AssetSymbol:
- this.assetsContractController.getERC721AssetSymbol.bind(
- this.assetsContractController,
- ),
- getERC721TokenURI: this.assetsContractController.getERC721TokenURI.bind(
- this.assetsContractController,
- ),
- getERC721OwnerOf: this.assetsContractController.getERC721OwnerOf.bind(
- this.assetsContractController,
- ),
- getERC1155BalanceOf:
- this.assetsContractController.getERC1155BalanceOf.bind(
- this.assetsContractController,
- ),
- getERC1155TokenURI: this.assetsContractController.getERC1155TokenURI.bind(
- this.assetsContractController,
- ),
onNftAdded: ({ address, symbol, tokenId, standard, source }) =>
this.metaMetricsController.trackEvent({
event: MetaMetricsEventName.NftAdded,
diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json
index 304da136a56f..e9711e5467ab 100644
--- a/lavamoat/browserify/beta/policy.json
+++ b/lavamoat/browserify/beta/policy.json
@@ -76,7 +76,15 @@
"TextEncoder": true
},
"packages": {
- "@metamask/assets-controllers>multiformats": true
+ "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": true
+ }
+ },
+ "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": {
+ "globals": {
+ "TextDecoder": true,
+ "TextEncoder": true,
+ "console.warn": true,
+ "crypto.subtle.digest": true
}
},
"@ensdomains/content-hash>multihashes": {
@@ -693,13 +701,13 @@
"setTimeout": true
},
"packages": {
+ "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": true,
"@ethereumjs/tx>@ethereumjs/util": true,
"@ethersproject/contracts": true,
"@ethersproject/providers": true,
"@metamask/abi-utils": true,
"@metamask/assets-controllers>@metamask/base-controller": true,
"@metamask/assets-controllers>@metamask/polling-controller": true,
- "@metamask/assets-controllers>multiformats": true,
"@metamask/contract-metadata": true,
"@metamask/controller-utils": true,
"@metamask/eth-query": true,
@@ -735,14 +743,6 @@
"uuid": true
}
},
- "@metamask/assets-controllers>multiformats": {
- "globals": {
- "TextDecoder": true,
- "TextEncoder": true,
- "console.warn": true,
- "crypto.subtle.digest": true
- }
- },
"@metamask/base-controller": {
"globals": {
"setTimeout": true
diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json
index 304da136a56f..e9711e5467ab 100644
--- a/lavamoat/browserify/flask/policy.json
+++ b/lavamoat/browserify/flask/policy.json
@@ -76,7 +76,15 @@
"TextEncoder": true
},
"packages": {
- "@metamask/assets-controllers>multiformats": true
+ "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": true
+ }
+ },
+ "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": {
+ "globals": {
+ "TextDecoder": true,
+ "TextEncoder": true,
+ "console.warn": true,
+ "crypto.subtle.digest": true
}
},
"@ensdomains/content-hash>multihashes": {
@@ -693,13 +701,13 @@
"setTimeout": true
},
"packages": {
+ "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": true,
"@ethereumjs/tx>@ethereumjs/util": true,
"@ethersproject/contracts": true,
"@ethersproject/providers": true,
"@metamask/abi-utils": true,
"@metamask/assets-controllers>@metamask/base-controller": true,
"@metamask/assets-controllers>@metamask/polling-controller": true,
- "@metamask/assets-controllers>multiformats": true,
"@metamask/contract-metadata": true,
"@metamask/controller-utils": true,
"@metamask/eth-query": true,
@@ -735,14 +743,6 @@
"uuid": true
}
},
- "@metamask/assets-controllers>multiformats": {
- "globals": {
- "TextDecoder": true,
- "TextEncoder": true,
- "console.warn": true,
- "crypto.subtle.digest": true
- }
- },
"@metamask/base-controller": {
"globals": {
"setTimeout": true
diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json
index 304da136a56f..e9711e5467ab 100644
--- a/lavamoat/browserify/main/policy.json
+++ b/lavamoat/browserify/main/policy.json
@@ -76,7 +76,15 @@
"TextEncoder": true
},
"packages": {
- "@metamask/assets-controllers>multiformats": true
+ "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": true
+ }
+ },
+ "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": {
+ "globals": {
+ "TextDecoder": true,
+ "TextEncoder": true,
+ "console.warn": true,
+ "crypto.subtle.digest": true
}
},
"@ensdomains/content-hash>multihashes": {
@@ -693,13 +701,13 @@
"setTimeout": true
},
"packages": {
+ "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": true,
"@ethereumjs/tx>@ethereumjs/util": true,
"@ethersproject/contracts": true,
"@ethersproject/providers": true,
"@metamask/abi-utils": true,
"@metamask/assets-controllers>@metamask/base-controller": true,
"@metamask/assets-controllers>@metamask/polling-controller": true,
- "@metamask/assets-controllers>multiformats": true,
"@metamask/contract-metadata": true,
"@metamask/controller-utils": true,
"@metamask/eth-query": true,
@@ -735,14 +743,6 @@
"uuid": true
}
},
- "@metamask/assets-controllers>multiformats": {
- "globals": {
- "TextDecoder": true,
- "TextEncoder": true,
- "console.warn": true,
- "crypto.subtle.digest": true
- }
- },
"@metamask/base-controller": {
"globals": {
"setTimeout": true
diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json
index 756f31eaeb55..ba37c2883157 100644
--- a/lavamoat/browserify/mmi/policy.json
+++ b/lavamoat/browserify/mmi/policy.json
@@ -76,7 +76,15 @@
"TextEncoder": true
},
"packages": {
- "@metamask/assets-controllers>multiformats": true
+ "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": true
+ }
+ },
+ "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": {
+ "globals": {
+ "TextDecoder": true,
+ "TextEncoder": true,
+ "console.warn": true,
+ "crypto.subtle.digest": true
}
},
"@ensdomains/content-hash>multihashes": {
@@ -785,13 +793,13 @@
"setTimeout": true
},
"packages": {
+ "@ensdomains/content-hash>multicodec>uint8arrays>multiformats": true,
"@ethereumjs/tx>@ethereumjs/util": true,
"@ethersproject/contracts": true,
"@ethersproject/providers": true,
"@metamask/abi-utils": true,
"@metamask/assets-controllers>@metamask/base-controller": true,
"@metamask/assets-controllers>@metamask/polling-controller": true,
- "@metamask/assets-controllers>multiformats": true,
"@metamask/contract-metadata": true,
"@metamask/controller-utils": true,
"@metamask/eth-query": true,
@@ -827,14 +835,6 @@
"uuid": true
}
},
- "@metamask/assets-controllers>multiformats": {
- "globals": {
- "TextDecoder": true,
- "TextEncoder": true,
- "console.warn": true,
- "crypto.subtle.digest": true
- }
- },
"@metamask/base-controller": {
"globals": {
"setTimeout": true
diff --git a/package.json b/package.json
index 7605e5042066..99ed1e060cf5 100644
--- a/package.json
+++ b/package.json
@@ -304,7 +304,7 @@
"@metamask/address-book-controller": "^5.0.0",
"@metamask/announcement-controller": "^7.0.0",
"@metamask/approval-controller": "^7.0.0",
- "@metamask/assets-controllers": "^36.0.0",
+ "@metamask/assets-controllers": "^37.0.0",
"@metamask/base-controller": "^7.0.0",
"@metamask/bitcoin-wallet-snap": "^0.6.0",
"@metamask/browser-passworder": "^4.3.0",
diff --git a/ui/components/app/assets/nfts/nft-details/__snapshots__/nft-details.test.js.snap b/ui/components/app/assets/nfts/nft-details/__snapshots__/nft-details.test.js.snap
index d03df016c1b3..0a025bc47ff0 100644
--- a/ui/components/app/assets/nfts/nft-details/__snapshots__/nft-details.test.js.snap
+++ b/ui/components/app/assets/nfts/nft-details/__snapshots__/nft-details.test.js.snap
@@ -1,6 +1,189 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`NFT Details should match minimal props and state snapshot 1`] = `
+
+
+
+
+
+
+
+
+
+ MUNK #1
+
+
+
+
+
+
+ Contract address
+
+
+
+
+ 0xDc738...06414
+
+
+
+
+
+
+
+
+
+
+ Token standard
+
+
+ ERC721
+
+
+
+
+
+
+
+ Disclaimer: MetaMask pulls the media file from the source url. This url sometimes gets changed by the marketplace on which the NFT was minted.
+
+
+
+
+
+
+
+`;
+
+exports[`NFT Details should match minimal props and state snapshot 2`] = `
`;
+
+exports[`NFT Details should match minimal props and state snapshot 3`] = `
`;
diff --git a/ui/components/app/assets/nfts/nft-details/__snapshots__/nft-full-image.test.js.snap b/ui/components/app/assets/nfts/nft-details/__snapshots__/nft-full-image.test.js.snap
new file mode 100644
index 000000000000..dfedee737c93
--- /dev/null
+++ b/ui/components/app/assets/nfts/nft-details/__snapshots__/nft-full-image.test.js.snap
@@ -0,0 +1,84 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`NFT full image should match snapshot 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`NFT full image should match snapshot 2`] = `
`;
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 65c3ba339e5f..350dc4813c6a 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
@@ -19,8 +19,17 @@ import {
} from '../../../../../store/actions';
import { CHAIN_IDS } from '../../../../../../shared/constants/network';
import { mockNetworkState } from '../../../../../../test/stub/networks';
+import {
+ getAssetImageURL,
+ shortenAddress,
+} from '../../../../../helpers/utils/util';
import NftDetails from './nft-details';
+jest.mock('../../../../../helpers/utils/util', () => ({
+ getAssetImageURL: jest.fn(),
+ shortenAddress: jest.fn(),
+}));
+
jest.mock('copy-to-clipboard');
const mockHistoryPush = jest.fn();
@@ -62,13 +71,20 @@ describe('NFT Details', () => {
jest.clearAllMocks();
});
- it('should match minimal props and state snapshot', () => {
+ it('should match minimal props and state snapshot', async () => {
+ getAssetImageURL.mockResolvedValue(
+ 'https://bafybeiclzx7zfjvuiuwobn5ip3ogc236bjqfjzoblumf4pau4ep6dqramu.ipfs.dweb.link',
+ );
+ shortenAddress.mockReturnValue('0xDc738...06414');
+
const { container } = renderWithProvider(
,
mockStore,
);
- expect(container).toMatchSnapshot();
+ await waitFor(() => {
+ expect(container).toMatchSnapshot();
+ });
});
it(`should route to '/' route when the back button is clicked`, () => {
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 0064dc38976c..8a857da43989 100644
--- a/ui/components/app/assets/nfts/nft-details/nft-details.tsx
+++ b/ui/components/app/assets/nfts/nft-details/nft-details.tsx
@@ -18,10 +18,7 @@ import {
AlignItems,
} from '../../../../../helpers/constants/design-system';
import { useI18nContext } from '../../../../../hooks/useI18nContext';
-import {
- getAssetImageURL,
- shortenAddress,
-} from '../../../../../helpers/utils/util';
+import { shortenAddress } from '../../../../../helpers/utils/util';
import { getNftImageAlt } from '../../../../../helpers/utils/nfts';
import {
getCurrentChainId,
@@ -73,6 +70,7 @@ import { SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP } from '../../../../../../
import { getConversionRate } from '../../../../../ducks/metamask/metamask';
import { Numeric } from '../../../../../../shared/modules/Numeric';
import { addUrlProtocolPrefix } from '../../../../../../app/scripts/lib/util';
+import useGetAssetImageUrl from '../../../../../hooks/useGetAssetImageUrl';
import NftDetailInformationRow from './nft-detail-information-row';
import NftDetailInformationFrame from './nft-detail-information-frame';
import NftDetailDescription from './nft-detail-description';
@@ -110,9 +108,10 @@ export default function NftDetails({ nft }: { nft: Nft }) {
const nftImageAlt = getNftImageAlt(nft);
const nftSrcUrl = imageOriginal ?? image;
- const nftImageURL = getAssetImageURL(imageOriginal ?? image, ipfsGateway);
const isIpfsURL = nftSrcUrl?.startsWith('ipfs:');
- const isImageHosted = image?.startsWith('https:');
+ const isImageHosted =
+ image?.startsWith('https:') || image?.startsWith('http:');
+ const nftImageURL = useGetAssetImageUrl(imageOriginal ?? image, ipfsGateway);
const hasFloorAskPrice = Boolean(
collection?.floorAsk?.price?.amount?.usd &&
@@ -165,6 +164,7 @@ export default function NftDetails({ nft }: { nft: Nft }) {
};
const { chainId } = currentChain;
+
useEffect(() => {
trackEvent({
event: MetaMetricsEventName.NftDetailsOpened,
diff --git a/ui/components/app/assets/nfts/nft-details/nft-full-image.test.js b/ui/components/app/assets/nfts/nft-details/nft-full-image.test.js
new file mode 100644
index 000000000000..193025a402ac
--- /dev/null
+++ b/ui/components/app/assets/nfts/nft-details/nft-full-image.test.js
@@ -0,0 +1,45 @@
+import { waitFor } from '@testing-library/react';
+import React from 'react';
+import configureMockStore from 'redux-mock-store';
+import thunk from 'redux-thunk';
+import { toHex } from '@metamask/controller-utils';
+import { renderWithProvider } from '../../../../../../test/lib/render-helpers';
+import mockState from '../../../../../../test/data/mock-state.json';
+import NftFullImage from './nft-full-image';
+
+const selectedAddress =
+ mockState.metamask.internalAccounts.accounts[
+ mockState.metamask.internalAccounts.selectedAccount
+ ].address;
+const nfts = mockState.metamask.allNfts[selectedAddress][toHex(5)];
+const mockAsset = nfts[0].address;
+const mockId = nfts[0].tokenId;
+jest.mock('react-router-dom', () => {
+ const original = jest.requireActual('react-router-dom');
+ return {
+ ...original,
+ useHistory: () => ({
+ push: jest.fn(),
+ }),
+ useParams: () => ({
+ asset: mockAsset,
+ id: mockId,
+ }),
+ };
+});
+
+describe('NFT full image', () => {
+ const mockStore = configureMockStore([thunk])(mockState);
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should match snapshot', async () => {
+ const { container } = renderWithProvider(
, mockStore);
+
+ await waitFor(() => {
+ expect(container).toMatchSnapshot();
+ });
+ });
+});
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 3d09cba1ddc4..64e2a3191c0e 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,7 +1,6 @@
import React, { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { useHistory, useParams } from 'react-router-dom';
-import { getAssetImageURL } from '../../../../../helpers/utils/util';
import { getNftImageAlt } from '../../../../../helpers/utils/nfts';
import { getCurrentNetwork, getIpfsGateway } from '../../../../../selectors';
@@ -23,6 +22,7 @@ import {
} from '../../../../../helpers/constants/design-system';
import { useI18nContext } from '../../../../../hooks/useI18nContext';
import { ASSET_ROUTE } from '../../../../../helpers/constants/routes';
+import useGetAssetImageUrl from '../../../../../hooks/useGetAssetImageUrl';
export default function NftFullImage() {
const t = useI18nContext();
@@ -37,10 +37,10 @@ export default function NftFullImage() {
const ipfsGateway = useSelector(getIpfsGateway);
const currentChain = useSelector(getCurrentNetwork);
+ const nftImageURL = useGetAssetImageUrl(imageOriginal ?? image, ipfsGateway);
const nftImageAlt = getNftImageAlt(nft);
const nftSrcUrl = imageOriginal ?? image;
- const nftImageURL = getAssetImageURL(imageOriginal ?? image, ipfsGateway);
const isIpfsURL = nftSrcUrl?.startsWith('ipfs:');
const isImageHosted = image?.startsWith('https:');
const history = useHistory();
diff --git a/ui/components/app/assets/nfts/nfts-items/collection-image.component.test.tsx b/ui/components/app/assets/nfts/nfts-items/collection-image.component.test.tsx
new file mode 100644
index 000000000000..726ca26508b9
--- /dev/null
+++ b/ui/components/app/assets/nfts/nfts-items/collection-image.component.test.tsx
@@ -0,0 +1,86 @@
+import React from 'react';
+import { useSelector } from 'react-redux';
+import configureMockStore from 'redux-mock-store';
+import thunk from 'redux-thunk';
+import { screen } from '@testing-library/react';
+import mockState from '../../../../../../test/data/mock-state.json';
+import { renderWithProvider } from '../../../../../../test/lib/render-helpers';
+import { getIpfsGateway, getOpenSeaEnabled } from '../../../../../selectors';
+import { CollectionImageComponent } from './collection-image.component';
+
+jest.mock('react-redux', () => ({
+ ...jest.requireActual('react-redux'),
+ useSelector: jest.fn(),
+}));
+
+jest.mock('../../../../../selectors', () => ({
+ ...jest.requireActual('../../../../../selectors'),
+ getIpfsGateway: jest.fn(),
+ getOpenSeaEnabled: jest.fn(),
+}));
+const mockStore = configureMockStore([thunk])(mockState);
+describe('CollectionImageComponent', () => {
+ const useSelectorMock = useSelector as jest.Mock;
+ beforeEach(() => {
+ jest.resetAllMocks();
+ });
+ it('should show collection first letter when ipfs is not enabled', async () => {
+ useSelectorMock.mockImplementation((selector) => {
+ if (selector === getIpfsGateway) {
+ return undefined;
+ }
+ return undefined;
+ });
+
+ const props = {
+ collectionName: 'NFT Collection',
+ collectionImage: 'ipfs://',
+ };
+
+ const { getByText } = renderWithProvider(
+
,
+ mockStore,
+ );
+
+ expect(getByText('N')).toBeInTheDocument();
+ });
+
+ it('should show collection first letter when opensea is not enabled', async () => {
+ useSelectorMock.mockImplementation((selector) => {
+ if (selector === getOpenSeaEnabled) {
+ return false;
+ }
+ return undefined;
+ });
+
+ const props = {
+ collectionName: 'Test NFT Collection',
+ collectionImage: 'https://image.png',
+ };
+
+ const { getByText } = renderWithProvider(
+
,
+ mockStore,
+ );
+
+ expect(getByText('T')).toBeInTheDocument();
+ });
+
+ it('should show collection image', async () => {
+ useSelectorMock.mockImplementation((selector) => {
+ if (selector === getOpenSeaEnabled) {
+ return true;
+ }
+ return undefined;
+ });
+
+ const props = {
+ collectionName: 'Test NFT Collection',
+ collectionImage: 'https://image.png',
+ };
+
+ renderWithProvider(
, mockStore);
+
+ expect(screen.getAllByRole('img')).toHaveLength(1);
+ });
+});
diff --git a/ui/components/app/assets/nfts/nfts-items/collection-image.component.tsx b/ui/components/app/assets/nfts/nfts-items/collection-image.component.tsx
new file mode 100644
index 000000000000..24f34714dd95
--- /dev/null
+++ b/ui/components/app/assets/nfts/nfts-items/collection-image.component.tsx
@@ -0,0 +1,53 @@
+import React from 'react';
+
+import { useSelector } from 'react-redux';
+
+import { getIpfsGateway, getOpenSeaEnabled } from '../../../../../selectors';
+import useGetAssetImageUrl from '../../../../../hooks/useGetAssetImageUrl';
+import { Box } from '../../../../component-library';
+
+export const CollectionImageComponent = ({
+ collectionImage,
+ collectionName,
+}: {
+ collectionImage: string;
+ collectionName: string;
+}) => {
+ const ipfsGateway = useSelector(getIpfsGateway);
+ const openSeaEnabled = useSelector(getOpenSeaEnabled);
+ const nftImageURL = useGetAssetImageUrl(collectionImage, ipfsGateway);
+
+ const renderCollectionImage = () => {
+ if (collectionImage?.startsWith('ipfs') && !ipfsGateway) {
+ return (
+
+ {collectionName?.[0]?.toUpperCase() ?? null}
+
+ );
+ }
+ if (!openSeaEnabled && !collectionImage?.startsWith('ipfs')) {
+ return (
+
+ {collectionName?.[0]?.toUpperCase() ?? null}
+
+ );
+ }
+
+ if (collectionImage) {
+ return (
+
+ );
+ }
+ return (
+
+ {collectionName?.[0]?.toUpperCase() ?? null}
+
+ );
+ };
+
+ return
{renderCollectionImage()} ;
+};
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 9e8196669b0e..c44de72b261b 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 } from 'react';
+import React, { useContext, useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { useDispatch, useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
@@ -22,7 +22,6 @@ import {
getIpfsGateway,
getSelectedInternalAccount,
getCurrentNetwork,
- getOpenSeaEnabled,
} from '../../../../../selectors';
import {
ASSET_ROUTE,
@@ -46,6 +45,8 @@ import {
MetaMetricsEventCategory,
MetaMetricsEventName,
} from '../../../../../../shared/constants/metametrics';
+import { isEqualCaseInsensitive } from '../../../../../../shared/modules/string-utils';
+import { CollectionImageComponent } from './collection-image.component';
const width = (isModal) => {
const env = getEnvironmentType() === ENVIRONMENT_TYPE_POPUP;
@@ -78,7 +79,8 @@ export default function NftsItems({
const currentChain = useSelector(getCurrentNetwork);
const t = useI18nContext();
const ipfsGateway = useSelector(getIpfsGateway);
- const openSeaEnabled = useSelector(getOpenSeaEnabled);
+
+ const [updatedNfts, setUpdatedNfts] = useState([]);
const trackEvent = useContext(MetaMetricsContext);
const sendAnalytics = useSelector(getSendAnalyticProperties);
@@ -116,39 +118,40 @@ export default function NftsItems({
dispatch,
]);
- const history = useHistory();
+ const getAssetImageUrlAndUpdate = async (image, nft) => {
+ const nftImage = await getAssetImageURL(image, ipfsGateway);
+ const updatedNFt = {
+ ...nft,
+ ipfsImageUpdated: nftImage,
+ };
+ return updatedNFt;
+ };
- const renderCollectionImage = (collectionImage, collectionName) => {
- if (collectionImage?.startsWith('ipfs') && !ipfsGateway) {
- return (
-
- {collectionName?.[0]?.toUpperCase() ?? null}
-
- );
- }
- if (!openSeaEnabled && !collectionImage?.startsWith('ipfs')) {
- return (
-
- {collectionName?.[0]?.toUpperCase() ?? null}
-
- );
- }
+ useEffect(() => {
+ const promisesArr = [];
+ const modifyItems = async () => {
+ for (const key of collectionsKeys) {
+ const { nfts } = collections[key];
+ for (const singleNft of nfts) {
+ const { image, imageOriginal } = singleNft;
- if (collectionImage) {
- return (
-
- );
- }
- return (
-
- {collectionName?.[0]?.toUpperCase() ?? null}
-
- );
- };
+ const isImageHosted =
+ image?.startsWith('https:') || image?.startsWith('http:');
+ if (!isImageHosted) {
+ promisesArr.push(
+ getAssetImageUrlAndUpdate(imageOriginal ?? image, singleNft),
+ );
+ }
+ }
+ }
+ const settled = await Promise.all(promisesArr);
+ setUpdatedNfts(settled);
+ };
+
+ modifyItems();
+ }, []);
+
+ const history = useHistory();
const updateNftDropDownStateKey = (key, isExpanded) => {
const newCurrentAccountState = {
@@ -198,6 +201,19 @@ 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 (
@@ -220,7 +236,10 @@ export default function NftsItems({
alignItems={AlignItems.center}
className="nfts-items__collection-header"
>
- {renderCollectionImage(collectionImage, collectionName)}
+
{
const { image, address, tokenId, name, imageOriginal, tokenURI } =
nft;
- const nftImage = getAssetImageURL(
- imageOriginal ?? image,
- ipfsGateway,
- );
const nftImageAlt = getNftImageAlt(nft);
- const isImageHosted = image?.startsWith('https:');
- const nftImageURL = imageOriginal?.startsWith('ipfs')
- ? nftImage
- : image;
+ const isImageHosted =
+ image?.startsWith('https:') || image?.startsWith('http:');
+
+ const source = getSource(isImageHosted, nft);
+
const isIpfsURL = (
imageOriginal ??
image ??
@@ -271,9 +287,8 @@ export default function NftsItems({
className="nfts-items__item-wrapper"
>
{
@@ -168,7 +172,7 @@ export const AssetPickerAmount = ({
standardizedAsset = {
type: asset.type,
image:
- getAssetImageURL(asset.details.image, ipfsGateway) ||
+ nftImageURL ||
(tokenList &&
asset.details?.address &&
tokenList[asset.details.address.toLowerCase()]?.iconUrl),
diff --git a/ui/components/multichain/pages/send/components/recipient-content.tsx b/ui/components/multichain/pages/send/components/recipient-content.tsx
index d6c8f00b446c..5c32bb5f3b66 100644
--- a/ui/components/multichain/pages/send/components/recipient-content.tsx
+++ b/ui/components/multichain/pages/send/components/recipient-content.tsx
@@ -45,13 +45,13 @@ import {
getTokenList,
getUseExternalServices,
} from '../../../../../selectors';
+import useGetAssetImageUrl from '../../../../../hooks/useGetAssetImageUrl';
///: END:ONLY_INCLUDE_IF
import type { Quote } from '../../../../../ducks/send/swap-and-send-utils';
import { isEqualCaseInsensitive } from '../../../../../../shared/modules/string-utils';
import { AssetPicker } from '../../../asset-picker-amount/asset-picker';
import { TabName } from '../../../asset-picker-amount/asset-picker-modal/asset-picker-modal-tabs';
-import { getAssetImageURL } from '../../../../../helpers/utils/util';
import { SendHexData, SendPageRow, QuoteCard } from '.';
export const SendPageRecipientContent = ({
@@ -94,6 +94,11 @@ export const SendPageRecipientContent = ({
const tokenList = useSelector(getTokenList) as TokenListMap;
const ipfsGateway = useSelector(getIpfsGateway);
+ const nftImageURL = useGetAssetImageUrl(
+ sendAsset.details?.image ?? null,
+ ipfsGateway,
+ );
+
isSwapAllowed =
isSwapsChain &&
!isSwapAndSendDisabledForNetwork &&
@@ -169,7 +174,7 @@ export const SendPageRecipientContent = ({
? nativeCurrencyImageUrl
: tokenList &&
sendAsset.details &&
- (getAssetImageURL(sendAsset.details?.image, ipfsGateway) ||
+ (nftImageURL ||
tokenList[sendAsset.details.address?.toLowerCase()]
?.iconUrl),
symbol: sendAsset?.details?.symbol || nativeCurrencySymbol,
diff --git a/ui/components/ui/identicon/identicon.component.js b/ui/components/ui/identicon/identicon.component.js
index 50b9075acb8c..0eb05822d292 100644
--- a/ui/components/ui/identicon/identicon.component.js
+++ b/ui/components/ui/identicon/identicon.component.js
@@ -12,6 +12,9 @@ const getStyles = (diameter) => ({
width: diameter,
borderRadius: diameter / 2,
});
+const getImage = async (image, ipfsGateway) => {
+ return await getAssetImageURL(image, ipfsGateway);
+};
export default class Identicon extends Component {
static propTypes = {
@@ -65,6 +68,7 @@ export default class Identicon extends Component {
state = {
imageLoadingError: false,
+ imageUrl: '',
};
static defaultProps = {
@@ -79,9 +83,25 @@ export default class Identicon extends Component {
watchedNftContracts: {},
};
+ loadImage = async () => {
+ const result = await getImage(this.props.image, this.props.ipfsGateway);
+ this.setState({ imageUrl: result });
+ };
+
+ async componentDidMount() {
+ this.loadImage();
+ }
+
+ async componentDidUpdate(prevProps) {
+ if (prevProps.image !== this.props.image) {
+ this.loadImage();
+ }
+ }
+
renderImage() {
- const { className, diameter, alt, imageBorder, ipfsGateway } = this.props;
+ const { className, diameter, alt, imageBorder } = this.props;
let { image } = this.props;
+ const { imageUrl } = this.state;
if (Array.isArray(image) && image.length) {
image = image[0];
@@ -91,7 +111,7 @@ export default class Identicon extends Component {
typeof image === 'string' &&
image.toLowerCase().startsWith('ipfs://')
) {
- image = getAssetImageURL(image, ipfsGateway);
+ image = imageUrl;
}
return (
diff --git a/ui/helpers/utils/util.js b/ui/helpers/utils/util.js
index 7eeab828a750..051dec05d1f9 100644
--- a/ui/helpers/utils/util.js
+++ b/ui/helpers/utils/util.js
@@ -562,7 +562,7 @@ export const sanitizeMessage = (msg, primaryType, types) => {
return { value: sanitizedStruct, type: primaryType };
};
-export function getAssetImageURL(image, ipfsGateway) {
+export async function getAssetImageURL(image, ipfsGateway) {
if (!image || typeof image !== 'string') {
return '';
}
@@ -593,7 +593,7 @@ export function getAssetImageURL(image, ipfsGateway) {
// In the future, we can look into solving the root cause, which might require
// no longer using multiform's CID.parse() method within the assets-controller
try {
- return getFormattedIpfsUrl(ipfsGateway, image, true);
+ return await getFormattedIpfsUrl(ipfsGateway, image, true);
} catch (e) {
logErrorWithMessage(e);
return '';
diff --git a/ui/hooks/useGetAssetImageUrl.test.ts b/ui/hooks/useGetAssetImageUrl.test.ts
new file mode 100644
index 000000000000..a6fe1bdc68a3
--- /dev/null
+++ b/ui/hooks/useGetAssetImageUrl.test.ts
@@ -0,0 +1,47 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import { renderHook } from '@testing-library/react-hooks';
+import { act } from 'react-dom/test-utils';
+import { getAssetImageURL } from '../helpers/utils/util';
+import useGetAssetImageUrl from './useGetAssetImageUrl';
+
+jest.mock('../helpers/utils/util', () => ({
+ getAssetImageURL: jest.fn(),
+}));
+
+const mockGetAssetImageURL = getAssetImageURL as jest.Mock;
+const testIpfsGateway = 'dweb.link';
+describe('useGetAssetImageUrl', () => {
+ it('should return data successfully', async () => {
+ const testIpfsImg =
+ 'ipfs://bafybeieazx4q4ofby24w6n6ftmpad65k4u3vkavv6qnmsazwoe6gaced7m/728.png';
+ const expectedRes =
+ 'https://bafybeieazx4q4ofby24w6n6ftmpad65k4u3vkavv6qnmsazwoe6gaced7m.ipfs.dweb.link/728.png';
+
+ mockGetAssetImageURL.mockResolvedValueOnce(expectedRes);
+ let result;
+
+ await act(async () => {
+ result = renderHook(() =>
+ useGetAssetImageUrl(testIpfsImg, testIpfsGateway),
+ );
+ });
+
+ expect((result as unknown as Record).result.current).toEqual(
+ expectedRes,
+ );
+ });
+
+ it('should return data successfully when image is null', async () => {
+ mockGetAssetImageURL.mockResolvedValueOnce('');
+ const testImage = null;
+ let result;
+ await act(async () => {
+ result = renderHook(() =>
+ useGetAssetImageUrl(testImage, testIpfsGateway),
+ );
+ });
+ expect((result as unknown as Record).result.current).toEqual(
+ '',
+ );
+ });
+});
diff --git a/ui/hooks/useGetAssetImageUrl.ts b/ui/hooks/useGetAssetImageUrl.ts
new file mode 100644
index 000000000000..b84a588ff68d
--- /dev/null
+++ b/ui/hooks/useGetAssetImageUrl.ts
@@ -0,0 +1,19 @@
+import { useState, useEffect } from 'react';
+import { getAssetImageURL } from '../helpers/utils/util';
+
+const useGetAssetImageUrl = (image: string | null, ipfsGateway: string) => {
+ const [imageUrl, setImageUrl] = useState('');
+
+ useEffect(() => {
+ const getAssetImgUrl = async () => {
+ const assetImageUrl = await getAssetImageURL(image, ipfsGateway);
+ setImageUrl(assetImageUrl);
+ };
+
+ getAssetImgUrl();
+ }, [image, ipfsGateway]);
+
+ return imageUrl;
+};
+
+export default useGetAssetImageUrl;
diff --git a/ui/pages/confirm-add-suggested-nft/confirm-add-suggested-nft.js b/ui/pages/confirm-add-suggested-nft/confirm-add-suggested-nft.js
index c5be9de7c9f8..822db143d29a 100644
--- a/ui/pages/confirm-add-suggested-nft/confirm-add-suggested-nft.js
+++ b/ui/pages/confirm-add-suggested-nft/confirm-add-suggested-nft.js
@@ -1,4 +1,4 @@
-import React, { useCallback, useContext, useEffect } from 'react';
+import React, { useCallback, useContext, useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
import { ethErrors, serializeError } from 'eth-rpc-errors';
@@ -59,6 +59,7 @@ import { PRIMARY } from '../../helpers/constants/common';
import { useUserPreferencedCurrency } from '../../hooks/useUserPreferencedCurrency';
import { useCurrencyDisplay } from '../../hooks/useCurrencyDisplay';
import { useOriginMetadata } from '../../hooks/useOriginMetadata';
+import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils';
const ConfirmAddSuggestedNFT = () => {
const t = useContext(I18nContext);
@@ -80,6 +81,7 @@ const ConfirmAddSuggestedNFT = () => {
const accountName = useSelector((state) =>
getAddressBookEntryOrAccountName(state, selectedAddress),
);
+ const [suggestedNftsWithImages, setSuggestedNftsWithImages] = useState([]);
const networkName = NETWORK_TO_NAME_MAP[chainId] || networkIdentifier;
@@ -152,6 +154,32 @@ const ConfirmAddSuggestedNFT = () => {
}
}
+ useEffect(() => {
+ const addImageUrlToSuggestedNFTs = async () => {
+ const suggestedNftWithImages = await Promise.all(
+ suggestedNfts.map(async (item) => {
+ const imgUrl = await getAssetImageURL(
+ item.requestData.asset.image,
+ ipfsGateway,
+ );
+ return {
+ ...item,
+ requestData: {
+ ...item.requestData,
+ asset: {
+ ...item.requestData.asset,
+ assetImageUrl: imgUrl,
+ },
+ },
+ };
+ }),
+ );
+ setSuggestedNftsWithImages(suggestedNftWithImages);
+ };
+
+ addImageUrlToSuggestedNFTs();
+ }, []); // Empty dependency array to run only on mount
+
return (
{
({
id,
requestData: {
- asset: { address, tokenId, symbol, image, name },
+ asset: { address, tokenId, symbol, name },
},
}) => {
- const nftImageURL = getAssetImageURL(image, ipfsGateway);
+ const found = suggestedNftsWithImages.find(
+ (elm) =>
+ elm.requestData.asset.tokenId === tokenId &&
+ isEqualCaseInsensitive(
+ elm.requestData.asset.address,
+ address,
+ ),
+ );
+
+ const nftImageURL = found
+ ? found.requestData.asset.assetImageUrl
+ : '';
+
const blockExplorerLink = getTokenTrackerLink(
address,
chainId,
diff --git a/ui/pages/confirm-add-suggested-nft/confirm-add-suggested-nft.test.js b/ui/pages/confirm-add-suggested-nft/confirm-add-suggested-nft.test.js
index 177bffb7d3bd..003e88f16351 100644
--- a/ui/pages/confirm-add-suggested-nft/confirm-add-suggested-nft.test.js
+++ b/ui/pages/confirm-add-suggested-nft/confirm-add-suggested-nft.test.js
@@ -11,6 +11,7 @@ import mockState from '../../../test/data/mock-state.json';
import { renderWithProvider } from '../../../test/jest/rendering';
import { CHAIN_IDS } from '../../../shared/constants/network';
import { mockNetworkState } from '../../../test/stub/networks';
+import * as util from '../../helpers/utils/util';
import ConfirmAddSuggestedNFT from '.';
const PENDING_NFT_APPROVALS = {
@@ -87,23 +88,25 @@ describe('ConfirmAddSuggestedNFT Component', () => {
jest.clearAllMocks();
});
- it('should render one suggested NFT', () => {
- renderComponent({
- 1: {
- id: '1',
- origin: 'https://www.opensea.io',
- time: 1,
- type: ApprovalType.WatchAsset,
- requestData: {
- asset: {
- address: '0x8b175474e89094c44da98b954eedeac495271d0a',
- name: 'CryptoKitty',
- tokenId: '15',
- standard: 'ERC721',
+ it('should render one suggested NFT', async () => {
+ await act(async () =>
+ renderComponent({
+ 1: {
+ id: '1',
+ origin: 'https://www.opensea.io',
+ time: 1,
+ type: ApprovalType.WatchAsset,
+ requestData: {
+ asset: {
+ address: '0x8b175474e89094c44da98b954eedeac495271d0a',
+ name: 'CryptoKitty',
+ tokenId: '15',
+ standard: 'ERC721',
+ },
},
},
- },
- });
+ }),
+ );
expect(screen.getByText('Add suggested NFTs')).toBeInTheDocument();
expect(screen.getByText('https://www.opensea.io')).toBeInTheDocument();
@@ -118,29 +121,35 @@ describe('ConfirmAddSuggestedNFT Component', () => {
expect(screen.getByRole('button', { name: 'Add NFT' })).toBeInTheDocument();
});
- it('should match snapshot', () => {
- const container = renderComponent({
- 1: {
- id: '1',
- origin: 'https://www.opensea.io',
- time: 1,
- type: ApprovalType.WatchAsset,
- requestData: {
- asset: {
- address: '0x8b175474e89094c44da98b954eedeac495271d0a',
- name: 'CryptoKitty',
- tokenId: '15',
- standard: 'ERC721',
+ it('should match snapshot', async () => {
+ let container;
+ await act(
+ async () =>
+ (container = renderComponent({
+ 1: {
+ id: '1',
+ origin: 'https://www.opensea.io',
+ time: 1,
+ type: ApprovalType.WatchAsset,
+ requestData: {
+ asset: {
+ address: '0x8b175474e89094c44da98b954eedeac495271d0a',
+ name: 'CryptoKitty',
+ tokenId: '15',
+ standard: 'ERC721',
+ },
+ },
},
- },
- },
- });
+ })),
+ );
expect(container).toMatchSnapshot();
});
- it('should render a list of suggested NFTs', () => {
- renderComponent({ ...PENDING_NFT_APPROVALS, ...PENDING_TOKEN_APPROVALS });
+ it('should render a list of suggested NFTs', async () => {
+ await act(async () =>
+ renderComponent({ ...PENDING_NFT_APPROVALS, ...PENDING_TOKEN_APPROVALS }),
+ );
for (const {
requestData: { asset },
@@ -215,4 +224,60 @@ describe('ConfirmAddSuggestedNFT Component', () => {
}),
);
});
+
+ it('should show suggested NFTs with default image', async () => {
+ await act(async () =>
+ renderComponent({
+ 1: {
+ id: '1',
+ origin: 'https://www.opensea.io',
+ time: 1,
+ type: ApprovalType.WatchAsset,
+ requestData: {
+ asset: {
+ address: '0x8b175474e89094c44da98b954eedeac495271d0a',
+ name: 'CryptoKitty',
+ tokenId: '15',
+ standard: 'ERC721',
+ },
+ },
+ },
+ }),
+ );
+
+ expect(screen.getByText('CryptoKitty')).toBeInTheDocument();
+ expect(screen.getByText(`#15`)).toBeInTheDocument();
+ expect(screen.getAllByRole('img')).toHaveLength(1);
+ const defaultImg = screen.getByTestId(`nft-default-image`);
+ expect(defaultImg).toBeInTheDocument();
+ });
+
+ it('should show suggested NFTs with image', async () => {
+ const expectedRes =
+ 'https://bafybeieazx4q4ofby24w6n6ftmpad65k4u3vkavv6qnmsazwoe6gaced7m.ipfs.dweb.link/728.png';
+
+ jest.spyOn(util, 'getAssetImageURL').mockResolvedValue(expectedRes);
+ await act(async () =>
+ renderComponent({
+ 1: {
+ id: '1',
+ origin: 'https://www.opensea.io',
+ time: 1,
+ type: ApprovalType.WatchAsset,
+ requestData: {
+ asset: {
+ address: '0x8b175474e89094c44da98b954eedeac495271d0a',
+ name: 'CryptoKitty',
+ tokenId: '15',
+ standard: 'ERC721',
+ },
+ },
+ },
+ }),
+ );
+
+ expect(screen.getByText('CryptoKitty')).toBeInTheDocument();
+ expect(screen.getByText(`#15`)).toBeInTheDocument();
+ expect(screen.getAllByRole('img')).toHaveLength(2);
+ });
});
diff --git a/ui/pages/confirmations/components/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js b/ui/pages/confirmations/components/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js
index 6df86587abce..4d5a83763213 100644
--- a/ui/pages/confirmations/components/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js
+++ b/ui/pages/confirmations/components/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js
@@ -16,7 +16,7 @@ import InfoTooltip from '../../../../../../components/ui/info-tooltip';
import NicknamePopovers from '../../../../../../components/app/modals/nickname-popovers';
import { ORIGIN_METAMASK } from '../../../../../../../shared/constants/app';
import SiteOrigin from '../../../../../../components/ui/site-origin';
-import { getAssetImageURL } from '../../../../../../helpers/utils/util';
+import useGetAssetImageUrl from '../../../../../../hooks/useGetAssetImageUrl';
const ConfirmPageContainerSummary = (props) => {
const {
@@ -36,6 +36,7 @@ const ConfirmPageContainerSummary = (props) => {
const ipfsGateway = useSelector(getIpfsGateway);
const txData = useSelector(txDataSelector);
+ const nftImageURL = useGetAssetImageUrl(image, ipfsGateway);
const { txParams = {} } = txData;
const { to: txParamsToAddress } = txParams;
@@ -66,14 +67,12 @@ const ConfirmPageContainerSummary = (props) => {
const checksummedAddress = toChecksumHexAddress(contractAddress);
const renderImage = () => {
- const imagePath = getAssetImageURL(image, ipfsGateway);
-
if (image) {
return (
);
} else if (contractAddress) {
diff --git a/yarn.lock b/yarn.lock
index adc43681e805..5fd8f17c33a1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4752,7 +4752,7 @@ __metadata:
languageName: node
linkType: hard
-"@metamask/abi-utils@npm:^2.0.2, @metamask/abi-utils@npm:^2.0.4":
+"@metamask/abi-utils@npm:^2.0.2, @metamask/abi-utils@npm:^2.0.3, @metamask/abi-utils@npm:^2.0.4":
version: 2.0.4
resolution: "@metamask/abi-utils@npm:2.0.4"
dependencies:
@@ -4849,7 +4849,7 @@ __metadata:
languageName: node
linkType: hard
-"@metamask/approval-controller@npm:^7.0.0, @metamask/approval-controller@npm:^7.0.1, @metamask/approval-controller@npm:^7.0.2":
+"@metamask/approval-controller@npm:^7.0.0, @metamask/approval-controller@npm:^7.0.2":
version: 7.0.2
resolution: "@metamask/approval-controller@npm:7.0.2"
dependencies:
@@ -4861,45 +4861,41 @@ __metadata:
languageName: node
linkType: hard
-"@metamask/assets-controllers@npm:^36.0.0":
- version: 36.0.0
- resolution: "@metamask/assets-controllers@npm:36.0.0"
+"@metamask/assets-controllers@npm:^37.0.0":
+ version: 37.0.0
+ resolution: "@metamask/assets-controllers@npm:37.0.0"
dependencies:
"@ethereumjs/util": "npm:^8.1.0"
"@ethersproject/address": "npm:^5.7.0"
"@ethersproject/bignumber": "npm:^5.7.0"
"@ethersproject/contracts": "npm:^5.7.0"
"@ethersproject/providers": "npm:^5.7.0"
- "@metamask/abi-utils": "npm:^2.0.2"
- "@metamask/accounts-controller": "npm:^17.2.0"
- "@metamask/approval-controller": "npm:^7.0.1"
- "@metamask/base-controller": "npm:^6.0.1"
+ "@metamask/abi-utils": "npm:^2.0.3"
+ "@metamask/base-controller": "npm:^6.0.2"
"@metamask/contract-metadata": "npm:^2.4.0"
- "@metamask/controller-utils": "npm:^11.0.1"
+ "@metamask/controller-utils": "npm:^11.0.2"
"@metamask/eth-query": "npm:^4.0.0"
- "@metamask/keyring-controller": "npm:^17.1.0"
"@metamask/metamask-eth-abis": "npm:^3.1.1"
- "@metamask/network-controller": "npm:^20.0.0"
- "@metamask/polling-controller": "npm:^9.0.0"
- "@metamask/preferences-controller": "npm:^13.0.0"
+ "@metamask/polling-controller": "npm:^9.0.1"
"@metamask/rpc-errors": "npm:^6.3.1"
- "@metamask/utils": "npm:^9.0.0"
+ "@metamask/utils": "npm:^9.1.0"
"@types/bn.js": "npm:^5.1.5"
"@types/uuid": "npm:^8.3.0"
async-mutex: "npm:^0.5.0"
bn.js: "npm:^5.2.1"
cockatiel: "npm:^3.1.2"
+ immer: "npm:^9.0.6"
lodash: "npm:^4.17.21"
- multiformats: "npm:^9.5.2"
+ multiformats: "npm:^13.1.0"
single-call-balance-checker-abi: "npm:^1.0.0"
uuid: "npm:^8.3.2"
peerDependencies:
- "@metamask/accounts-controller": ^17.0.0
+ "@metamask/accounts-controller": ^18.0.0
"@metamask/approval-controller": ^7.0.0
"@metamask/keyring-controller": ^17.0.0
"@metamask/network-controller": ^20.0.0
"@metamask/preferences-controller": ^13.0.0
- checksum: 10/7855bf544e77c4a1dc233748941981b9f09f1633cc1f7aac06cdc6555e7ee2690cff0866d55ae5ea012c6d05bf61511b39f886ae9b1c498a9154bf9520cdf199
+ checksum: 10/89798930cb80a134263ce82db736feebd064fe6c999ddcf41ca86fad81cfadbb9e37d1919a6384aaf6d3aa0cb520684e7b8228da3b9bc1e70e7aea174a69c4ac
languageName: node
linkType: hard
@@ -4980,7 +4976,7 @@ __metadata:
languageName: node
linkType: hard
-"@metamask/controller-utils@npm:^11.0.0, @metamask/controller-utils@npm:^11.0.1, @metamask/controller-utils@npm:^11.0.2, @metamask/controller-utils@npm:^11.1.0, @metamask/controller-utils@npm:^11.2.0, @metamask/controller-utils@npm:^11.3.0":
+"@metamask/controller-utils@npm:^11.0.0, @metamask/controller-utils@npm:^11.0.2, @metamask/controller-utils@npm:^11.1.0, @metamask/controller-utils@npm:^11.2.0, @metamask/controller-utils@npm:^11.3.0":
version: 11.3.0
resolution: "@metamask/controller-utils@npm:11.3.0"
dependencies:
@@ -6020,20 +6016,19 @@ __metadata:
languageName: node
linkType: hard
-"@metamask/polling-controller@npm:^9.0.0":
- version: 9.0.0
- resolution: "@metamask/polling-controller@npm:9.0.0"
+"@metamask/polling-controller@npm:^9.0.1":
+ version: 9.0.1
+ resolution: "@metamask/polling-controller@npm:9.0.1"
dependencies:
- "@metamask/base-controller": "npm:^6.0.0"
- "@metamask/controller-utils": "npm:^11.0.0"
- "@metamask/network-controller": "npm:^20.0.0"
- "@metamask/utils": "npm:^8.3.0"
+ "@metamask/base-controller": "npm:^6.0.2"
+ "@metamask/controller-utils": "npm:^11.0.2"
+ "@metamask/utils": "npm:^9.1.0"
"@types/uuid": "npm:^8.3.0"
fast-json-stable-stringify: "npm:^2.1.0"
uuid: "npm:^8.3.2"
peerDependencies:
"@metamask/network-controller": ^20.0.0
- checksum: 10/5e3abd84dcb3fb128add949bbda78a34d509f56b71d27f60f8ff3fd5de116424dc9e202c812c5f3c233d1489740c376b9de47f28faa387e64a198d246f962baf
+ checksum: 10/e9e8c51013290a2e4b2817ba1e0915783474f6a55fe614e20acf92bf707e300bec1fa612c8019ae9afe9635d018fb5d5b106c8027446ba12767220db91cf1ee5
languageName: node
linkType: hard
@@ -6065,18 +6060,6 @@ __metadata:
languageName: node
linkType: hard
-"@metamask/preferences-controller@npm:^13.0.0":
- version: 13.0.0
- resolution: "@metamask/preferences-controller@npm:13.0.0"
- dependencies:
- "@metamask/base-controller": "npm:^6.0.0"
- "@metamask/controller-utils": "npm:^11.0.0"
- peerDependencies:
- "@metamask/keyring-controller": ^17.0.0
- checksum: 10/99d39de8adb9a43bbb5972d70feabfba6bac0c1d71d1567838e506e661f7fd293205e2c83361f7166ce72b38437d79cd0da8a7a14dd584f59b5f583dcb4d769b
- languageName: node
- linkType: hard
-
"@metamask/profile-sync-controller@npm:^0.8.0":
version: 0.8.0
resolution: "@metamask/profile-sync-controller@npm:0.8.0"
@@ -26138,7 +26121,7 @@ __metadata:
"@metamask/announcement-controller": "npm:^7.0.0"
"@metamask/api-specs": "npm:^0.9.3"
"@metamask/approval-controller": "npm:^7.0.0"
- "@metamask/assets-controllers": "npm:^36.0.0"
+ "@metamask/assets-controllers": "npm:^37.0.0"
"@metamask/auto-changelog": "npm:^2.1.0"
"@metamask/base-controller": "npm:^7.0.0"
"@metamask/bitcoin-wallet-snap": "npm:^0.6.0"
@@ -27471,7 +27454,14 @@ __metadata:
languageName: node
linkType: hard
-"multiformats@npm:^9.4.2, multiformats@npm:^9.5.2":
+"multiformats@npm:^13.1.0":
+ version: 13.2.2
+ resolution: "multiformats@npm:13.2.2"
+ checksum: 10/6e673320e9b06d5fdbbf2bde0d3132f13fac94fb40f36d646265b5c38eba4a28c40a2c76b4efa0c1a23517fe87320e540e9ef7f28d71c1cc3239c91bf6770ce6
+ languageName: node
+ linkType: hard
+
+"multiformats@npm:^9.4.2":
version: 9.9.0
resolution: "multiformats@npm:9.9.0"
checksum: 10/ad55c7d480d22f4258a68fd88aa2aab744fe0cb1e68d732fc886f67d858b37e3aa6c2cec12b2960ead7730d43be690931485238569952d8a3d7f90fdc726c652