diff --git a/eslint-warning-thresholds.json b/eslint-warning-thresholds.json index b05522c2a37..159ab7001cf 100644 --- a/eslint-warning-thresholds.json +++ b/eslint-warning-thresholds.json @@ -39,13 +39,11 @@ }, "packages/assets-controllers/src/NftController.test.ts": { "import-x/namespace": 9, - "import-x/order": 3, - "jest/no-conditional-in-test": 8 + "jest/no-conditional-in-test": 6 }, "packages/assets-controllers/src/NftController.ts": { "@typescript-eslint/prefer-readonly": 1, - "jsdoc/check-tag-names": 46, - "jsdoc/tag-lines": 4 + "jsdoc/check-tag-names": 46 }, "packages/assets-controllers/src/NftDetectionController.test.ts": { "import-x/namespace": 6, diff --git a/packages/assets-controllers/src/AccountTrackerController.test.ts b/packages/assets-controllers/src/AccountTrackerController.test.ts index 59c40417a02..e29b537cd89 100644 --- a/packages/assets-controllers/src/AccountTrackerController.test.ts +++ b/packages/assets-controllers/src/AccountTrackerController.test.ts @@ -127,7 +127,7 @@ describe('AccountTrackerController', () => { listAccounts: [mockAccount1, mockAccount2], }, async ({ controller }) => { - await controller.refresh(); + await controller.refresh(['mainnet']); expect(controller.state).toStrictEqual({ accountsByChainId: { '0x1': { @@ -154,7 +154,7 @@ describe('AccountTrackerController', () => { listAccounts: [ACCOUNT_1], }, async ({ controller }) => { - await controller.refresh(); + await controller.refresh(['mainnet']); expect(controller.state).toStrictEqual({ accountsByChainId: { @@ -181,7 +181,7 @@ describe('AccountTrackerController', () => { listAccounts: [ACCOUNT_1, ACCOUNT_2], }, async ({ controller }) => { - await controller.refresh(); + await controller.refresh(['mainnet']); expect(controller.state).toStrictEqual({ accountsByChainId: { @@ -207,7 +207,7 @@ describe('AccountTrackerController', () => { listAccounts: [ACCOUNT_1, ACCOUNT_2], }, async ({ controller }) => { - await controller.refresh(); + await controller.refresh(['mainnet']); expect(controller.state).toStrictEqual({ accountsByChainId: { @@ -237,7 +237,7 @@ describe('AccountTrackerController', () => { listAccounts: [ACCOUNT_1, ACCOUNT_2], }, async ({ controller }) => { - await controller.refresh(); + await controller.refresh(['mainnet']); expect(controller.state).toStrictEqual({ accountsByChainId: { @@ -272,7 +272,7 @@ describe('AccountTrackerController', () => { listAccounts: [ACCOUNT_1, ACCOUNT_2], }, async ({ controller }) => { - await controller.refresh(); + await controller.refresh(['mainnet']); expect(controller.state).toStrictEqual({ accountsByChainId: { @@ -306,7 +306,7 @@ describe('AccountTrackerController', () => { listAccounts: [ACCOUNT_1, ACCOUNT_2], }, async ({ controller }) => { - await controller.refresh(); + await controller.refresh(['mainnet']); expect(controller.state).toStrictEqual({ accountsByChainId: { @@ -366,7 +366,7 @@ describe('AccountTrackerController', () => { }, }, async ({ controller }) => { - await controller.refresh(networkClientId); + await controller.refresh(['networkClientId1']); expect(controller.state).toStrictEqual({ accountsByChainId: { '0x1': { @@ -403,7 +403,7 @@ describe('AccountTrackerController', () => { }, }, async ({ controller }) => { - await controller.refresh(networkClientId); + await controller.refresh(['networkClientId1']); expect(controller.state).toStrictEqual({ accountsByChainId: { @@ -441,7 +441,7 @@ describe('AccountTrackerController', () => { }, }, async ({ controller }) => { - await controller.refresh(networkClientId); + await controller.refresh(['networkClientId1']); expect(controller.state).toStrictEqual({ accountsByChainId: { @@ -477,7 +477,7 @@ describe('AccountTrackerController', () => { }, }, async ({ controller }) => { - await controller.refresh(networkClientId); + await controller.refresh(['networkClientId1']); expect(controller.state).toStrictEqual({ accountsByChainId: { @@ -517,7 +517,7 @@ describe('AccountTrackerController', () => { }, }, async ({ controller }) => { - await controller.refresh(); + await controller.refresh(['mainnet']); expect(controller.state).toStrictEqual({ accountsByChainId: { @@ -558,7 +558,7 @@ describe('AccountTrackerController', () => { }, }, async ({ controller }) => { - await controller.refresh(); + await controller.refresh(['mainnet']); expect(controller.state).toStrictEqual({ accountsByChainId: { @@ -598,7 +598,7 @@ describe('AccountTrackerController', () => { }, }, async ({ controller }) => { - await controller.refresh(); + await controller.refresh(['mainnet']); expect(controller.state).toStrictEqual({ accountsByChainId: { @@ -640,7 +640,7 @@ describe('AccountTrackerController', () => { }, }, async ({ controller }) => { - await controller.refresh(); + await controller.refresh(['mainnet']); expect(controller.state).toStrictEqual({ accountsByChainId: { @@ -726,7 +726,7 @@ describe('AccountTrackerController', () => { jest.spyOn(controller, 'refresh').mockResolvedValue(); await controller.startPolling({ - networkClientId: 'networkClientId1', + networkClientIds: ['networkClientId1'], }); await advanceTime({ clock, duration: 1 }); @@ -759,34 +759,34 @@ describe('AccountTrackerController', () => { .mockResolvedValue(); controller.startPolling({ - networkClientId: networkClientId1, + networkClientIds: [networkClientId1], }); await advanceTime({ clock, duration: 0 }); - expect(refreshSpy).toHaveBeenNthCalledWith(1, networkClientId1); + expect(refreshSpy).toHaveBeenNthCalledWith(1, [networkClientId1]); expect(refreshSpy).toHaveBeenCalledTimes(1); await advanceTime({ clock, duration: 50 }); expect(refreshSpy).toHaveBeenCalledTimes(1); await advanceTime({ clock, duration: 50 }); - expect(refreshSpy).toHaveBeenNthCalledWith(2, networkClientId1); + expect(refreshSpy).toHaveBeenNthCalledWith(2, [networkClientId1]); expect(refreshSpy).toHaveBeenCalledTimes(2); const pollToken = controller.startPolling({ - networkClientId: networkClientId2, + networkClientIds: [networkClientId2], }); await advanceTime({ clock, duration: 0 }); - expect(refreshSpy).toHaveBeenNthCalledWith(3, networkClientId2); + expect(refreshSpy).toHaveBeenNthCalledWith(3, [networkClientId2]); expect(refreshSpy).toHaveBeenCalledTimes(3); await advanceTime({ clock, duration: 100 }); - expect(refreshSpy).toHaveBeenNthCalledWith(4, networkClientId1); - expect(refreshSpy).toHaveBeenNthCalledWith(5, networkClientId2); + expect(refreshSpy).toHaveBeenNthCalledWith(4, [networkClientId1]); + expect(refreshSpy).toHaveBeenNthCalledWith(5, [networkClientId2]); expect(refreshSpy).toHaveBeenCalledTimes(5); controller.stopPollingByPollingToken(pollToken); await advanceTime({ clock, duration: 100 }); - expect(refreshSpy).toHaveBeenNthCalledWith(6, networkClientId1); + expect(refreshSpy).toHaveBeenNthCalledWith(6, [networkClientId1]); expect(refreshSpy).toHaveBeenCalledTimes(6); controller.stopAllPolling(); @@ -810,7 +810,7 @@ describe('AccountTrackerController', () => { expect(refreshSpy).not.toHaveBeenCalled(); controller.startPolling({ - networkClientId: 'networkClientId1', + networkClientIds: ['networkClientId1'], }); await advanceTime({ clock, duration: 1 }); diff --git a/packages/assets-controllers/src/AccountTrackerController.ts b/packages/assets-controllers/src/AccountTrackerController.ts index 56a073bbaa2..c8d9f0871e3 100644 --- a/packages/assets-controllers/src/AccountTrackerController.ts +++ b/packages/assets-controllers/src/AccountTrackerController.ts @@ -124,7 +124,7 @@ export type AccountTrackerControllerMessenger = RestrictedMessenger< /** The input to start polling for the {@link AccountTrackerController} */ type AccountTrackerPollingInput = { - networkClientId: NetworkClientId; + networkClientIds: NetworkClientId[]; }; /** @@ -194,7 +194,7 @@ export class AccountTrackerController extends StaticIntervalPollingController this.refresh(), + () => this.refresh(this.#getNetworkClientIds()), ); } @@ -282,18 +282,35 @@ export class AccountTrackerController extends StaticIntervalPollingController + networkConfiguration.rpcEndpoints.map( + (rpcEndpoint) => rpcEndpoint.networkClientId, + ), + ); + } + /** * Refreshes the balances of the accounts using the networkClientId * * @param input - The input for the poll. - * @param input.networkClientId - The network client ID used to get balances. + * @param input.networkClientIds - The network client IDs used to get balances. */ async _executePoll({ - networkClientId, + networkClientIds, }: AccountTrackerPollingInput): Promise { // TODO: Either fix this lint violation or explain why it's necessary to ignore. // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.refresh(networkClientId); + this.refresh(networkClientIds); } /** @@ -301,50 +318,77 @@ export class AccountTrackerController extends StaticIntervalPollingController { + const { chainId, ethQuery } = + this.#getCorrectNetworkClient(networkClientId); + this.syncAccounts(chainId); + const { accountsByChainId } = this.state; + const { isMultiAccountBalancesEnabled } = this.messagingSystem.call( + 'PreferencesController:getState', + ); + + const accountsToUpdate = isMultiAccountBalancesEnabled + ? Object.keys(accountsByChainId[chainId]) + : [toChecksumHexAddress(selectedAccount.address)]; + + const accountsForChain = { ...accountsByChainId[chainId] }; + + // Create an array of promises for balance and staked balance fetching + const balancePromises = accountsToUpdate.map(async (address) => { + const balancePromise = this.#getBalanceFromChain(address, ethQuery); + const stakedBalancePromise = this.#includeStakedAssets + ? this.#getStakedBalanceForChain(address, networkClientId) + : Promise.resolve(null); + + const [balanceResult, stakedBalanceResult] = await Promise.allSettled( + [balancePromise, stakedBalancePromise], ); - if (stakedBalance) { + + // Update account balances + if (balanceResult.status === 'fulfilled' && balanceResult.value) { + accountsForChain[address] = { + balance: balanceResult.value, + }; + } + + if ( + stakedBalanceResult.status === 'fulfilled' && + stakedBalanceResult.value + ) { accountsForChain[address] = { ...accountsForChain[address], - stakedBalance, + stakedBalance: stakedBalanceResult.value, }; } - } - } + }); - this.update((state) => { - state.accountsByChainId[chainId] = accountsForChain; + // Wait for all balance-related promises to settle + await Promise.allSettled(balancePromises); + + // After all promises for this networkClientId are settled, return the updated data + return { chainId, accountsForChain }; + }); + + // Wait for all networkClientId updates to settle in parallel + const allResults = await Promise.allSettled(updatePromises); + + // Update the state once all networkClientId updates are completed + allResults.forEach((result) => { + if (result.status === 'fulfilled') { + const { chainId, accountsForChain } = result.value; + this.update((state) => { + state.accountsByChainId[chainId] = accountsForChain; + }); + } }); } finally { releaseLock(); diff --git a/packages/assets-controllers/src/AssetsContractController.test.ts b/packages/assets-controllers/src/AssetsContractController.test.ts index c51221372e4..7944646c9cf 100644 --- a/packages/assets-controllers/src/AssetsContractController.test.ts +++ b/packages/assets-controllers/src/AssetsContractController.test.ts @@ -34,7 +34,6 @@ import { AssetsContractController, MISSING_PROVIDER_ERROR, } from './AssetsContractController'; -import { SupportedTokenDetectionNetworks } from './assetsUtil'; const ERC20_UNI_ADDRESS = '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984'; const ERC20_SAI_ADDRESS = '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359'; @@ -192,10 +191,8 @@ describe('AssetsContractController', () => { it('should set default config', async () => { const { assetsContract, messenger } = await setupAssetContractControllers(); expect({ - chainId: assetsContract.chainId, ipfsGateway: assetsContract.ipfsGateway, }).toStrictEqual({ - chainId: SupportedTokenDetectionNetworks.mainnet, ipfsGateway: IPFS_DEFAULT_GATEWAY_URL, }); messenger.clearEventSubscriptions('NetworkController:networkDidChange'); @@ -205,10 +202,8 @@ describe('AssetsContractController', () => { const { assetsContract, messenger, triggerPreferencesStateChange } = await setupAssetContractControllers(); expect({ - chainId: assetsContract.chainId, ipfsGateway: assetsContract.ipfsGateway, }).toStrictEqual({ - chainId: SupportedTokenDetectionNetworks.mainnet, ipfsGateway: IPFS_DEFAULT_GATEWAY_URL, }); @@ -218,45 +213,42 @@ describe('AssetsContractController', () => { }); expect({ - chainId: assetsContract.chainId, ipfsGateway: assetsContract.ipfsGateway, }).toStrictEqual({ ipfsGateway: 'newIPFSGateWay', - chainId: SupportedTokenDetectionNetworks.mainnet, }); messenger.clearEventSubscriptions('NetworkController:networkDidChange'); }); it('should throw missing provider error when getting ERC-20 token balance when missing provider', async () => { - const { assetsContract, messenger } = await setupAssetContractControllers(); - assetsContract.setProvider(undefined); + const { messenger } = await setupAssetContractControllers(); await expect( messenger.call( `AssetsContractController:getERC20BalanceOf`, ERC20_UNI_ADDRESS, TEST_ACCOUNT_PUBLIC_ADDRESS, + undefined as unknown as NetworkClientId, ), ).rejects.toThrow(MISSING_PROVIDER_ERROR); messenger.clearEventSubscriptions('NetworkController:networkDidChange'); }); it('should throw missing provider error when getting ERC-20 token decimal when missing provider', async () => { - const { assetsContract, messenger } = await setupAssetContractControllers(); - assetsContract.setProvider(undefined); + const { messenger } = await setupAssetContractControllers(); await expect( messenger.call( `AssetsContractController:getERC20TokenDecimals`, ERC20_UNI_ADDRESS, + undefined as unknown as NetworkClientId, ), ).rejects.toThrow(MISSING_PROVIDER_ERROR); messenger.clearEventSubscriptions('NetworkController:networkDidChange'); }); it('should get balance of ERC-20 token contract correctly', async () => { - const { assetsContract, messenger, provider, networkClientConfiguration } = + const { messenger, networkClientConfiguration } = await setupAssetContractControllers(); - assetsContract.setProvider(provider); mockNetworkWithDefaultChainId({ networkClientConfiguration, mocks: [ @@ -298,11 +290,13 @@ describe('AssetsContractController', () => { `AssetsContractController:getERC20BalanceOf`, ERC20_UNI_ADDRESS, TEST_ACCOUNT_PUBLIC_ADDRESS, + 'mainnet', ); const UNINoBalance = await messenger.call( `AssetsContractController:getERC20BalanceOf`, ERC20_UNI_ADDRESS, '0x202637dAAEfbd7f131f90338a4A6c69F6Cd5CE91', + 'mainnet', ); expect(UNIBalance.toString(16)).not.toBe('0'); expect(UNINoBalance.toString(16)).toBe('0'); @@ -310,9 +304,8 @@ describe('AssetsContractController', () => { }); it('should get ERC-721 NFT tokenId correctly', async () => { - const { assetsContract, messenger, provider, networkClientConfiguration } = + const { messenger, networkClientConfiguration } = await setupAssetContractControllers(); - assetsContract.setProvider(provider); mockNetworkWithDefaultChainId({ networkClientConfiguration, mocks: [ @@ -339,33 +332,33 @@ describe('AssetsContractController', () => { ERC721_GODS_ADDRESS, '0x9a90bd8d1149a88b42a99cf62215ad955d6f498a', 0, + 'mainnet', ); expect(tokenId).not.toBe(0); messenger.clearEventSubscriptions('NetworkController:networkDidChange'); }); it('should throw missing provider error when getting ERC-721 token standard and details when missing provider', async () => { - const { assetsContract, messenger } = await setupAssetContractControllers(); - assetsContract.setProvider(undefined); + const { messenger } = await setupAssetContractControllers(); await expect( messenger.call( `AssetsContractController:getTokenStandardAndDetails`, ERC20_UNI_ADDRESS, TEST_ACCOUNT_PUBLIC_ADDRESS, + undefined as unknown as NetworkClientId, ), ).rejects.toThrow(MISSING_PROVIDER_ERROR); messenger.clearEventSubscriptions('NetworkController:networkDidChange'); }); it('should throw contract standard error when getting ERC-20 token standard and details when provided with invalid ERC-20 address', async () => { - const { assetsContract, messenger, provider } = - await setupAssetContractControllers(); - assetsContract.setProvider(provider); + const { messenger } = await setupAssetContractControllers(); const error = 'Unable to determine contract standard'; await expect( messenger.call( `AssetsContractController:getTokenStandardAndDetails`, 'BaDeRc20AdDrEsS', + 'mainnet', TEST_ACCOUNT_PUBLIC_ADDRESS, ), ).rejects.toThrow(error); @@ -373,9 +366,8 @@ describe('AssetsContractController', () => { }); it('should get ERC-721 token standard and details', async () => { - const { assetsContract, messenger, provider, networkClientConfiguration } = + const { messenger, networkClientConfiguration } = await setupAssetContractControllers(); - assetsContract.setProvider(provider); mockNetworkWithDefaultChainId({ networkClientConfiguration, mocks: [ @@ -432,6 +424,7 @@ describe('AssetsContractController', () => { const standardAndDetails = await messenger.call( `AssetsContractController:getTokenStandardAndDetails`, ERC721_GODS_ADDRESS, + 'mainnet', TEST_ACCOUNT_PUBLIC_ADDRESS, ); expect(standardAndDetails.standard).toBe('ERC721'); @@ -439,9 +432,8 @@ describe('AssetsContractController', () => { }); it('should get ERC-1155 token standard and details', async () => { - const { assetsContract, messenger, provider, networkClientConfiguration } = + const { messenger, networkClientConfiguration } = await setupAssetContractControllers(); - assetsContract.setProvider(provider); mockNetworkWithDefaultChainId({ networkClientConfiguration, mocks: [ @@ -514,6 +506,7 @@ describe('AssetsContractController', () => { const standardAndDetails = await messenger.call( `AssetsContractController:getTokenStandardAndDetails`, ERC1155_ADDRESS, + 'mainnet', TEST_ACCOUNT_PUBLIC_ADDRESS, ); @@ -525,9 +518,8 @@ describe('AssetsContractController', () => { }); it('should get ERC-20 token standard and details', async () => { - const { assetsContract, messenger, provider, networkClientConfiguration } = + const { messenger, networkClientConfiguration } = await setupAssetContractControllers(); - assetsContract.setProvider(provider); mockNetworkWithDefaultChainId({ networkClientConfiguration, mocks: [ @@ -616,6 +608,7 @@ describe('AssetsContractController', () => { const standardAndDetails = await messenger.call( `AssetsContractController:getTokenStandardAndDetails`, ERC20_UNI_ADDRESS, + 'mainnet', TEST_ACCOUNT_PUBLIC_ADDRESS, ); expect(standardAndDetails.standard).toBe('ERC20'); @@ -623,9 +616,8 @@ describe('AssetsContractController', () => { }); it('should get ERC-721 NFT tokenURI correctly', async () => { - const { assetsContract, messenger, provider, networkClientConfiguration } = + const { messenger, networkClientConfiguration } = await setupAssetContractControllers(); - assetsContract.setProvider(provider); mockNetworkWithDefaultChainId({ networkClientConfiguration, mocks: [ @@ -667,15 +659,15 @@ describe('AssetsContractController', () => { `AssetsContractController:getERC721TokenURI`, ERC721_GODS_ADDRESS, '0', + 'mainnet', ); expect(tokenId).toBe('https://api.godsunchained.com/card/0'); messenger.clearEventSubscriptions('NetworkController:networkDidChange'); }); it('should not throw an error when address given does not support NFT Metadata interface', async () => { - const { assetsContract, messenger, provider, networkClientConfiguration } = + const { messenger, networkClientConfiguration } = await setupAssetContractControllers(); - assetsContract.setProvider(provider); const errorLogSpy = jest .spyOn(console, 'error') .mockImplementationOnce(() => { @@ -721,6 +713,7 @@ describe('AssetsContractController', () => { `AssetsContractController:getERC721TokenURI`, '0x0000000000000000000000000000000000000000', '0', + 'mainnet', ); expect(uri).toBe('https://api.godsunchained.com/card/0'); expect(errorLogSpy).toHaveBeenCalledTimes(1); @@ -732,9 +725,8 @@ describe('AssetsContractController', () => { }); it('should get ERC-721 NFT name', async () => { - const { assetsContract, messenger, provider, networkClientConfiguration } = + const { messenger, networkClientConfiguration } = await setupAssetContractControllers(); - assetsContract.setProvider(provider); mockNetworkWithDefaultChainId({ networkClientConfiguration, mocks: [ @@ -759,15 +751,15 @@ describe('AssetsContractController', () => { const name = await messenger.call( `AssetsContractController:getERC721AssetName`, ERC721_GODS_ADDRESS, + 'mainnet', ); expect(name).toBe('Gods Unchained'); messenger.clearEventSubscriptions('NetworkController:networkDidChange'); }); it('should get ERC-721 NFT symbol', async () => { - const { assetsContract, messenger, provider, networkClientConfiguration } = + const { messenger, networkClientConfiguration } = await setupAssetContractControllers(); - assetsContract.setProvider(provider); mockNetworkWithDefaultChainId({ networkClientConfiguration, mocks: [ @@ -792,6 +784,7 @@ describe('AssetsContractController', () => { const symbol = await messenger.call( `AssetsContractController:getERC721AssetSymbol`, ERC721_GODS_ADDRESS, + 'mainnet', ); expect(symbol).toBe('GODS'); messenger.clearEventSubscriptions('NetworkController:networkDidChange'); @@ -803,15 +796,15 @@ describe('AssetsContractController', () => { messenger.call( `AssetsContractController:getERC721AssetSymbol`, ERC721_GODS_ADDRESS, + undefined as unknown as string, ), ).rejects.toThrow(MISSING_PROVIDER_ERROR); messenger.clearEventSubscriptions('NetworkController:networkDidChange'); }); it('should get ERC-20 token decimals', async () => { - const { assetsContract, messenger, provider, networkClientConfiguration } = + const { messenger, networkClientConfiguration } = await setupAssetContractControllers(); - assetsContract.setProvider(provider); mockNetworkWithDefaultChainId({ networkClientConfiguration, mocks: [ @@ -836,15 +829,15 @@ describe('AssetsContractController', () => { const decimals = await messenger.call( `AssetsContractController:getERC20TokenDecimals`, ERC20_SAI_ADDRESS, + 'mainnet', ); expect(Number(decimals)).toBe(18); messenger.clearEventSubscriptions('NetworkController:networkDidChange'); }); it('should get ERC-20 token name', async () => { - const { assetsContract, messenger, provider, networkClientConfiguration } = + const { messenger, networkClientConfiguration } = await setupAssetContractControllers(); - assetsContract.setProvider(provider); mockNetworkWithDefaultChainId({ networkClientConfiguration, mocks: [ @@ -870,6 +863,7 @@ describe('AssetsContractController', () => { const name = await messenger.call( `AssetsContractController:getERC20TokenName`, ERC20_DAI_ADDRESS, + 'mainnet', ); expect(name).toBe('Dai Stablecoin'); @@ -877,9 +871,8 @@ describe('AssetsContractController', () => { }); it('should get ERC-721 NFT ownership', async () => { - const { assetsContract, messenger, provider, networkClientConfiguration } = + const { messenger, networkClientConfiguration } = await setupAssetContractControllers(); - assetsContract.setProvider(provider); mockNetworkWithDefaultChainId({ networkClientConfiguration, mocks: [ @@ -905,6 +898,7 @@ describe('AssetsContractController', () => { `AssetsContractController:getERC721OwnerOf`, ERC721_GODS_ADDRESS, '148332', + 'mainnet', ); expect(tokenId).not.toBe(''); messenger.clearEventSubscriptions('NetworkController:networkDidChange'); @@ -917,15 +911,15 @@ describe('AssetsContractController', () => { `AssetsContractController:getERC721OwnerOf`, ERC721_GODS_ADDRESS, '148332', + undefined as unknown as string, ), ).rejects.toThrow(MISSING_PROVIDER_ERROR); messenger.clearEventSubscriptions('NetworkController:networkDidChange'); }); it('should get balance of ERC-20 token in a single call on network with token detection support', async () => { - const { assetsContract, messenger, provider, networkClientConfiguration } = + const { messenger, networkClientConfiguration } = await setupAssetContractControllers(); - assetsContract.setProvider(provider); mockNetworkWithDefaultChainId({ networkClientConfiguration, mocks: [ @@ -951,6 +945,7 @@ describe('AssetsContractController', () => { `AssetsContractController:getBalancesInSingleCall`, ERC20_SAI_ADDRESS, [ERC20_SAI_ADDRESS], + 'mainnet', ); expect(balances[ERC20_SAI_ADDRESS]).toBeDefined(); messenger.clearEventSubscriptions('NetworkController:networkDidChange'); @@ -1024,20 +1019,19 @@ describe('AssetsContractController', () => { }, ], }); - const { assetsContract, messenger, provider } = - await setupAssetContractControllers({ - options: { - chainId: ChainId.mainnet, - }, - useNetworkControllerProvider: true, - infuraProjectId, - }); - assetsContract.setProvider(provider); + const { messenger } = await setupAssetContractControllers({ + options: { + chainId: ChainId.mainnet, + }, + useNetworkControllerProvider: true, + infuraProjectId, + }); const balancesOnMainnet = await messenger.call( 'AssetsContractController:getBalancesInSingleCall', ERC20_SAI_ADDRESS, [ERC20_SAI_ADDRESS], + 'mainnet', ); expect(balancesOnMainnet).toStrictEqual({ [ERC20_SAI_ADDRESS]: BigNumber.from('0x0733ed8ef4c4a0155d09'), @@ -1052,6 +1046,7 @@ describe('AssetsContractController', () => { 'AssetsContractController:getBalancesInSingleCall', ERC20_SAI_ADDRESS, [ERC20_SAI_ADDRESS], + 'linea-mainnet', ); expect(balancesOnLineaMainnet).toStrictEqual({ [ERC20_SAI_ADDRESS]: BigNumber.from('0xa0155d09733ed8ef4c4'), @@ -1060,14 +1055,8 @@ describe('AssetsContractController', () => { }); it('should not have balance in a single call after switching to network without token detection support', async () => { - const { - assetsContract, - messenger, - network, - provider, - networkClientConfiguration, - } = await setupAssetContractControllers(); - assetsContract.setProvider(provider); + const { messenger, network, networkClientConfiguration } = + await setupAssetContractControllers(); mockNetworkWithDefaultChainId({ networkClientConfiguration, mocks: [ @@ -1125,6 +1114,7 @@ describe('AssetsContractController', () => { `AssetsContractController:getBalancesInSingleCall`, ERC20_SAI_ADDRESS, [ERC20_SAI_ADDRESS], + 'mainnet', ); expect(balances[ERC20_SAI_ADDRESS]).toBeDefined(); @@ -1134,14 +1124,14 @@ describe('AssetsContractController', () => { `AssetsContractController:getBalancesInSingleCall`, ERC20_SAI_ADDRESS, [ERC20_SAI_ADDRESS], + 'sepolia', ); expect(noBalances).toStrictEqual({}); messenger.clearEventSubscriptions('NetworkController:networkDidChange'); }); it('should throw missing provider error when transferring single ERC-1155 when missing provider', async () => { - const { assetsContract, messenger } = await setupAssetContractControllers(); - assetsContract.setProvider(undefined); + const { messenger } = await setupAssetContractControllers(); await expect( messenger.call( `AssetsContractController:transferSingleERC1155`, @@ -1150,15 +1140,15 @@ describe('AssetsContractController', () => { TEST_ACCOUNT_PUBLIC_ADDRESS, ERC1155_ID, '1', + undefined as unknown as string, ), ).rejects.toThrow(MISSING_PROVIDER_ERROR); messenger.clearEventSubscriptions('NetworkController:networkDidChange'); }); it('should throw when ERC1155 function transferSingle is not defined', async () => { - const { assetsContract, messenger, provider, networkClientConfiguration } = + const { messenger, networkClientConfiguration } = await setupAssetContractControllers(); - assetsContract.setProvider(provider); mockNetworkWithDefaultChainId({ networkClientConfiguration, mocks: [ @@ -1188,15 +1178,15 @@ describe('AssetsContractController', () => { TEST_ACCOUNT_PUBLIC_ADDRESS, ERC1155_ID, '1', + 'sepolia', ), ).rejects.toThrow('contract.transferSingle is not a function'); messenger.clearEventSubscriptions('NetworkController:networkDidChange'); }); it('should get the balance of a ERC-1155 NFT for a given address', async () => { - const { assetsContract, messenger, provider, networkClientConfiguration } = + const { messenger, networkClientConfiguration } = await setupAssetContractControllers(); - assetsContract.setProvider(provider); mockNetworkWithDefaultChainId({ networkClientConfiguration, mocks: [ @@ -1223,6 +1213,7 @@ describe('AssetsContractController', () => { TEST_ACCOUNT_PUBLIC_ADDRESS, ERC1155_ADDRESS, ERC1155_ID, + 'sepolia', ); expect(Number(balance)).toBeGreaterThan(0); messenger.clearEventSubscriptions('NetworkController:networkDidChange'); @@ -1236,15 +1227,15 @@ describe('AssetsContractController', () => { TEST_ACCOUNT_PUBLIC_ADDRESS, ERC1155_ADDRESS, ERC1155_ID, + undefined as unknown as string, ), ).rejects.toThrow(MISSING_PROVIDER_ERROR); messenger.clearEventSubscriptions('NetworkController:networkDidChange'); }); it('should get the URI of a ERC-1155 NFT', async () => { - const { assetsContract, messenger, provider, networkClientConfiguration } = + const { messenger, networkClientConfiguration } = await setupAssetContractControllers(); - assetsContract.setProvider(provider); mockNetworkWithDefaultChainId({ networkClientConfiguration, mocks: [ @@ -1271,15 +1262,15 @@ describe('AssetsContractController', () => { `AssetsContractController:getERC1155TokenURI`, ERC1155_ADDRESS, ERC1155_ID, + 'mainnet', ); expect(uri.toLowerCase()).toStrictEqual(expectedUri); messenger.clearEventSubscriptions('NetworkController:networkDidChange'); }); it('should get the staked ethereum balance for an address', async () => { - const { assetsContract, messenger, provider, networkClientConfiguration } = + const { assetsContract, messenger, networkClientConfiguration } = await setupAssetContractControllers(); - assetsContract.setProvider(provider); mockNetworkWithDefaultChainId({ networkClientConfiguration, @@ -1323,6 +1314,7 @@ describe('AssetsContractController', () => { const balance = await assetsContract.getStakedBalanceForChain( TEST_ACCOUNT_PUBLIC_ADDRESS, + 'mainnet', ); // exchange rate shares = 1e18 @@ -1339,9 +1331,8 @@ describe('AssetsContractController', () => { it('should return default of zero hex as staked ethereum balance if user has no shares', async () => { const errorSpy = jest.spyOn(console, 'error'); - const { assetsContract, messenger, provider, networkClientConfiguration } = + const { assetsContract, messenger, networkClientConfiguration } = await setupAssetContractControllers(); - assetsContract.setProvider(provider); mockNetworkWithDefaultChainId({ networkClientConfiguration, @@ -1368,6 +1359,7 @@ describe('AssetsContractController', () => { const balance = await assetsContract.getStakedBalanceForChain( TEST_ACCOUNT_PUBLIC_ADDRESS, + 'mainnet', ); expect(balance).toBeDefined(); @@ -1386,12 +1378,11 @@ describe('AssetsContractController', () => { .mockImplementationOnce((e) => { error = e; }); - const { assetsContract, messenger, provider } = - await setupAssetContractControllers(); - assetsContract.setProvider(provider); + const { assetsContract, messenger } = await setupAssetContractControllers(); const balance = await assetsContract.getStakedBalanceForChain( TEST_ACCOUNT_PUBLIC_ADDRESS, + 'mainnet', ); expect(balance).toBeDefined(); @@ -1407,7 +1398,10 @@ describe('AssetsContractController', () => { it('should throw missing provider error when getting staked ethereum balance and missing provider', async () => { const { assetsContract, messenger } = await setupAssetContractControllers(); await expect( - assetsContract.getStakedBalanceForChain(TEST_ACCOUNT_PUBLIC_ADDRESS), + assetsContract.getStakedBalanceForChain( + TEST_ACCOUNT_PUBLIC_ADDRESS, + undefined as unknown as string, + ), ).rejects.toThrow(MISSING_PROVIDER_ERROR); messenger.clearEventSubscriptions('NetworkController:networkDidChange'); }); diff --git a/packages/assets-controllers/src/AssetsContractController.ts b/packages/assets-controllers/src/AssetsContractController.ts index 5e8a9398d67..b593abdb3b2 100644 --- a/packages/assets-controllers/src/AssetsContractController.ts +++ b/packages/assets-controllers/src/AssetsContractController.ts @@ -14,7 +14,6 @@ import type { NetworkControllerGetSelectedNetworkClientAction, NetworkControllerGetStateAction, NetworkControllerNetworkDidChangeEvent, - Provider, } from '@metamask/network-controller'; import type { PreferencesControllerStateChangeEvent } from '@metamask/preferences-controller'; import { getKnownPropertyNames, type Hex } from '@metamask/utils'; @@ -219,11 +218,9 @@ export class AssetsContractController { protected messagingSystem: AssetsContractControllerMessenger; - #provider: Provider | undefined; - #ipfsGateway: string; - #chainId: Hex; + // #chainId: Hex; /** * Creates a AssetsContractController instance. @@ -234,15 +231,14 @@ export class AssetsContractController { */ constructor({ messenger, - chainId: initialChainId, + // chainId: initialChainId, }: { messenger: AssetsContractControllerMessenger; chainId: Hex; }) { this.messagingSystem = messenger; - this.#provider = undefined; this.#ipfsGateway = IPFS_DEFAULT_GATEWAY_URL; - this.#chainId = initialChainId; + // this.#chainId = initialChainId; this.#registerActionHandlers(); this.#registerEventSubscriptions(); @@ -284,37 +280,15 @@ export class AssetsContractController { this.#ipfsGateway = ipfsGateway; }, ); - - this.messagingSystem.subscribe( - `NetworkController:networkDidChange`, - ({ selectedNetworkClientId }) => { - const chainId = this.#getCorrectChainId(selectedNetworkClientId); - - if (this.#chainId !== chainId) { - this.#chainId = chainId; - // @ts-expect-error TODO: remove this annotation once the `Eip1193Provider` class is released - this.#provider = this.#getCorrectProvider(); - } - }, - ); - } - - /** - * Sets a new provider. - * - * @param provider - Provider used to create a new underlying Web3 instance - */ - setProvider(provider: Provider | undefined) { - this.#provider = provider; } get ipfsGateway() { return this.#ipfsGateway; } - get chainId() { - return this.#chainId; - } + // get chainId() { + // return this.#chainId; + // } /** * Get the relevant provider instance. @@ -322,20 +296,16 @@ export class AssetsContractController { * @param networkClientId - Network Client ID. * @returns Web3Provider instance. */ - #getCorrectProvider(networkClientId?: NetworkClientId): Web3Provider { - const provider = networkClientId - ? this.messagingSystem.call( - `NetworkController:getNetworkClientById`, - networkClientId, - ).provider - : (this.messagingSystem.call('NetworkController:getSelectedNetworkClient') - ?.provider ?? this.#provider); - - if (provider === undefined) { + #getCorrectProvider(networkClientId: NetworkClientId): Web3Provider { + try { + const { provider } = this.messagingSystem.call( + `NetworkController:getNetworkClientById`, + networkClientId, + ); + return new Web3Provider(provider); + } catch { throw new Error(MISSING_PROVIDER_ERROR); } - - return new Web3Provider(provider); } /** @@ -344,15 +314,13 @@ export class AssetsContractController { * @param networkClientId - Network Client ID used to get the provider. * @returns Hex chain ID. */ - #getCorrectChainId(networkClientId?: NetworkClientId): Hex { - if (networkClientId) { - const networkClientConfiguration = this.messagingSystem.call( - 'NetworkController:getNetworkConfigurationByNetworkClientId', - networkClientId, - ); - if (networkClientConfiguration) { - return networkClientConfiguration.chainId; - } + #getCorrectChainId(networkClientId: NetworkClientId): Hex { + const networkClientConfiguration = this.messagingSystem.call( + 'NetworkController:getNetworkConfigurationByNetworkClientId', + networkClientId, + ); + if (networkClientConfiguration) { + return networkClientConfiguration.chainId; } const { selectedNetworkClientId } = this.messagingSystem.call( 'NetworkController:getState', @@ -361,7 +329,7 @@ export class AssetsContractController { 'NetworkController:getNetworkClientById', selectedNetworkClientId, ); - return networkClient.configuration?.chainId ?? this.#chainId; + return networkClient.configuration.chainId; } /** @@ -370,7 +338,7 @@ export class AssetsContractController { * @param networkClientId - Network Client ID used to get the provider. * @returns ERC20Standard instance. */ - getERC20Standard(networkClientId?: NetworkClientId): ERC20Standard { + getERC20Standard(networkClientId: NetworkClientId): ERC20Standard { const provider = this.#getCorrectProvider(networkClientId); return new ERC20Standard(provider); } @@ -381,7 +349,7 @@ export class AssetsContractController { * @param networkClientId - Network Client ID used to get the provider. * @returns ERC721Standard instance. */ - getERC721Standard(networkClientId?: NetworkClientId): ERC721Standard { + getERC721Standard(networkClientId: NetworkClientId): ERC721Standard { const provider = this.#getCorrectProvider(networkClientId); return new ERC721Standard(provider); } @@ -392,7 +360,7 @@ export class AssetsContractController { * @param networkClientId - Network Client ID used to get the provider. * @returns ERC1155Standard instance. */ - getERC1155Standard(networkClientId?: NetworkClientId): ERC1155Standard { + getERC1155Standard(networkClientId: NetworkClientId): ERC1155Standard { const provider = this.#getCorrectProvider(networkClientId); return new ERC1155Standard(provider); } @@ -408,7 +376,7 @@ export class AssetsContractController { async getERC20BalanceOf( address: string, selectedAddress: string, - networkClientId?: NetworkClientId, + networkClientId: NetworkClientId, ): Promise { const erc20Standard = this.getERC20Standard(networkClientId); return erc20Standard.getBalanceOf(address, selectedAddress); @@ -423,7 +391,7 @@ export class AssetsContractController { */ async getERC20TokenDecimals( address: string, - networkClientId?: NetworkClientId, + networkClientId: NetworkClientId, ): Promise { const erc20Standard = this.getERC20Standard(networkClientId); return erc20Standard.getTokenDecimals(address); @@ -438,7 +406,7 @@ export class AssetsContractController { */ async getERC20TokenName( address: string, - networkClientId?: NetworkClientId, + networkClientId: NetworkClientId, ): Promise { const erc20Standard = this.getERC20Standard(networkClientId); return erc20Standard.getTokenName(address); @@ -457,7 +425,7 @@ export class AssetsContractController { address: string, selectedAddress: string, index: number, - networkClientId?: NetworkClientId, + networkClientId: NetworkClientId, ): Promise { const erc721Standard = this.getERC721Standard(networkClientId); return erc721Standard.getNftTokenId(address, selectedAddress, index); @@ -467,16 +435,16 @@ export class AssetsContractController { * Enumerate assets assigned to an owner. * * @param tokenAddress - ERC721 asset contract address. + * @param networkClientId - Network Client ID to fetch the provider with. * @param userAddress - Current account public address. * @param tokenId - ERC721 asset identifier. - * @param networkClientId - Network Client ID to fetch the provider with. * @returns Promise resolving to an object containing the token standard and a set of details which depend on which standard the token supports. */ async getTokenStandardAndDetails( tokenAddress: string, + networkClientId: NetworkClientId, userAddress?: string, tokenId?: string, - networkClientId?: NetworkClientId, ): Promise<{ standard: string; tokenURI?: string | undefined; @@ -540,7 +508,7 @@ export class AssetsContractController { async getERC721TokenURI( address: string, tokenId: string, - networkClientId?: NetworkClientId, + networkClientId: NetworkClientId, ): Promise { const erc721Standard = this.getERC721Standard(networkClientId); return erc721Standard.getTokenURI(address, tokenId); @@ -555,7 +523,7 @@ export class AssetsContractController { */ async getERC721AssetName( address: string, - networkClientId?: NetworkClientId, + networkClientId: NetworkClientId, ): Promise { const erc721Standard = this.getERC721Standard(networkClientId); return erc721Standard.getAssetName(address); @@ -570,7 +538,7 @@ export class AssetsContractController { */ async getERC721AssetSymbol( address: string, - networkClientId?: NetworkClientId, + networkClientId: NetworkClientId, ): Promise { const erc721Standard = this.getERC721Standard(networkClientId); return erc721Standard.getAssetSymbol(address); @@ -587,7 +555,7 @@ export class AssetsContractController { async getERC721OwnerOf( address: string, tokenId: string, - networkClientId?: NetworkClientId, + networkClientId: NetworkClientId, ): Promise { const erc721Standard = this.getERC721Standard(networkClientId); return erc721Standard.getOwnerOf(address, tokenId); @@ -604,7 +572,7 @@ export class AssetsContractController { async getERC1155TokenURI( address: string, tokenId: string, - networkClientId?: NetworkClientId, + networkClientId: NetworkClientId, ): Promise { const erc1155Standard = this.getERC1155Standard(networkClientId); return erc1155Standard.getTokenURI(address, tokenId); @@ -623,7 +591,7 @@ export class AssetsContractController { userAddress: string, nftAddress: string, nftId: string, - networkClientId?: NetworkClientId, + networkClientId: NetworkClientId, ): Promise { const erc1155Standard = this.getERC1155Standard(networkClientId); return erc1155Standard.getBalanceOf(nftAddress, userAddress, nftId); @@ -646,7 +614,7 @@ export class AssetsContractController { recipientAddress: string, nftId: string, qty: string, - networkClientId?: NetworkClientId, + networkClientId: NetworkClientId, ): Promise { const erc1155Standard = this.getERC1155Standard(networkClientId); return erc1155Standard.transferSingle( @@ -670,7 +638,7 @@ export class AssetsContractController { async getBalancesInSingleCall( selectedAddress: string, tokensToDetect: string[], - networkClientId?: NetworkClientId, + networkClientId: NetworkClientId, ) { const chainId = this.#getCorrectChainId(networkClientId); const provider = this.#getCorrectProvider(networkClientId); @@ -712,7 +680,7 @@ export class AssetsContractController { */ async getStakedBalanceForChain( address: string, - networkClientId?: NetworkClientId, + networkClientId: NetworkClientId, ): Promise { const chainId = this.#getCorrectChainId(networkClientId); const provider = this.#getCorrectProvider(networkClientId); diff --git a/packages/assets-controllers/src/NftController.test.ts b/packages/assets-controllers/src/NftController.test.ts index 53244cb3f77..1c3fbf0cce4 100644 --- a/packages/assets-controllers/src/NftController.test.ts +++ b/packages/assets-controllers/src/NftController.test.ts @@ -18,7 +18,7 @@ import { ERC20, NetworksTicker, NFT_API_BASE_URL, - InfuraNetworkType, + // //InfuraNetworkType, convertHexToDecimal, } from '@metamask/controller-utils'; import type { InternalAccount } from '@metamask/keyring-internal-api'; @@ -26,25 +26,17 @@ import type { NetworkClientConfiguration, NetworkClientId, } from '@metamask/network-controller'; -import { getDefaultNetworkControllerState } from '@metamask/network-controller'; +// import { getDefaultNetworkControllerState } from '@metamask/network-controller'; import { getDefaultPreferencesState, type PreferencesState, } from '@metamask/preferences-controller'; +import type { Hex } from '@metamask/utils'; import BN from 'bn.js'; import nock from 'nock'; import * as sinon from 'sinon'; import { v4 } from 'uuid'; -import { createMockInternalAccount } from '../../accounts-controller/src/tests/mocks'; -import type { - ExtractAvailableAction, - ExtractAvailableEvent, -} from '../../base-controller/tests/helpers'; -import { - buildCustomNetworkClientConfiguration, - buildMockGetNetworkClientById, -} from '../../network-controller/tests/helpers'; import type { AssetsContractControllerGetERC1155BalanceOfAction, AssetsContractControllerGetERC1155TokenURIAction, @@ -61,8 +53,19 @@ import type { NftControllerMessenger, AllowedActions as NftControllerAllowedActions, AllowedEvents as NftControllerAllowedEvents, + NFTStandardType, } from './NftController'; import { NftController } from './NftController'; +import { createMockInternalAccount } from '../../accounts-controller/src/tests/mocks'; +import type { + ExtractAvailableAction, + ExtractAvailableEvent, +} from '../../base-controller/tests/helpers'; +import { + buildCustomNetworkClientConfiguration, + buildMockGetNetworkClientByChainId, + buildMockGetNetworkClientById, +} from '../../network-controller/tests/helpers'; const CRYPTOPUNK_ADDRESS = '0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB'; const ERC721_KUDOSADDRESS = '0x2aEa4Add166EBf38b63d09a75dE1a7b94Aa24163'; @@ -145,6 +148,7 @@ jest.mock('uuid', () => { * @param args.mockNetworkClientConfigurationsByNetworkClientId - Used to construct * mock versions of network clients and ultimately mock the * `NetworkController:getNetworkClientById` action. + * @param args.mockGetNetworkClientIdByChainId - Used to construct mock versions of the * @param args.getAccount - Used to construct mock versions of the * `AccountsController:getAccount` action. * @param args.getSelectedAccount - Used to construct mock versions of the @@ -164,6 +168,7 @@ function setupController({ getSelectedAccount, mockNetworkClientConfigurationsByNetworkClientId = {}, defaultSelectedAccount = OWNER_ACCOUNT, + mockGetNetworkClientIdByChainId = {}, }: { options?: Partial[0]>; getERC721AssetName?: jest.Mock< @@ -203,6 +208,7 @@ function setupController({ NetworkClientConfiguration >; defaultSelectedAccount?: InternalAccount; + mockGetNetworkClientIdByChainId?: Record; } = {}) { const messenger = new Messenger< | ExtractAvailableAction @@ -217,10 +223,17 @@ function setupController({ const getNetworkClientById = buildMockGetNetworkClientById( mockNetworkClientConfigurationsByNetworkClientId, ); + const getNetworkClientIdByChainId = buildMockGetNetworkClientByChainId( + mockGetNetworkClientIdByChainId, + ); messenger.registerActionHandler( 'NetworkController:getNetworkClientById', getNetworkClientById, ); + messenger.registerActionHandler( + 'NetworkController:getNetworkClientIdByChainId', + getNetworkClientIdByChainId, + ); const mockGetAccount = getAccount ?? jest.fn().mockReturnValue(defaultSelectedAccount); @@ -326,12 +339,12 @@ function setupController({ 'AssetsContractController:getERC721OwnerOf', 'AssetsContractController:getERC1155BalanceOf', 'AssetsContractController:getERC1155TokenURI', + 'NetworkController:getNetworkClientIdByChainId', ], allowedEvents: [ 'AccountsController:selectedAccountChange', 'AccountsController:selectedEvmAccountChange', 'PreferencesController:stateChange', - 'NetworkController:networkDidChange', ], }); @@ -347,17 +360,6 @@ function setupController({ messenger.publish('PreferencesController:stateChange', state, []); }; - const changeNetwork = ({ - selectedNetworkClientId, - }: { - selectedNetworkClientId: NetworkClientId; - }) => { - messenger.publish('NetworkController:networkDidChange', { - ...getDefaultNetworkControllerState(), - selectedNetworkClientId, - }); - }; - triggerPreferencesStateChange({ ...getDefaultPreferencesState(), openSeaEnabled: true, @@ -378,7 +380,6 @@ function setupController({ nftController, messenger, approvalController, - changeNetwork, triggerPreferencesStateChange, triggerSelectedAccountChange, mockGetAccount, @@ -461,39 +462,62 @@ describe('NftController', () => { tokenId: ERC1155_NFT_ID, }; + it('should error if passed no networkClientId', async function () { + const { nftController } = setupController(); + const networkClientId = undefined; + + const erc721Result = nftController.watchNft( + ERC721_NFT, + ERC721, + 'https://testdapp.com', + networkClientId as unknown as string, + ); + await expect(erc721Result).rejects.toThrow( + 'Network client id is required', + ); + }); + it('should error if passed no type', async function () { const { nftController } = setupController(); const type = undefined; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore-next-line - const erc721Result = nftController.watchNft(ERC721_NFT, type); + const erc721Result = nftController.watchNft( + ERC721_NFT, + type as unknown as NFTStandardType, + 'https://test-dapp.com', + 'mainnet', + ); await expect(erc721Result).rejects.toThrow('Asset type is required'); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore-next-line - const erc1155Result = nftController.watchNft(ERC1155_NFT, type); + const erc1155Result = nftController.watchNft( + ERC1155_NFT, + type as unknown as NFTStandardType, + 'https://test-dapp.com', + 'mainnet', + ); await expect(erc1155Result).rejects.toThrow('Asset type is required'); }); it('should error if asset type is not supported', async function () { const { nftController } = setupController(); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore-next-line - const erc721Result = nftController.watchNft(ERC721_NFT, ERC20); + const erc721Result = nftController.watchNft( + ERC721_NFT, + ERC20 as unknown as NFTStandardType, + 'https://test-dapp.com', + 'mainnet', + ); await expect(erc721Result).rejects.toThrow( - // TODO: Either fix this lint violation or explain why it's necessary to ignore. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions `Non NFT asset type ${ERC20} not supported by watchNft`, ); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore-next-line - const erc1155Result = nftController.watchNft(ERC1155_NFT, ERC20); + const erc1155Result = nftController.watchNft( + ERC1155_NFT, + ERC20 as unknown as NFTStandardType, + 'https://test-dapp.com', + 'mainnet', + ); await expect(erc1155Result).rejects.toThrow( - // TODO: Either fix this lint violation or explain why it's necessary to ignore. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions `Non NFT asset type ${ERC20} not supported by watchNft`, ); }); @@ -518,7 +542,12 @@ describe('NftController', () => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore-next-line - const erc721Result = nftController.watchNft(ERC721_NFT, ERC1155); + const erc721Result = nftController.watchNft( + ERC721_NFT, + ERC1155, + 'https://test-dapp.com', + 'mainnet', + ); await expect(erc721Result).rejects.toThrow( // TODO: Either fix this lint violation or explain why it's necessary to ignore. // eslint-disable-next-line @typescript-eslint/restrict-template-expressions @@ -529,13 +558,16 @@ describe('NftController', () => { it('should error if address is not defined', async function () { const { nftController } = setupController(); const assetWithNoAddress = { - address: undefined, + address: undefined as unknown as string, tokenId: ERC721_NFT_ID, }; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore-next-line - const result = nftController.watchNft(assetWithNoAddress, ERC721); + const result = nftController.watchNft( + assetWithNoAddress, + ERC721, + 'https://testdapp.com', + 'mainnet', + ); await expect(result).rejects.toThrow( 'Both address and tokenId are required', ); @@ -545,12 +577,15 @@ describe('NftController', () => { const { nftController } = setupController(); const assetWithNoAddress = { address: ERC721_NFT_ADDRESS, - tokenId: undefined, + tokenId: undefined as unknown as string, }; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore-next-line - const result = nftController.watchNft(assetWithNoAddress, ERC721); + const result = nftController.watchNft( + assetWithNoAddress, + ERC721, + 'https://test-dapp.com', + 'mainnet', + ); await expect(result).rejects.toThrow( 'Both address and tokenId are required', ); @@ -563,9 +598,12 @@ describe('NftController', () => { tokenId: '123abc', }; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore-next-line - const result = nftController.watchNft(assetWithNumericTokenId, ERC721); + const result = nftController.watchNft( + assetWithNumericTokenId, + ERC721, + 'https://test-dapp.com', + 'mainnet', + ); await expect(result).rejects.toThrow('Invalid tokenId'); }); @@ -579,6 +617,7 @@ describe('NftController', () => { assetWithInvalidAddress, ERC721, 'https://test-dapp.com', + 'mainnet', ); await expect(result).rejects.toThrow('Invalid address'); }); @@ -591,7 +630,12 @@ describe('NftController', () => { const callActionSpy = jest.spyOn(messenger, 'call'); await expect(() => - nftController.watchNft(ERC721_NFT, ERC721, 'https://test-dapp.com'), + nftController.watchNft( + ERC721_NFT, + ERC721, + 'https://test-dapp.com', + 'mainnet', + ), ).rejects.toThrow('Suggested NFT is not owned by the selected account'); // First call is getInternalAccount. Second call is the approval request. expect(callActionSpy).not.toHaveBeenNthCalledWith( @@ -609,6 +653,7 @@ describe('NftController', () => { ERC721_NFT, ERC721, 'https://test-dapp.com', + 'mainnet', ); } catch (err) { // eslint-disable-next-line jest/no-conditional-expect @@ -624,7 +669,12 @@ describe('NftController', () => { const callActionSpy = jest.spyOn(messenger, 'call'); await expect(() => - nftController.watchNft(ERC1155_NFT, ERC1155, 'https://test-dapp.com'), + nftController.watchNft( + ERC1155_NFT, + ERC1155, + 'https://test-dapp.com', + 'mainnet', + ), ).rejects.toThrow('Suggested NFT is not owned by the selected account'); // First call is to get InternalAccount expect(callActionSpy).toHaveBeenNthCalledWith( @@ -659,6 +709,7 @@ describe('NftController', () => { getERC721AssetName: jest.fn().mockResolvedValue('testERC721Name'), getERC721AssetSymbol: jest.fn().mockResolvedValue('testERC721Symbol'), }); + triggerSelectedAccountChange(OWNER_ACCOUNT); triggerPreferencesStateChange({ ...getDefaultPreferencesState(), @@ -678,21 +729,65 @@ describe('NftController', () => { .mockReturnValueOnce(OWNER_ACCOUNT) // 2. `AssetsContractController:getERC721OwnerOf` .mockResolvedValueOnce(OWNER_ADDRESS) + // 3. `NetworkClientController:getNetworkClientById` + .mockReturnValueOnce({ + configuration: { + type: 'infura', + network: 'mainnet', + failoverRpcUrls: [], + infuraProjectId: 'test-infura-project-id', + chainId: '0x1', + ticker: 'ETH', + rpcUrl: 'https://mainnet.infura.io/v3/test-infura-project-id', + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any) // 3. `AssetsContractController:getERC721TokenURI` .mockResolvedValueOnce('https://testtokenuri.com') // 4. `ApprovalController:addRequest` .mockResolvedValueOnce({}) // 5. `AccountsController:getAccount` .mockReturnValueOnce(OWNER_ACCOUNT) + // 3. `NetworkClientController:getNetworkClientById` + .mockReturnValueOnce({ + configuration: { + type: 'infura', + network: 'mainnet', + failoverRpcUrls: [], + infuraProjectId: 'test-infura-project-id', + chainId: '0x1', + ticker: 'ETH', + rpcUrl: 'https://mainnet.infura.io/v3/test-infura-project-id', + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any) // 6. `AssetsContractController:getERC721AssetName` .mockResolvedValueOnce('testERC721Name') // 7. `AssetsContractController:getERC721AssetSymbol` - .mockResolvedValueOnce('testERC721Symbol'); + .mockResolvedValueOnce('testERC721Symbol') + // 3. `NetworkClientController:getNetworkClientById` + .mockReturnValueOnce({ + configuration: { + type: 'infura', + network: 'mainnet', + failoverRpcUrls: [], + infuraProjectId: 'test-infura-project-id', + chainId: '0x1', + ticker: 'ETH', + rpcUrl: 'https://mainnet.infura.io/v3/test-infura-project-id', + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any); - await nftController.watchNft(ERC721_NFT, ERC721, 'https://test-dapp.com'); - expect(callActionSpy).toHaveBeenCalledTimes(7); + await nftController.watchNft( + ERC721_NFT, + ERC721, + 'https://test-dapp.com', + 'mainnet', + ); + expect(callActionSpy).toHaveBeenCalledTimes(10); expect(callActionSpy).toHaveBeenNthCalledWith( - 4, + 5, 'ApprovalController:addRequest', { id: requestId, @@ -760,21 +855,65 @@ describe('NftController', () => { .mockReturnValueOnce(OWNER_ACCOUNT) // 2. `AssetsContractController:getERC721OwnerOf` .mockResolvedValueOnce(OWNER_ADDRESS) - // 3. `AssetsContractController:getERC721TokenURI` + // 3. `NetworkClientController:getNetworkClientById` + .mockReturnValueOnce({ + configuration: { + type: 'infura', + network: 'mainnet', + failoverRpcUrls: [], + infuraProjectId: 'test-infura-project-id', + chainId: '0x1', + ticker: 'ETH', + rpcUrl: 'https://mainnet.infura.io/v3/test-infura-project-id', + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any) + // 4. `AssetsContractController:getERC721TokenURI` .mockResolvedValueOnce('https://testtokenuri.com') - // 4. `ApprovalController:addRequest` + // 5. `ApprovalController:addRequest` .mockResolvedValueOnce({}) - // 5. `AccountsController:getAccount` + // 6. `AccountsController:getAccount` .mockReturnValueOnce(OWNER_ACCOUNT) - // 6. `AssetsContractController:getERC721AssetName` + // 7. `NetworkClientController:getNetworkClientById` + .mockReturnValueOnce({ + configuration: { + type: 'infura', + network: 'mainnet', + failoverRpcUrls: [], + infuraProjectId: 'test-infura-project-id', + chainId: '0x1', + ticker: 'ETH', + rpcUrl: 'https://mainnet.infura.io/v3/test-infura-project-id', + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any) + // 8. `AssetsContractController:getERC721AssetName` .mockResolvedValueOnce('testERC721Name') - // 7. `AssetsContractController:getERC721AssetSymbol` - .mockResolvedValueOnce('testERC721Symbol'); + // 9. `AssetsContractController:getERC721AssetSymbol` + .mockResolvedValueOnce('testERC721Symbol') + // 10. `NetworkClientController:getNetworkClientById` + .mockReturnValueOnce({ + configuration: { + type: 'infura', + network: 'mainnet', + failoverRpcUrls: [], + infuraProjectId: 'test-infura-project-id', + chainId: '0x1', + ticker: 'ETH', + rpcUrl: 'https://mainnet.infura.io/v3/test-infura-project-id', + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any); - await nftController.watchNft(ERC721_NFT, ERC721, 'https://test-dapp.com'); - expect(callActionSpy).toHaveBeenCalledTimes(7); + await nftController.watchNft( + ERC721_NFT, + ERC721, + 'https://test-dapp.com', + 'mainnet', + ); + expect(callActionSpy).toHaveBeenCalledTimes(10); expect(callActionSpy).toHaveBeenNthCalledWith( - 4, + 5, 'ApprovalController:addRequest', { id: requestId, @@ -842,21 +981,65 @@ describe('NftController', () => { .mockReturnValueOnce(OWNER_ACCOUNT) // 2. `AssetsContractController:getERC721OwnerOf` .mockResolvedValueOnce(OWNER_ADDRESS) - // 3. `AssetsContractController:getERC721TokenURI` + // 3. `NetworkClientController:getNetworkClientById` + .mockReturnValueOnce({ + configuration: { + type: 'infura', + network: 'mainnet', + failoverRpcUrls: [], + infuraProjectId: 'test-infura-project-id', + chainId: '0x1', + ticker: 'ETH', + rpcUrl: 'https://mainnet.infura.io/v3/test-infura-project-id', + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any) + // 4. `AssetsContractController:getERC721TokenURI` .mockResolvedValueOnce('https://testtokenuri.com') - // 4. `ApprovalController:addRequest` + // 5. `ApprovalController:addRequest` .mockResolvedValueOnce({}) - // 5. `AccountsController:getAccount` + // 6. `AccountsController:getAccount` .mockReturnValueOnce(OWNER_ACCOUNT) - // 6. `AssetsContractController:getERC721AssetName` + // 7. `NetworkClientController:getNetworkClientById` + .mockReturnValueOnce({ + configuration: { + type: 'infura', + network: 'mainnet', + failoverRpcUrls: [], + infuraProjectId: 'test-infura-project-id', + chainId: '0x1', + ticker: 'ETH', + rpcUrl: 'https://mainnet.infura.io/v3/test-infura-project-id', + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any) + // 8. `AssetsContractController:getERC721AssetName` .mockResolvedValueOnce('testERC721Name') - // 7. `AssetsContractController:getERC721AssetSymbol` - .mockResolvedValueOnce('testERC721Symbol'); + // 9. `AssetsContractController:getERC721AssetSymbol` + .mockResolvedValueOnce('testERC721Symbol') + // 10. `NetworkClientController:getNetworkClientById` + .mockReturnValueOnce({ + configuration: { + type: 'infura', + network: 'mainnet', + failoverRpcUrls: [], + infuraProjectId: 'test-infura-project-id', + chainId: '0x1', + ticker: 'ETH', + rpcUrl: 'https://mainnet.infura.io/v3/test-infura-project-id', + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any); - await nftController.watchNft(ERC721_NFT, ERC721, 'https://test-dapp.com'); - expect(callActionSpy).toHaveBeenCalledTimes(7); + await nftController.watchNft( + ERC721_NFT, + ERC721, + 'https://test-dapp.com', + 'mainnet', + ); + expect(callActionSpy).toHaveBeenCalledTimes(10); expect(callActionSpy).toHaveBeenNthCalledWith( - 4, + 5, 'ApprovalController:addRequest', { id: requestId, @@ -925,21 +1108,65 @@ describe('NftController', () => { .mockReturnValueOnce(OWNER_ACCOUNT) // 2. `AssetsContractController:getERC721OwnerOf` .mockResolvedValueOnce(OWNER_ADDRESS) - // 3. `AssetsContractController:getERC721TokenURI` + // 3. `NetworkClientController:getNetworkClientById` + .mockReturnValueOnce({ + configuration: { + type: 'infura', + network: 'mainnet', + failoverRpcUrls: [], + infuraProjectId: 'test-infura-project-id', + chainId: '0x1', + ticker: 'ETH', + rpcUrl: 'https://mainnet.infura.io/v3/test-infura-project-id', + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any) + // 4. `AssetsContractController:getERC721TokenURI` .mockResolvedValueOnce('https://testtokenuri.com') - // 4. `ApprovalController:addRequest` + // 5. `ApprovalController:addRequest` .mockResolvedValueOnce({}) - // 5. `AccountsController:getAccount` + // 6. `AccountsController:getAccount` .mockReturnValueOnce(OWNER_ACCOUNT) - // 6. `AssetsContractController:getERC721AssetName` + // 7. `NetworkClientController:getNetworkClientById` + .mockReturnValueOnce({ + configuration: { + type: 'infura', + network: 'mainnet', + failoverRpcUrls: [], + infuraProjectId: 'test-infura-project-id', + chainId: '0x1', + ticker: 'ETH', + rpcUrl: 'https://mainnet.infura.io/v3/test-infura-project-id', + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any) + // 8. `AssetsContractController:getERC721AssetName` .mockResolvedValueOnce('testERC721Name') - // 7. `AssetsContractController:getERC721AssetSymbol` - .mockResolvedValueOnce('testERC721Symbol'); + // 9. `AssetsContractController:getERC721AssetSymbol` + .mockResolvedValueOnce('testERC721Symbol') + // 10. `NetworkClientController:getNetworkClientById` + .mockReturnValueOnce({ + configuration: { + type: 'infura', + network: 'mainnet', + failoverRpcUrls: [], + infuraProjectId: 'test-infura-project-id', + chainId: '0x1', + ticker: 'ETH', + rpcUrl: 'https://mainnet.infura.io/v3/test-infura-project-id', + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any); - await nftController.watchNft(ERC721_NFT, ERC721, 'https://test-dapp.com'); - expect(callActionSpy).toHaveBeenCalledTimes(7); + await nftController.watchNft( + ERC721_NFT, + ERC721, + 'https://test-dapp.com', + 'mainnet', + ); + expect(callActionSpy).toHaveBeenCalledTimes(10); expect(callActionSpy).toHaveBeenNthCalledWith( - 4, + 5, 'ApprovalController:addRequest', { id: requestId, @@ -1014,27 +1241,67 @@ describe('NftController', () => { .mockRejectedValueOnce(new Error('Not an ERC721 contract')) // 3. `AssetsContractController:getERC1155BalanceOf` .mockResolvedValueOnce(new BN(1)) - // 4. `AssetsContractController:getERC721TokenURI` + // 4. `NetworkClientController:getNetworkClientById` + .mockReturnValueOnce({ + configuration: { + type: 'infura', + network: 'mainnet', + failoverRpcUrls: [], + infuraProjectId: 'test-infura-project-id', + chainId: '0x1', + ticker: 'ETH', + rpcUrl: 'https://mainnet.infura.io/v3/test-infura-project-id', + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any) + // 5. `AssetsContractController:getERC721TokenURI` .mockRejectedValueOnce(new Error('Not an ERC721 contract')) - // 5. `AssetsContractController:getERC1155TokenURI` + // 6. `AssetsContractController:getERC1155TokenURI` .mockResolvedValueOnce('https://testtokenuri.com') - // 6. `ApprovalController:addRequest` + // 7. `ApprovalController:addRequest` .mockResolvedValueOnce({}) - // 7. `AccountsController:getAccount` + // 8. `AccountsController:getAccount` .mockReturnValueOnce(OWNER_ACCOUNT) - // 8. `AssetsContractController:getERC721AssetName` + // 9. `NetworkClientController:getNetworkClientById` + .mockReturnValueOnce({ + configuration: { + type: 'infura', + network: 'mainnet', + failoverRpcUrls: [], + infuraProjectId: 'test-infura-project-id', + chainId: '0x1', + ticker: 'ETH', + rpcUrl: 'https://mainnet.infura.io/v3/test-infura-project-id', + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any) + // 10. `AssetsContractController:getERC721AssetName` .mockRejectedValueOnce(new Error('Not an ERC721 contract')) - // 9. `AssetsContractController:getERC721AssetSymbol` - .mockRejectedValueOnce(new Error('Not an ERC721 contract')); + // 11. `AssetsContractController:getERC721AssetSymbol` + .mockRejectedValueOnce(new Error('Not an ERC721 contract')) + // 12. `NetworkClientController:getNetworkClientById` + .mockReturnValueOnce({ + configuration: { + type: 'infura', + network: 'mainnet', + failoverRpcUrls: [], + infuraProjectId: 'test-infura-project-id', + chainId: '0x1', + ticker: 'ETH', + rpcUrl: 'https://mainnet.infura.io/v3/test-infura-project-id', + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any); await nftController.watchNft( ERC1155_NFT, ERC1155, 'https://etherscan.io', + 'mainnet', ); - expect(callActionSpy).toHaveBeenCalledTimes(9); + expect(callActionSpy).toHaveBeenCalledTimes(12); expect(callActionSpy).toHaveBeenNthCalledWith( - 6, + 7, 'ApprovalController:addRequest', { id: requestId, @@ -1103,6 +1370,19 @@ describe('NftController', () => { .mockRejectedValueOnce(new Error('Not an ERC721 contract')) // 3. `AssetsContractController:getERC1155BalanceOf` .mockResolvedValueOnce(new BN(1)) + // 4. `NetworkClientController:getNetworkClientById` + .mockReturnValueOnce({ + configuration: { + type: 'infura', + network: 'mainnet', + failoverRpcUrls: [], + infuraProjectId: 'test-infura-project-id', + chainId: '0x1', + ticker: 'ETH', + rpcUrl: 'https://mainnet.infura.io/v3/test-infura-project-id', + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any) // 4. `AssetsContractController:getERC721TokenURI` .mockRejectedValueOnce(new Error('Not an ERC721 contract')) // 5. `AssetsContractController:getERC1155TokenURI` @@ -1111,20 +1391,47 @@ describe('NftController', () => { .mockResolvedValueOnce({}) // 7. `AccountsController:getAccount` .mockReturnValueOnce(OWNER_ACCOUNT) + // 9. `NetworkClientController:getNetworkClientById` + .mockReturnValueOnce({ + configuration: { + type: 'infura', + network: 'mainnet', + failoverRpcUrls: [], + infuraProjectId: 'test-infura-project-id', + chainId: '0x1', + ticker: 'ETH', + rpcUrl: 'https://mainnet.infura.io/v3/test-infura-project-id', + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any) // 8. `AssetsContractController:getERC721AssetName` .mockRejectedValueOnce(new Error('Not an ERC721 contract')) // 9. `AssetsContractController:getERC721AssetSymbol` - .mockRejectedValueOnce(new Error('Not an ERC721 contract')); + .mockRejectedValueOnce(new Error('Not an ERC721 contract')) + // 9. `NetworkClientController:getNetworkClientById` + .mockReturnValueOnce({ + configuration: { + type: 'infura', + network: 'mainnet', + failoverRpcUrls: [], + infuraProjectId: 'test-infura-project-id', + chainId: '0x1', + ticker: 'ETH', + rpcUrl: 'https://mainnet.infura.io/v3/test-infura-project-id', + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any); await nftController.watchNft( ERC1155_NFT, ERC1155, 'https://etherscan.io', + 'mainnet', ); - expect(callActionSpy).toHaveBeenCalledTimes(9); + expect(callActionSpy).toHaveBeenCalledTimes(12); expect(callActionSpy).toHaveBeenNthCalledWith( - 6, + 7, 'ApprovalController:addRequest', { id: requestId, @@ -1164,7 +1471,6 @@ describe('NftController', () => { nftController, messenger, approvalController, - changeNetwork, triggerPreferencesStateChange, triggerSelectedAccountChange, } = setupController({ @@ -1208,13 +1514,18 @@ describe('NftController', () => { ...getDefaultPreferencesState(), openSeaEnabled: true, }); - changeNetwork({ selectedNetworkClientId: InfuraNetworkType.goerli }); // TODO: Either fix this lint violation or explain why it's necessary to ignore. // eslint-disable-next-line @typescript-eslint/no-floating-promises - nftController.watchNft(ERC721_NFT, ERC721, 'https://etherscan.io', { - userAddress: SECOND_OWNER_ADDRESS, - }); + nftController.watchNft( + ERC721_NFT, + ERC721, + 'https://etherscan.io', + 'goerli', + { + userAddress: SECOND_OWNER_ADDRESS, + }, + ); await pendingRequest; @@ -1265,7 +1576,6 @@ describe('NftController', () => { approvalController, triggerPreferencesStateChange, triggerSelectedAccountChange, - changeNetwork, } = setupController({ getERC721OwnerOf: jest.fn().mockImplementation(() => OWNER_ADDRESS), getERC721TokenURI: jest @@ -1311,9 +1621,12 @@ describe('NftController', () => { // TODO: Either fix this lint violation or explain why it's necessary to ignore. // eslint-disable-next-line @typescript-eslint/no-floating-promises - nftController.watchNft(ERC721_NFT, ERC721, 'https://etherscan.io', { - networkClientId: 'goerli', - }); + nftController.watchNft( + ERC721_NFT, + ERC721, + 'https://etherscan.io', + 'goerli', + ); await pendingRequest; @@ -1326,7 +1639,6 @@ describe('NftController', () => { ...getDefaultPreferencesState(), openSeaEnabled: true, }); - changeNetwork({ selectedNetworkClientId: InfuraNetworkType.sepolia }); // now accept the request // TODO: Either fix this lint violation or explain why it's necessary to ignore. // eslint-disable-next-line @typescript-eslint/no-floating-promises @@ -1360,12 +1672,10 @@ describe('NftController', () => { }); it('should throw an error when calls to `ownerOf` and `balanceOf` revert', async function () { - const { nftController, changeNetwork } = setupController(); + const { nftController } = setupController(); // getERC721OwnerOf not mocked // getERC1155BalanceOf not mocked - changeNetwork({ selectedNetworkClientId: InfuraNetworkType.sepolia }); - const requestId = 'approval-request-id-1'; (v4 as jest.Mock).mockImplementationOnce(() => requestId); @@ -1377,6 +1687,7 @@ describe('NftController', () => { ERC721_NFT, ERC721, 'https://test-dapp.com', + 'sepolia', ), ).rejects.toThrow( "Unable to verify ownership. Possibly because the standard is not supported or the user's currently selected network does not match the chain of the asset in question.", @@ -1386,15 +1697,12 @@ describe('NftController', () => { describe('addNft', () => { it('should add the nft contract to the correct chain in state when source is detected', async () => { - const { nftController, changeNetwork } = setupController({ - options: { - chainId: ChainId.mainnet, - }, + const { nftController } = setupController({ + options: {}, getERC721AssetName: jest.fn().mockResolvedValue('Name'), }); - changeNetwork({ selectedNetworkClientId: InfuraNetworkType.sepolia }); - await nftController.addNft('0x01', '1', { + await nftController.addNft('0x01', '1', 'mainnet', { nftMetadata: { name: 'name', image: 'image', @@ -1406,7 +1714,7 @@ describe('NftController', () => { image: 'url', }, }, - chainId: ChainId.mainnet, + // chainId: ChainId.mainnet, source: Source.Detected, }); @@ -1424,15 +1732,12 @@ describe('NftController', () => { }); it('should add the nft contract to the correct chain in state when source is custom', async () => { - const { nftController, changeNetwork } = setupController({ - options: { - chainId: ChainId.mainnet, - }, + const { nftController } = setupController({ + options: {}, getERC721AssetName: jest.fn().mockResolvedValue('Name'), }); - changeNetwork({ selectedNetworkClientId: InfuraNetworkType.sepolia }); - await nftController.addNft('0x01', '1', { + await nftController.addNft('0x01', '1', 'sepolia', { nftMetadata: { name: 'name', image: 'image', @@ -1461,12 +1766,12 @@ describe('NftController', () => { it('should add NFT and NFT contract', async () => { const { nftController } = setupController({ options: { - chainId: ChainId.mainnet, + // chainId: ChainId.mainnet, }, getERC721AssetName: jest.fn().mockResolvedValue('Name'), }); - await nftController.addNft('0x01', '1', { + await nftController.addNft('0x01', '1', 'mainnet', { nftMetadata: { name: 'name', image: 'image', @@ -1519,7 +1824,7 @@ describe('NftController', () => { }, }); - await nftController.addNft('0x01', '1', { + await nftController.addNft('0x01', '1', 'mainnet', { nftMetadata: { name: 'name', image: 'image', @@ -1546,7 +1851,7 @@ describe('NftController', () => { }); const detectedUserAddress = '0x123'; - await nftController.addNft('0x01', '2', { + await nftController.addNft('0x01', '2', 'mainnet', { nftMetadata: { name: 'name', image: 'image', @@ -1598,14 +1903,14 @@ describe('NftController', () => { ...getDefaultPreferencesState(), openSeaEnabled: true, }); - await nftController.addNft('0x01', '1234'); + await nftController.addNft('0x01', '1234', 'mainnet'); mockGetAccount.mockReturnValue(secondAccount); triggerSelectedAccountChange(secondAccount); triggerPreferencesStateChange({ ...getDefaultPreferencesState(), openSeaEnabled: true, }); - await nftController.addNft('0x02', '4321'); + await nftController.addNft('0x02', '4321', 'mainnet'); mockGetAccount.mockReturnValue(firstAccount); triggerSelectedAccountChange(firstAccount); triggerPreferencesStateChange({ @@ -1633,7 +1938,7 @@ describe('NftController', () => { defaultSelectedAccount: OWNER_ACCOUNT, }); - await nftController.addNft('0x01', '1', { + await nftController.addNft('0x01', '1', 'mainnet', { nftMetadata: { name: 'name', image: 'image', @@ -1657,7 +1962,7 @@ describe('NftController', () => { isCurrentlyOwned: true, }); - await nftController.addNft('0x01', '1', { + await nftController.addNft('0x01', '1', 'mainnet', { nftMetadata: { name: 'name', image: 'image-updated', @@ -1687,7 +1992,7 @@ describe('NftController', () => { defaultSelectedAccount: OWNER_ACCOUNT, }); - await nftController.addNft('0x01', '1', { + await nftController.addNft('0x01', '1', 'mainnet', { nftMetadata: { name: 'name', image: 'image', @@ -1711,7 +2016,7 @@ describe('NftController', () => { isCurrentlyOwned: true, }); - await nftController.addNft('0x01', '1', { + await nftController.addNft('0x01', '1', 'mainnet', { nftMetadata: { name: 'name', image: 'image', @@ -1755,7 +2060,7 @@ describe('NftController', () => { defaultSelectedAccount: OWNER_ACCOUNT, }); - await nftController.addNft('0x01', '1', { + await nftController.addNft('0x01', '1', 'mainnet', { nftMetadata: { name: 'name', image: 'image', @@ -1792,7 +2097,7 @@ describe('NftController', () => { mockOnNftAdded.mockReset(); - await nftController.addNft('0x01', '1', { + await nftController.addNft('0x01', '1', 'mainnet', { nftMetadata: { name: 'name', image: 'image', @@ -1834,7 +2139,7 @@ describe('NftController', () => { defaultSelectedAccount: OWNER_ACCOUNT, }); - await nftController.addNft('0x01', '1', { + await nftController.addNft('0x01', '1', 'mainnet', { nftMetadata: { name: 'name', image: 'image', @@ -1844,7 +2149,7 @@ describe('NftController', () => { }, }); - await nftController.addNft('0x01', '1', { + await nftController.addNft('0x01', '1', 'mainnet', { nftMetadata: { name: 'name', image: 'image', @@ -1917,7 +2222,7 @@ describe('NftController', () => { ], }); - await nftController.addNft('0x01', '1'); + await nftController.addNft('0x01', '1', 'mainnet'); expect( nftController.state.allNfts[OWNER_ACCOUNT.address][ChainId.mainnet][0], ).toStrictEqual({ @@ -1988,7 +2293,11 @@ describe('NftController', () => { description: 'Kudos Description (directly from tokenURI)', }); - await nftController.addNft(ERC721_KUDOSADDRESS, ERC721_KUDOS_TOKEN_ID); + await nftController.addNft( + ERC721_KUDOSADDRESS, + ERC721_KUDOS_TOKEN_ID, + 'mainnet', + ); expect( nftController.state.allNfts[OWNER_ACCOUNT.address][ChainId.mainnet][0], @@ -2078,7 +2387,11 @@ describe('NftController', () => { description: 'Kudos Description (directly from tokenURI)', }); - await nftController.addNft(ERC721_KUDOSADDRESS, ERC721_KUDOS_TOKEN_ID); + await nftController.addNft( + ERC721_KUDOSADDRESS, + ERC721_KUDOS_TOKEN_ID, + 'mainnet', + ); expect( nftController.state.allNfts[OWNER_ACCOUNT.address][ChainId.mainnet][0], @@ -2144,7 +2457,11 @@ describe('NftController', () => { animation_url: null, }); - await nftController.addNft(ERC1155_NFT_ADDRESS, ERC1155_NFT_ID); + await nftController.addNft( + ERC1155_NFT_ADDRESS, + ERC1155_NFT_ID, + 'mainnet', + ); expect( nftController.state.allNfts[OWNER_ACCOUNT.address][ChainId.mainnet][0], @@ -2191,7 +2508,11 @@ describe('NftController', () => { ) .reply(404, { error: 'Not found' }); - await nftController.addNft(ERC721_KUDOSADDRESS, ERC721_KUDOS_TOKEN_ID); + await nftController.addNft( + ERC721_KUDOSADDRESS, + ERC721_KUDOS_TOKEN_ID, + 'mainnet', + ); expect( nftController.state.allNfts[OWNER_ACCOUNT.address][ChainId.mainnet][0], @@ -2230,7 +2551,11 @@ describe('NftController', () => { getERC721TokenURI: jest.fn().mockResolvedValue(testTokenUriEncoded), defaultSelectedAccount: OWNER_ACCOUNT, }); - await nftController.addNft(ERC721_KUDOSADDRESS, ERC721_KUDOS_TOKEN_ID); + await nftController.addNft( + ERC721_KUDOSADDRESS, + ERC721_KUDOS_TOKEN_ID, + 'mainnet', + ); expect( nftController.state.allNfts[OWNER_ACCOUNT.address][ChainId.mainnet][0], @@ -2251,7 +2576,7 @@ describe('NftController', () => { it('should add NFT by provider type', async () => { const tokenURI = 'https://url/'; const mockGetERC721TokenURI = jest.fn().mockResolvedValue(tokenURI); - const { nftController, changeNetwork } = setupController({ + const { nftController } = setupController({ getERC721TokenURI: mockGetERC721TokenURI, defaultSelectedAccount: OWNER_ACCOUNT, }); @@ -2261,16 +2586,7 @@ describe('NftController', () => { description: 'description', }); - changeNetwork({ selectedNetworkClientId: InfuraNetworkType.sepolia }); - await nftController.addNft('0x01', '1234'); - changeNetwork({ selectedNetworkClientId: InfuraNetworkType.goerli }); - changeNetwork({ selectedNetworkClientId: InfuraNetworkType.sepolia }); - - expect( - nftController.state.allNfts[OWNER_ACCOUNT.address]?.[ - ChainId[GOERLI.type] - ], - ).toBeUndefined(); + await nftController.addNft('0x01', '1234', 'sepolia'); expect( nftController.state.allNfts[OWNER_ACCOUNT.address][ @@ -2312,7 +2628,7 @@ describe('NftController', () => { description: 'description', }); - await nftController.addNft('0x01234abcdefg', '1234'); + await nftController.addNft('0x01234abcdefg', '1234', 'mainnet'); expect(nftController.state.allNftContracts).toStrictEqual({ [OWNER_ACCOUNT.address]: { @@ -2359,7 +2675,7 @@ describe('NftController', () => { const mockGetERC721AssetSymbol = jest.fn().mockResolvedValue(''); const mockGetERC721AssetName = jest.fn().mockResolvedValue(''); const mockGetERC721TokenURI = jest.fn().mockResolvedValue(tokenURI); - const { nftController, changeNetwork } = setupController({ + const { nftController } = setupController({ options: { onNftAdded: mockOnNftAdded, }, @@ -2372,9 +2688,8 @@ describe('NftController', () => { image: 'url', description: 'description', }); - changeNetwork({ selectedNetworkClientId: InfuraNetworkType.goerli }); - await nftController.addNft('0x01234abcdefg', '1234', { + await nftController.addNft('0x01234abcdefg', '1234', 'goerli', { userAddress: '0x123', source: Source.Dapp, }); @@ -2463,6 +2778,7 @@ describe('NftController', () => { await nftController.addNft( '0x6EbeAf8e8E946F0716E6533A6f2cefc83f60e8Ab', '123', + 'mainnet', { userAddress: OWNER_ACCOUNT.address, source: Source.Detected, @@ -2479,10 +2795,15 @@ describe('NftController', () => { ], ).toBeUndefined(); - await nftController.addNft(ERC721_KUDOSADDRESS, ERC721_KUDOS_TOKEN_ID, { - userAddress: OWNER_ACCOUNT.address, - source: Source.Detected, - }); + await nftController.addNft( + ERC721_KUDOSADDRESS, + ERC721_KUDOS_TOKEN_ID, + 'mainnet', + { + userAddress: OWNER_ACCOUNT.address, + source: Source.Detected, + }, + ); expect( nftController.state.allNfts[OWNER_ACCOUNT.address][ChainId.mainnet], @@ -2586,6 +2907,7 @@ describe('NftController', () => { await nftController.addNft( '0x6EbeAf8e8E946F0716E6533A6f2cefc83f60e8Ab', '123', + 'mainnet', { userAddress: OWNER_ACCOUNT.address, source: Source.Detected, @@ -2602,10 +2924,15 @@ describe('NftController', () => { ], ).toBeUndefined(); - await nftController.addNft(ERC721_KUDOSADDRESS, ERC721_KUDOS_TOKEN_ID, { - userAddress: OWNER_ACCOUNT.address, - source: Source.Detected, - }); + await nftController.addNft( + ERC721_KUDOSADDRESS, + ERC721_KUDOS_TOKEN_ID, + 'mainnet', + { + userAddress: OWNER_ACCOUNT.address, + source: Source.Detected, + }, + ); expect( nftController.state.allNfts[OWNER_ACCOUNT.address][ChainId.mainnet], @@ -2679,15 +3006,21 @@ describe('NftController', () => { await nftController.addNft( '0x6EbeAf8e8E946F0716E6533A6f2cefc83f60e8Ab', '123', + 'mainnet', + { + userAddress: OWNER_ACCOUNT.address, + source: Source.Detected, + }, + ); + await nftController.addNft( + ERC721_KUDOSADDRESS, + ERC721_KUDOS_TOKEN_ID, + 'mainnet', { userAddress: OWNER_ACCOUNT.address, source: Source.Detected, }, ); - await nftController.addNft(ERC721_KUDOSADDRESS, ERC721_KUDOS_TOKEN_ID, { - userAddress: OWNER_ACCOUNT.address, - source: Source.Detected, - }); expect(nftController.state.allNfts).toStrictEqual({}); expect(nftController.state.allNftContracts).toStrictEqual({}); @@ -2699,7 +3032,7 @@ describe('NftController', () => { defaultSelectedAccount: OWNER_ACCOUNT, }); - await nftController.addNft('0x01', '1', { + await nftController.addNft('0x01', '1', 'mainnet', { nftMetadata: { name: 'name', image: 'image', @@ -2708,7 +3041,7 @@ describe('NftController', () => { }, }); - await nftController.addNft('0x01', '2', { + await nftController.addNft('0x01', '2', 'mainnet', { nftMetadata: { name: 'name', image: 'image', @@ -2722,13 +3055,13 @@ describe('NftController', () => { ).toHaveLength(2); expect(nftController.state.ignoredNfts).toHaveLength(0); - nftController.removeAndIgnoreNft('0x01', '1'); + nftController.removeAndIgnoreNft('0x01', '1', 'mainnet'); expect( nftController.state.allNfts[OWNER_ACCOUNT.address][ChainId.mainnet], ).toHaveLength(1); expect(nftController.state.ignoredNfts).toHaveLength(1); - await nftController.addNft('0x01', '1', { + await nftController.addNft('0x01', '1', 'mainnet', { nftMetadata: { name: 'name', image: 'image', @@ -2742,7 +3075,7 @@ describe('NftController', () => { ).toHaveLength(2); expect(nftController.state.ignoredNfts).toHaveLength(1); - nftController.removeAndIgnoreNft('0x01', '1'); + nftController.removeAndIgnoreNft('0x01', '1', 'mainnet'); expect( nftController.state.allNfts[OWNER_ACCOUNT.address][ChainId.mainnet], ).toHaveLength(1); @@ -2777,6 +3110,7 @@ describe('NftController', () => { await nftController.addNft( ERC721_DEPRESSIONIST_ADDRESS, ERC721_DEPRESSIONIST_ID, + 'mainnet', ); expect( @@ -2814,7 +3148,7 @@ describe('NftController', () => { ) .replyWithError(new Error('Failed to fetch')); - await nftController.addNft(ERC721_NFT_ADDRESS, ERC721_NFT_ID); + await nftController.addNft(ERC721_NFT_ADDRESS, ERC721_NFT_ID, 'mainnet'); expect( nftController.state.allNfts[OWNER_ACCOUNT.address][ChainId.mainnet][0], @@ -2892,15 +3226,9 @@ describe('NftController', () => { }, }); - await nftController.addNft('0x01', '1234', { - networkClientId: 'sepolia', - }); - await nftController.addNft('0x02', '4321', { - networkClientId: 'goerli', - }); - await nftController.addNft('0x03', '5678', { - networkClientId: 'customNetworkClientId-1', - }); + await nftController.addNft('0x01', '1234', 'sepolia'); + await nftController.addNft('0x02', '4321', 'goerli'); + await nftController.addNft('0x03', '5678', 'customNetworkClientId-1'); expect( nftController.state.allNfts[OWNER_ADDRESS][SEPOLIA.chainId], @@ -2987,8 +3315,9 @@ describe('NftController', () => { }), ); - const { nftController, changeNetwork } = setupController({ + const { nftController } = setupController({ getERC721TokenURI: jest.fn().mockImplementation((tokenAddress) => { + // eslint-disable-next-line jest/no-conditional-in-test switch (tokenAddress) { case '0x01': return 'https://testtokenuri-1.com'; @@ -2999,6 +3328,7 @@ describe('NftController', () => { } }), getERC1155TokenURI: jest.fn().mockImplementation((tokenAddress) => { + // eslint-disable-next-line jest/no-conditional-in-test switch (tokenAddress) { case '0x03': return 'https://testtokenuri-3.com'; @@ -3008,18 +3338,15 @@ describe('NftController', () => { }), }); - await nftController.addNft('0x01', '1234', { + await nftController.addNft('0x01', '1234', 'mainnet', { userAddress, }); - changeNetwork({ selectedNetworkClientId: InfuraNetworkType.goerli }); - - await nftController.addNft('0x02', '4321', { + await nftController.addNft('0x02', '4321', 'goerli', { userAddress, }); - changeNetwork({ selectedNetworkClientId: InfuraNetworkType.sepolia }); - await nftController.addNft('0x03', '5678', { + await nftController.addNft('0x03', '5678', 'sepolia', { userAddress, }); @@ -3074,14 +3401,14 @@ describe('NftController', () => { it('should handle unset selectedAccount', async () => { const { nftController, mockGetAccount } = setupController({ options: { - chainId: ChainId.mainnet, + // chainId: ChainId.mainnet, }, getERC721AssetName: jest.fn().mockResolvedValue('Name'), }); mockGetAccount.mockReturnValue(null); - await nftController.addNft('0x01', '1', { + await nftController.addNft('0x01', '1', 'mainnet', { nftMetadata: { name: 'name', image: 'image', @@ -3135,14 +3462,14 @@ describe('NftController', () => { ...getDefaultPreferencesState(), openSeaEnabled: true, }); - await nftController.addNftVerifyOwnership('0x01', '1234'); + await nftController.addNftVerifyOwnership('0x01', '1234', 'mainnet'); mockGetAccount.mockReturnValue(secondAccount); triggerSelectedAccountChange(secondAccount); triggerPreferencesStateChange({ ...getDefaultPreferencesState(), openSeaEnabled: true, }); - await nftController.addNftVerifyOwnership('0x02', '4321'); + await nftController.addNftVerifyOwnership('0x02', '4321', 'mainnet'); mockGetAccount.mockReturnValue(firstAccount); triggerSelectedAccountChange(firstAccount); triggerPreferencesStateChange({ @@ -3185,7 +3512,7 @@ describe('NftController', () => { openSeaEnabled: true, }); const result = async () => - await nftController.addNftVerifyOwnership('0x01', '1234'); + await nftController.addNftVerifyOwnership('0x01', '1234', 'mainnet'); const error = 'This NFT is not owned by the user'; await expect(result).rejects.toThrow(error); }); @@ -3229,18 +3556,14 @@ describe('NftController', () => { ...getDefaultPreferencesState(), openSeaEnabled: true, }); - await nftController.addNftVerifyOwnership('0x01', '1234', { - networkClientId: 'sepolia', - }); + await nftController.addNftVerifyOwnership('0x01', '1234', 'sepolia'); mockGetAccount.mockReturnValue(secondAccount); triggerSelectedAccountChange(secondAccount); triggerPreferencesStateChange({ ...getDefaultPreferencesState(), openSeaEnabled: true, }); - await nftController.addNftVerifyOwnership('0x02', '4321', { - networkClientId: 'goerli', - }); + await nftController.addNftVerifyOwnership('0x02', '4321', 'goerli'); expect( nftController.state.allNfts[firstAccount.address][SEPOLIA.chainId][0], @@ -3277,7 +3600,6 @@ describe('NftController', () => { const mockGetERC721TokenURI = jest.fn().mockResolvedValue(tokenURI); const { nftController, - changeNetwork, triggerPreferencesStateChange, triggerSelectedAccountChange, } = setupController({ @@ -3303,12 +3625,10 @@ describe('NftController', () => { description: 'description', }) .persist(); - changeNetwork({ selectedNetworkClientId: InfuraNetworkType.sepolia }); - await nftController.addNftVerifyOwnership('0x01', '1234', { + await nftController.addNftVerifyOwnership('0x01', '1234', 'sepolia', { userAddress: firstAddress, }); - changeNetwork({ selectedNetworkClientId: InfuraNetworkType.goerli }); - await nftController.addNftVerifyOwnership('0x02', '4321', { + await nftController.addNftVerifyOwnership('0x02', '4321', 'goerli', { userAddress: secondAddress, }); @@ -3349,7 +3669,7 @@ describe('NftController', () => { defaultSelectedAccount: OWNER_ACCOUNT, }); - await nftController.addNft('0x01', '1', { + await nftController.addNft('0x01', '1', 'mainnet', { nftMetadata: { name: 'name', image: 'image', @@ -3357,7 +3677,7 @@ describe('NftController', () => { standard: 'standard', }, }); - nftController.removeNft('0x01', '1'); + nftController.removeNft('0x01', '1', 'mainnet'); expect( nftController.state.allNfts[OWNER_ACCOUNT.address][ChainId.mainnet], ).toHaveLength(0); @@ -3372,7 +3692,7 @@ describe('NftController', () => { it('should not remove NFT contract if NFT still exists', async () => { const { nftController } = setupController(); - await nftController.addNft('0x01', '1', { + await nftController.addNft('0x01', '1', 'mainnet', { nftMetadata: { name: 'name', image: 'image', @@ -3381,7 +3701,7 @@ describe('NftController', () => { }, }); - await nftController.addNft('0x01', '2', { + await nftController.addNft('0x01', '2', 'mainnet', { nftMetadata: { name: 'name', image: 'image', @@ -3389,7 +3709,7 @@ describe('NftController', () => { standard: 'standard', }, }); - nftController.removeNft('0x01', '1'); + nftController.removeNft('0x01', '1', 'mainnet'); expect( nftController.state.allNfts[OWNER_ACCOUNT.address][ChainId.mainnet], ).toHaveLength(1); @@ -3433,15 +3753,15 @@ describe('NftController', () => { ...getDefaultPreferencesState(), openSeaEnabled: true, }); - await nftController.addNft('0x02', '4321'); + await nftController.addNft('0x02', '4321', 'mainnet'); mockGetAccount.mockReturnValue(secondAccount); triggerSelectedAccountChange(secondAccount); triggerPreferencesStateChange({ ...getDefaultPreferencesState(), openSeaEnabled: true, }); - await nftController.addNft('0x01', '1234'); - nftController.removeNft('0x01', '1234'); + await nftController.addNft('0x01', '1234', 'mainnet'); + nftController.removeNft('0x01', '1234', 'mainnet'); expect( nftController.state.allNfts[secondAccount.address][ChainId.mainnet], ).toHaveLength(0); @@ -3468,7 +3788,7 @@ describe('NftController', () => { it('should remove NFT by provider type', async () => { const tokenURI = 'https://url/'; const mockGetERC721TokenURI = jest.fn().mockResolvedValue(tokenURI); - const { nftController, changeNetwork } = setupController({ + const { nftController } = setupController({ getERC721TokenURI: mockGetERC721TokenURI, defaultSelectedAccount: OWNER_ACCOUNT, }); @@ -3478,17 +3798,13 @@ describe('NftController', () => { image: 'url', description: 'description', }); - changeNetwork({ selectedNetworkClientId: InfuraNetworkType.sepolia }); - await nftController.addNft('0x02', '4321'); - changeNetwork({ selectedNetworkClientId: InfuraNetworkType.goerli }); - await nftController.addNft('0x01', '1234'); - nftController.removeNft('0x01', '1234'); + await nftController.addNft('0x02', '4321', 'sepolia'); + await nftController.addNft('0x01', '1234', 'goerli'); + nftController.removeNft('0x01', '1234', 'goerli'); expect( nftController.state.allNfts[OWNER_ACCOUNT.address][GOERLI.chainId], ).toHaveLength(0); - changeNetwork({ selectedNetworkClientId: InfuraNetworkType.sepolia }); - expect( nftController.state.allNfts[OWNER_ACCOUNT.address][SEPOLIA.chainId][0], ).toStrictEqual({ @@ -3508,7 +3824,6 @@ describe('NftController', () => { it('should remove correct NFT and NFT contract when passed networkClientId and userAddress in options', async () => { const { nftController, - changeNetwork, triggerPreferencesStateChange, triggerSelectedAccountChange, mockGetAccount, @@ -3525,7 +3840,6 @@ describe('NftController', () => { id: '9ea40063-a95c-4f79-a4b6-0c065549245e', }); - changeNetwork({ selectedNetworkClientId: InfuraNetworkType.sepolia }); mockGetAccount.mockReturnValue(userAccount1); triggerSelectedAccountChange(userAccount1); triggerPreferencesStateChange({ @@ -3533,7 +3847,7 @@ describe('NftController', () => { openSeaEnabled: true, }); - await nftController.addNft('0x01', '1', { + await nftController.addNft('0x01', '1', 'sepolia', { nftMetadata: { name: 'name', image: 'image', @@ -3556,7 +3870,6 @@ describe('NftController', () => { isCurrentlyOwned: true, }); - changeNetwork({ selectedNetworkClientId: InfuraNetworkType.goerli }); mockGetAccount.mockReturnValue(userAccount2); triggerSelectedAccountChange(userAccount2); triggerPreferencesStateChange({ @@ -3565,8 +3878,7 @@ describe('NftController', () => { }); // now remove the nft after changing to a different network and account from the one where it was added - nftController.removeNft('0x01', '1', { - networkClientId: SEPOLIA.type, + nftController.removeNft('0x01', '1', 'sepolia', { userAddress: userAddress1, }); @@ -3585,7 +3897,7 @@ describe('NftController', () => { defaultSelectedAccount: OWNER_ACCOUNT, }); - await nftController.addNft('0x02', '1', { + await nftController.addNft('0x02', '1', 'mainnet', { nftMetadata: { name: 'name', image: 'image', @@ -3600,7 +3912,7 @@ describe('NftController', () => { ).toHaveLength(1); expect(nftController.state.ignoredNfts).toHaveLength(0); - nftController.removeAndIgnoreNft('0x02', '1'); + nftController.removeAndIgnoreNft('0x02', '1', 'mainnet'); expect( nftController.state.allNfts[OWNER_ACCOUNT.address][ChainId.mainnet], ).toHaveLength(0); @@ -3625,7 +3937,7 @@ describe('NftController', () => { OWNER_ADDRESS, '0x2b26675403a063d92ccad0293d387485471a7d3a', String(1), - { networkClientId: 'sepolia' }, + 'sepolia', ); expect(isOwner).toBe(true); }); @@ -3644,6 +3956,7 @@ describe('NftController', () => { OWNER_ADDRESS, ERC721_NFT_ADDRESS, String(ERC721_NFT_ID), + 'mainnet', ); expect(isOwner).toBe(true); }); @@ -3662,6 +3975,7 @@ describe('NftController', () => { '0x0000000000000000000000000000000000000000', ERC721_NFT_ADDRESS, String(ERC721_NFT_ID), + 'mainnet', ); expect(isOwner).toBe(false); }); @@ -3680,6 +3994,7 @@ describe('NftController', () => { OWNER_ADDRESS, ERC1155_NFT_ADDRESS, ERC1155_NFT_ID, + 'mainnet', ); expect(isOwner).toBe(true); }); @@ -3698,6 +4013,7 @@ describe('NftController', () => { '0x0000000000000000000000000000000000000000', ERC1155_NFT_ADDRESS, ERC1155_NFT_ID, + 'mainnet', ); expect(isOwner).toBe(false); @@ -3721,6 +4037,7 @@ describe('NftController', () => { '0x0000000000000000000000000000000000000000', CRYPTOPUNK_ADDRESS, '0', + 'mainnet', ); }; await expect(result).rejects.toThrow(error); @@ -3743,7 +4060,11 @@ describe('NftController', () => { openSeaEnabled: false, }); - await nftController.addNft(ERC1155_NFT_ADDRESS, ERC1155_NFT_ID); + await nftController.addNft( + ERC1155_NFT_ADDRESS, + ERC1155_NFT_ID, + 'mainnet', + ); expect( nftController.state.allNfts[OWNER_ACCOUNT.address][ChainId.mainnet][0], @@ -3771,6 +4092,7 @@ describe('NftController', () => { await nftController.addNft( ERC721_DEPRESSIONIST_ADDRESS, ERC721_DEPRESSIONIST_ID, + 'mainnet', { nftMetadata: { name: '', description: '', image: '', standard: '' } }, ); @@ -3778,6 +4100,7 @@ describe('NftController', () => { ERC721_DEPRESSIONIST_ADDRESS, '666', true, + 'mainnet', ); expect( @@ -3798,6 +4121,7 @@ describe('NftController', () => { await nftController.addNft( ERC721_DEPRESSIONIST_ADDRESS, ERC721_DEPRESSIONIST_ID, + 'mainnet', { nftMetadata: { name: '', description: '', image: '', standard: '' } }, ); @@ -3805,6 +4129,7 @@ describe('NftController', () => { ERC721_DEPRESSIONIST_ADDRESS, ERC721_DEPRESSIONIST_ID, true, + 'mainnet', ); expect( @@ -3826,6 +4151,7 @@ describe('NftController', () => { await nftController.addNft( ERC721_DEPRESSIONIST_ADDRESS, ERC721_DEPRESSIONIST_ID, + 'mainnet', { nftMetadata: { name: '', description: '', image: '', standard: '' } }, ); @@ -3833,6 +4159,7 @@ describe('NftController', () => { ERC721_DEPRESSIONIST_ADDRESS, ERC721_DEPRESSIONIST_ID, true, + 'mainnet', ); expect( @@ -3849,6 +4176,7 @@ describe('NftController', () => { ERC721_DEPRESSIONIST_ADDRESS, ERC721_DEPRESSIONIST_ID, false, + 'mainnet', ); expect( @@ -3870,6 +4198,7 @@ describe('NftController', () => { await nftController.addNft( ERC721_DEPRESSIONIST_ADDRESS, ERC721_DEPRESSIONIST_ID, + 'mainnet', { nftMetadata: { name: '', description: '', image: '', standard: '' } }, ); @@ -3877,6 +4206,7 @@ describe('NftController', () => { ERC721_DEPRESSIONIST_ADDRESS, ERC721_DEPRESSIONIST_ID, true, + 'mainnet', ); expect( @@ -3892,6 +4222,7 @@ describe('NftController', () => { await nftController.addNft( ERC721_DEPRESSIONIST_ADDRESS, ERC721_DEPRESSIONIST_ID, + 'mainnet', { nftMetadata: { image: 'new_image', @@ -3929,6 +4260,7 @@ describe('NftController', () => { await nftController.addNft( ERC721_DEPRESSIONIST_ADDRESS, ERC721_DEPRESSIONIST_ID, + 'mainnet', { nftMetadata: { name: '', description: '', image: '', standard: '' } }, ); @@ -3945,6 +4277,7 @@ describe('NftController', () => { await nftController.addNft( ERC721_DEPRESSIONIST_ADDRESS, ERC721_DEPRESSIONIST_ID, + 'mainnet', { nftMetadata: { image: 'new_image', @@ -3978,7 +4311,6 @@ describe('NftController', () => { const { nftController, triggerPreferencesStateChange, - changeNetwork, triggerSelectedAccountChange, mockGetAccount, } = setupController(); @@ -3994,7 +4326,6 @@ describe('NftController', () => { id: '09b239a4-c229-4a2b-9739-1cb4b9dea7b9', }); - changeNetwork({ selectedNetworkClientId: InfuraNetworkType.sepolia }); mockGetAccount.mockReturnValue(userAccount1); triggerSelectedAccountChange(userAccount1); triggerPreferencesStateChange({ @@ -4005,6 +4336,7 @@ describe('NftController', () => { await nftController.addNft( ERC721_DEPRESSIONIST_ADDRESS, ERC721_DEPRESSIONIST_ID, + 'sepolia', { nftMetadata: { name: '', description: '', image: '', standard: '' } }, ); @@ -4018,7 +4350,6 @@ describe('NftController', () => { }), ); - changeNetwork({ selectedNetworkClientId: InfuraNetworkType.goerli }); mockGetAccount.mockReturnValue(userAccount2); triggerSelectedAccountChange(userAccount2); triggerPreferencesStateChange({ @@ -4031,10 +4362,8 @@ describe('NftController', () => { ERC721_DEPRESSIONIST_ADDRESS, ERC721_DEPRESSIONIST_ID, true, - { - networkClientId: SEPOLIA.type, - userAddress: userAccount1.address, - }, + 'sepolia', + { userAddress: userAccount1.address }, ); expect( @@ -4057,7 +4386,7 @@ describe('NftController', () => { }); jest.spyOn(nftController, 'isNftOwner').mockResolvedValue(false); - await nftController.addNft('0x02', '1', { + await nftController.addNft('0x02', '1', 'mainnet', { nftMetadata: { name: 'name', image: 'image', @@ -4071,7 +4400,7 @@ describe('NftController', () => { .isCurrentlyOwned, ).toBe(true); - await nftController.checkAndUpdateAllNftsOwnershipStatus(); + await nftController.checkAndUpdateAllNftsOwnershipStatus('mainnet'); expect( nftController.state.allNfts[OWNER_ACCOUNT.address][ChainId.mainnet][0] @@ -4085,7 +4414,7 @@ describe('NftController', () => { }); jest.spyOn(nftController, 'isNftOwner').mockResolvedValue(true); - await nftController.addNft('0x02', '1', { + await nftController.addNft('0x02', '1', 'mainnet', { nftMetadata: { name: 'name', image: 'image', @@ -4100,7 +4429,7 @@ describe('NftController', () => { .isCurrentlyOwned, ).toBe(true); - await nftController.checkAndUpdateAllNftsOwnershipStatus(); + await nftController.checkAndUpdateAllNftsOwnershipStatus('mainnet'); expect( nftController.state.allNfts[OWNER_ACCOUNT.address][ChainId.mainnet][0] .isCurrentlyOwned, @@ -4115,7 +4444,7 @@ describe('NftController', () => { .spyOn(nftController, 'isNftOwner') .mockRejectedValue('Unable to verify ownership'); - await nftController.addNft('0x02', '1', { + await nftController.addNft('0x02', '1', 'mainnet', { nftMetadata: { name: 'name', image: 'image', @@ -4130,7 +4459,7 @@ describe('NftController', () => { .isCurrentlyOwned, ).toBe(true); - await nftController.checkAndUpdateAllNftsOwnershipStatus(); + await nftController.checkAndUpdateAllNftsOwnershipStatus('mainnet'); expect( nftController.state.allNfts[OWNER_ACCOUNT.address][ChainId.mainnet][0] .isCurrentlyOwned, @@ -4138,21 +4467,16 @@ describe('NftController', () => { }); it('should check whether NFTs for the current selectedAccount/chainId combination are still owned by the selectedAccount and update the isCurrentlyOwned value to false when NFT is not still owned, when the currently configured selectedAccount/chainId are different from those passed', async () => { - const { - nftController, - changeNetwork, - triggerPreferencesStateChange, - mockGetAccount, - } = setupController(); + const { nftController, triggerPreferencesStateChange, mockGetAccount } = + setupController(); mockGetAccount.mockReturnValue(OWNER_ACCOUNT); triggerPreferencesStateChange({ ...getDefaultPreferencesState(), openSeaEnabled: true, }); - changeNetwork({ selectedNetworkClientId: InfuraNetworkType.sepolia }); - await nftController.addNft('0x02', '1', { + await nftController.addNft('0x02', '1', 'sepolia', { nftMetadata: { name: 'name', image: 'image', @@ -4173,11 +4497,9 @@ describe('NftController', () => { ...getDefaultPreferencesState(), openSeaEnabled: true, }); - changeNetwork({ selectedNetworkClientId: InfuraNetworkType.goerli }); - await nftController.checkAndUpdateAllNftsOwnershipStatus({ + await nftController.checkAndUpdateAllNftsOwnershipStatus('sepolia', { userAddress: OWNER_ADDRESS, - networkClientId: 'sepolia', }); expect( @@ -4191,7 +4513,7 @@ describe('NftController', () => { mockGetAccount.mockReturnValue(null); jest.spyOn(nftController, 'isNftOwner').mockResolvedValue(false); - await nftController.addNft('0x02', '1', { + await nftController.addNft('0x02', '1', 'mainnet', { nftMetadata: { name: 'name', image: 'image', @@ -4202,7 +4524,7 @@ describe('NftController', () => { }); expect(nftController.state.allNfts['']).toBeUndefined(); - await nftController.checkAndUpdateAllNftsOwnershipStatus(); + await nftController.checkAndUpdateAllNftsOwnershipStatus('mainnet'); expect(nftController.state.allNfts['']).toBeUndefined(); }); @@ -4224,7 +4546,7 @@ describe('NftController', () => { favorite: false, }; - await nftController.addNft(nft.address, nft.tokenId, { + await nftController.addNft(nft.address, nft.tokenId, 'mainnet', { nftMetadata: nft, }); @@ -4235,7 +4557,11 @@ describe('NftController', () => { jest.spyOn(nftController, 'isNftOwner').mockResolvedValue(false); - await nftController.checkAndUpdateSingleNftOwnershipStatus(nft, false); + await nftController.checkAndUpdateSingleNftOwnershipStatus( + nft, + false, + 'mainnet', + ); expect( nftController.state.allNfts[OWNER_ACCOUNT.address][ChainId.mainnet][0] @@ -4258,7 +4584,7 @@ describe('NftController', () => { favorite: false, }; - await nftController.addNft(nft.address, nft.tokenId, { + await nftController.addNft(nft.address, nft.tokenId, 'mainnet', { nftMetadata: nft, }); @@ -4270,7 +4596,11 @@ describe('NftController', () => { jest.spyOn(nftController, 'isNftOwner').mockResolvedValue(false); const updatedNft = - await nftController.checkAndUpdateSingleNftOwnershipStatus(nft, true); + await nftController.checkAndUpdateSingleNftOwnershipStatus( + nft, + true, + 'mainnet', + ); expect( nftController.state.allNfts[OWNER_ACCOUNT.address][ChainId.mainnet][0] @@ -4284,7 +4614,6 @@ describe('NftController', () => { const firstSelectedAddress = OWNER_ACCOUNT.address; const { nftController, - changeNetwork, triggerPreferencesStateChange, triggerSelectedAccountChange, } = setupController(); @@ -4294,7 +4623,6 @@ describe('NftController', () => { ...getDefaultPreferencesState(), openSeaEnabled: true, }); - changeNetwork({ selectedNetworkClientId: InfuraNetworkType.sepolia }); const nft = { address: '0x02', @@ -4306,7 +4634,7 @@ describe('NftController', () => { favorite: false, }; - await nftController.addNft(nft.address, nft.tokenId, { + await nftController.addNft(nft.address, nft.tokenId, 'sepolia', { nftMetadata: nft, }); @@ -4324,12 +4652,15 @@ describe('NftController', () => { ...getDefaultPreferencesState(), openSeaEnabled: true, }); - changeNetwork({ selectedNetworkClientId: InfuraNetworkType.goerli }); - await nftController.checkAndUpdateSingleNftOwnershipStatus(nft, false, { - userAddress: OWNER_ADDRESS, - networkClientId: 'sepolia', - }); + await nftController.checkAndUpdateSingleNftOwnershipStatus( + nft, + false, + 'sepolia', + { + userAddress: OWNER_ADDRESS, + }, + ); expect( nftController.state.allNfts[OWNER_ADDRESS][SEPOLIA.chainId][0] @@ -4341,7 +4672,6 @@ describe('NftController', () => { const firstSelectedAddress = OWNER_ACCOUNT.address; const { nftController, - changeNetwork, triggerPreferencesStateChange, triggerSelectedAccountChange, } = setupController(); @@ -4351,7 +4681,6 @@ describe('NftController', () => { ...getDefaultPreferencesState(), openSeaEnabled: true, }); - changeNetwork({ selectedNetworkClientId: InfuraNetworkType.sepolia }); const nft = { address: '0x02', @@ -4363,7 +4692,7 @@ describe('NftController', () => { favorite: false, }; - await nftController.addNft(nft.address, nft.tokenId, { + await nftController.addNft(nft.address, nft.tokenId, 'sepolia', { nftMetadata: nft, }); @@ -4381,15 +4710,14 @@ describe('NftController', () => { ...getDefaultPreferencesState(), openSeaEnabled: true, }); - changeNetwork({ selectedNetworkClientId: InfuraNetworkType.goerli }); const updatedNft = await nftController.checkAndUpdateSingleNftOwnershipStatus( nft, false, + 'sepolia', { userAddress: OWNER_ADDRESS, - networkClientId: SEPOLIA.type, }, ); @@ -4611,9 +4939,8 @@ describe('NftController', () => { const spy = jest.spyOn(nftController, 'updateNftMetadata'); const testNetworkClientId = 'mainnet'; mockGetAccount.mockReturnValue(OWNER_ACCOUNT); - await nftController.addNft('0xtest', '3', { + await nftController.addNft('0xtest', '3', testNetworkClientId, { nftMetadata: { name: '', description: '', image: '', standard: '' }, - networkClientId: testNetworkClientId, }); triggerSelectedAccountChange(OWNER_ACCOUNT); @@ -4636,9 +4963,8 @@ describe('NftController', () => { const spy = jest.spyOn(nftController, 'updateNft'); const testNetworkClientId = 'sepolia'; mockGetAccount.mockReturnValue(OWNER_ACCOUNT); - await nftController.addNft('0xtest', '3', { + await nftController.addNft('0xtest', '3', testNetworkClientId, { nftMetadata: { name: '', description: '', image: '', standard: '' }, - networkClientId: testNetworkClientId, }); nock('https://api.pudgypenguins.io').get('/lil/4').reply(200, { @@ -4657,12 +4983,13 @@ describe('NftController', () => { standard: ERC721, tokenId: '3', tokenURI, + chainId: 11155111, }, ]; await nftController.updateNftMetadata({ nfts: testInputNfts, - networkClientId: testNetworkClientId, + // networkClientId: testNetworkClientId, }); expect(spy).toHaveBeenCalledTimes(1); @@ -4670,6 +4997,7 @@ describe('NftController', () => { nftController.state.allNfts[OWNER_ACCOUNT.address][SEPOLIA.chainId][0], ).toStrictEqual({ address: '0xtest', + chainId: 11155111, description: 'description pudgy', image: 'url pudgy', name: 'name pudgy', @@ -4691,7 +5019,7 @@ describe('NftController', () => { const updateNftSpy = jest.spyOn(nftController, 'updateNft'); const testNetworkClientId = 'sepolia'; mockGetAccount.mockReturnValue(OWNER_ACCOUNT); - await nftController.addNft('0xtest', '3', { + await nftController.addNft('0xtest', '3', testNetworkClientId, { nftMetadata: { name: 'toto', description: 'description', @@ -4699,7 +5027,6 @@ describe('NftController', () => { standard: ERC721, tokenURI, }, - networkClientId: testNetworkClientId, }); nock('https://url') @@ -4720,13 +5047,14 @@ describe('NftController', () => { name: 'toto', standard: ERC721, tokenId: '3', + chainId: convertHexToDecimal(ChainId.sepolia), }, ]; mockGetAccount.mockReturnValue(OWNER_ACCOUNT); await nftController.updateNftMetadata({ nfts: testInputNfts, - networkClientId: testNetworkClientId, + // networkClientId: testNetworkClientId, }); expect(updateNftSpy).toHaveBeenCalledTimes(0); @@ -4756,14 +5084,14 @@ describe('NftController', () => { const spy = jest.spyOn(nftController, 'updateNft'); const testNetworkClientId = 'sepolia'; mockGetAccount.mockReturnValue(OWNER_ACCOUNT); - await nftController.addNft('0xtest', '3', { + await nftController.addNft('0xtest', '3', testNetworkClientId, { nftMetadata: { name: 'toto', description: 'description', image: 'image.png', standard: ERC721, }, - networkClientId: testNetworkClientId, + // networkClientId: testNetworkClientId, }); nock('https://url').get('/').reply(200, { @@ -4781,12 +5109,13 @@ describe('NftController', () => { name: 'toto', standard: ERC721, tokenId: '3', + chainId: convertHexToDecimal(ChainId.sepolia), }, ]; await nftController.updateNftMetadata({ nfts: testInputNfts, - networkClientId: testNetworkClientId, + // networkClientId: testNetworkClientId, }); expect(spy).toHaveBeenCalledTimes(1); @@ -4802,6 +5131,7 @@ describe('NftController', () => { standard: ERC721, tokenId: '3', tokenURI, + chainId: convertHexToDecimal(ChainId.sepolia), }); }); @@ -4815,7 +5145,7 @@ describe('NftController', () => { const testNetworkClientId = 'sepolia'; // Add nfts - await nftController.addNft('0xtest', '3', { + await nftController.addNft('0xtest', '3', testNetworkClientId, { nftMetadata: { name: 'test name', description: 'test description', @@ -4823,7 +5153,7 @@ describe('NftController', () => { standard: ERC721, }, userAddress: OWNER_ADDRESS, - networkClientId: testNetworkClientId, + // networkClientId: testNetworkClientId, }); triggerSelectedAccountChange(OWNER_ACCOUNT); @@ -4843,17 +5173,15 @@ describe('NftController', () => { const { nftController, triggerPreferencesStateChange, - changeNetwork, triggerSelectedAccountChange, } = setupController({ getERC721TokenURI: mockGetERC721TokenURI, }); - changeNetwork({ selectedNetworkClientId: InfuraNetworkType.sepolia }); const spy = jest.spyOn(nftController, 'updateNftMetadata'); const testNetworkClientId = 'sepolia'; // Add nfts - await nftController.addNft('0xtest', '1', { + await nftController.addNft('0xtest', '1', testNetworkClientId, { nftMetadata: { name: '', description: '', @@ -4861,7 +5189,7 @@ describe('NftController', () => { standard: ERC721, }, userAddress: OWNER_ADDRESS, - networkClientId: testNetworkClientId, + // networkClientId: testNetworkClientId, }); expect( @@ -4891,17 +5219,15 @@ describe('NftController', () => { const { nftController, triggerPreferencesStateChange, - changeNetwork, triggerSelectedAccountChange, } = setupController({ getERC721TokenURI: mockGetERC721TokenURI, }); - changeNetwork({ selectedNetworkClientId: InfuraNetworkType.sepolia }); const spy = jest.spyOn(nftController, 'updateNftMetadata'); const testNetworkClientId = 'sepolia'; // Add nfts - await nftController.addNft('0xtest', '1', { + await nftController.addNft('0xtest', '1', testNetworkClientId, { nftMetadata: { name: '', description: '', @@ -4909,7 +5235,7 @@ describe('NftController', () => { standard: ERC721, }, userAddress: OWNER_ADDRESS, - networkClientId: testNetworkClientId, + // networkClientId: testNetworkClientId, }); expect( @@ -4943,9 +5269,9 @@ describe('NftController', () => { const selectedAddress = OWNER_ADDRESS; const spy = jest.spyOn(nftController, 'updateNft'); const testNetworkClientId = 'sepolia'; - await nftController.addNft('0xtest', '3', { + await nftController.addNft('0xtest', '3', testNetworkClientId, { nftMetadata: { name: '', description: '', image: '', standard: '' }, - networkClientId: testNetworkClientId, + // networkClientId: testNetworkClientId, }); nock('https://api.pudgypenguins.io/lil').get('/4').reply(200, { @@ -4964,13 +5290,14 @@ describe('NftController', () => { standard: 'ERC721', tokenId: '3', tokenURI: 'https://api.pudgypenguins.io/lil/4', + chainId: convertHexToDecimal(ChainId.sepolia), }, ]; // Make first call to updateNftMetadata should trigger state update await nftController.updateNftMetadata({ nfts: testInputNfts, - networkClientId: testNetworkClientId, + // networkClientId: testNetworkClientId, }); expect(spy).toHaveBeenCalledTimes(1); @@ -4986,6 +5313,7 @@ describe('NftController', () => { favorite: false, isCurrentlyOwned: true, tokenURI: 'https://api.pudgypenguins.io/lil/4', + chainId: convertHexToDecimal(ChainId.sepolia), }); spy.mockClear(); @@ -4994,7 +5322,7 @@ describe('NftController', () => { const spy2 = jest.spyOn(nftController, 'updateNft'); await nftController.updateNftMetadata({ nfts: testInputNfts, - networkClientId: testNetworkClientId, + // networkClientId: testNetworkClientId, }); // No updates to state should be made expect(spy2).toHaveBeenCalledTimes(0); @@ -5007,9 +5335,9 @@ describe('NftController', () => { }); spy.mockClear(); - await nftController.addNft('0xtest', '4', { + await nftController.addNft('0xtest', '4', testNetworkClientId, { nftMetadata: { name: '', description: '', image: '', standard: '' }, - networkClientId: testNetworkClientId, + // networkClientId: testNetworkClientId, }); const testInputNfts2: Nft[] = [ @@ -5023,13 +5351,14 @@ describe('NftController', () => { standard: 'ERC721', tokenId: '4', tokenURI: 'https://api.pudgypenguins.io/lil/4', + chainId: convertHexToDecimal(ChainId.sepolia), }, ]; const spy3 = jest.spyOn(nftController, 'updateNft'); await nftController.updateNftMetadata({ nfts: testInputNfts2, - networkClientId: testNetworkClientId, + // networkClientId: testNetworkClientId, }); // When the account changed, and updateNftMetadata is called state update should be triggered expect(spy3).toHaveBeenCalledTimes(1); diff --git a/packages/assets-controllers/src/NftController.ts b/packages/assets-controllers/src/NftController.ts index 795960fbe63..29139c0d7bb 100644 --- a/packages/assets-controllers/src/NftController.ts +++ b/packages/assets-controllers/src/NftController.ts @@ -26,13 +26,12 @@ import { NFT_API_BASE_URL, NFT_API_VERSION, convertHexToDecimal, + toHex, } from '@metamask/controller-utils'; import { type InternalAccount } from '@metamask/keyring-internal-api'; import type { NetworkClientId, NetworkControllerGetNetworkClientByIdAction, - NetworkControllerNetworkDidChangeEvent, - NetworkState, } from '@metamask/network-controller'; import type { PreferencesControllerStateChangeEvent, @@ -68,8 +67,9 @@ import type { GetCollectionsResponse, TopBid, } from './NftDetectionController'; +import type { NetworkControllerGetNetworkClientIdByChainIdAction } from '../../network-controller/src/NetworkController'; -type NFTStandardType = 'ERC721' | 'ERC1155'; +export type NFTStandardType = 'ERC721' | 'ERC1155'; type SuggestedNftMeta = { asset: { address: string; tokenId: string } & NftMetadata; @@ -244,11 +244,11 @@ export type AllowedActions = | AssetsContractControllerGetERC721TokenURIAction | AssetsContractControllerGetERC721OwnerOfAction | AssetsContractControllerGetERC1155BalanceOfAction - | AssetsContractControllerGetERC1155TokenURIAction; + | AssetsContractControllerGetERC1155TokenURIAction + | NetworkControllerGetNetworkClientIdByChainIdAction; export type AllowedEvents = | PreferencesControllerStateChangeEvent - | NetworkControllerNetworkDidChangeEvent | AccountsControllerSelectedEvmAccountChangeEvent; export type NftControllerStateChangeEvent = ControllerStateChangeEvent< @@ -294,8 +294,6 @@ export class NftController extends BaseController< #selectedAccountId: string; - #chainId: Hex; - #ipfsGateway: string; #openSeaEnabled: boolean; @@ -316,7 +314,6 @@ export class NftController extends BaseController< * Creates an NftController instance. * * @param options - The controller options. - * @param options.chainId - The chain ID of the current network. * @param options.ipfsGateway - The configured IPFS gateway. * @param options.openSeaEnabled - Controls whether the OpenSea API is used. * @param options.useIpfsSubdomains - Controls whether IPFS subdomains are used. @@ -327,7 +324,6 @@ export class NftController extends BaseController< * @param options.state - Initial state to set on this controller. */ constructor({ - chainId: initialChainId, ipfsGateway = IPFS_DEFAULT_GATEWAY_URL, openSeaEnabled = false, useIpfsSubdomains = true, @@ -336,7 +332,6 @@ export class NftController extends BaseController< messenger, state = {}, }: { - chainId: Hex; ipfsGateway?: string; openSeaEnabled?: boolean; useIpfsSubdomains?: boolean; @@ -364,7 +359,6 @@ export class NftController extends BaseController< this.#selectedAccountId = this.messagingSystem.call( 'AccountsController:getSelectedAccount', ).id; - this.#chainId = initialChainId; this.#ipfsGateway = ipfsGateway; this.#openSeaEnabled = openSeaEnabled; this.#useIpfsSubdomains = useIpfsSubdomains; @@ -378,11 +372,6 @@ export class NftController extends BaseController< this.#onPreferencesControllerStateChange.bind(this), ); - this.messagingSystem.subscribe( - 'NetworkController:networkDidChange', - this.#onNetworkControllerNetworkDidChange.bind(this), - ); - this.messagingSystem.subscribe( 'AccountsController:selectedEvmAccountChange', // TODO: Either fix this lint violation or explain why it's necessary to ignore. @@ -391,25 +380,9 @@ export class NftController extends BaseController< ); } - /** - * Handles the network change on the network controller. - * @param networkState - The new state of the preference controller. - * @param networkState.selectedNetworkClientId - The current selected network client id. - */ - #onNetworkControllerNetworkDidChange({ - selectedNetworkClientId, - }: NetworkState) { - const { - configuration: { chainId }, - } = this.messagingSystem.call( - 'NetworkController:getNetworkClientById', - selectedNetworkClientId, - ); - this.#chainId = chainId; - } - /** * Handles the state change of the preference controller. + * * @param preferencesState - The new state of the preference controller. * @param preferencesState.ipfsGateway - The configured IPFS gateway. * @param preferencesState.openSeaEnabled - Controls whether the OpenSea API is used. @@ -444,6 +417,7 @@ export class NftController extends BaseController< /** * Handles the selected account change on the accounts controller. + * * @param internalAccount - The new selected account. */ async #onSelectedAccountChange(internalAccount: InternalAccount) { @@ -631,7 +605,7 @@ export class NftController extends BaseController< async #getNftInformationFromTokenURI( contractAddress: string, tokenId: string, - networkClientId?: NetworkClientId, + networkClientId: NetworkClientId, ): Promise { const result = await this.#getNftURIAndStandard( contractAddress, @@ -722,7 +696,7 @@ export class NftController extends BaseController< async #getNftURIAndStandard( contractAddress: string, tokenId: string, - networkClientId?: NetworkClientId, + networkClientId: NetworkClientId, ): Promise<[string, string]> { // try ERC721 uri try { @@ -778,11 +752,14 @@ export class NftController extends BaseController< async #getNftInformation( contractAddress: string, tokenId: string, - networkClientId?: NetworkClientId, + networkClientId: NetworkClientId, ): Promise { - const chainId = this.#getCorrectChainId({ + const { + configuration: { chainId }, + } = this.messagingSystem.call( + 'NetworkController:getNetworkClientById', networkClientId, - }); + ); const [blockchainMetadata, nftApiMetadata] = await Promise.all([ safelyExecute(() => this.#getNftInformationFromTokenURI( @@ -817,8 +794,9 @@ export class NftController extends BaseController< * @returns Promise resolving to the current NFT name and image. */ async #getNftContractInformationFromContract( + // TODO for calls to blockchain we need to explicitly pass the currentNetworkClientId since its relying on the provider contractAddress: string, - networkClientId?: NetworkClientId, + networkClientId: NetworkClientId, ): Promise< Partial & Pick & @@ -855,7 +833,7 @@ export class NftController extends BaseController< async #getNftContractInformation( contractAddress: string, nftMetadataFromApi: NftMetadata, - networkClientId?: NetworkClientId, + networkClientId: NetworkClientId, ): Promise< Partial & Pick & @@ -1015,37 +993,38 @@ export class NftController extends BaseController< /** * Adds an NFT contract to the stored NFT contracts list. * + * @param networkClientId - The networkClientId that can be used to identify the network client to use for this request. * @param options - options. * @param options.tokenAddress - Hex address of the NFT contract. * @param options.userAddress - The address of the account where the NFT is being added. * @param options.nftMetadata - The retrieved NFTMetadata from API. - * @param options.networkClientId - The networkClientId that can be used to identify the network client to use for this request. * @param options.source - Whether the NFT was detected, added manually or suggested by a dapp. - * @param options.chainIdHex - The chainId to add the NFT contract to. * @returns Promise resolving to the current NFT contracts list. */ - async #addNftContract({ - tokenAddress, - userAddress, - networkClientId, - source, - nftMetadata, - chainIdHex, - }: { - tokenAddress: string; - userAddress: string; - nftMetadata: NftMetadata; - networkClientId?: NetworkClientId; - source?: Source; - chainIdHex?: Hex; - }): Promise { + async #addNftContract( + networkClientId: NetworkClientId, + { + tokenAddress, + userAddress, + source, + nftMetadata, + }: { + tokenAddress: string; + userAddress: string; + nftMetadata: NftMetadata; + source?: Source; + }, + ): Promise { const releaseLock = await this.#mutex.acquire(); try { const checksumHexAddress = toChecksumHexAddress(tokenAddress); const { allNftContracts } = this.state; - // TODO: revisit this with Solana support and instead of passing chainId, make sure chainId is read from nftMetadata when nftMetadata is available - const chainId = - chainIdHex || this.#getCorrectChainId({ networkClientId }); + const { + configuration: { chainId }, + } = this.messagingSystem.call( + 'NetworkController:getNetworkClientById', + networkClientId as NetworkClientId, + ); const nftContracts = allNftContracts[userAddress]?.[chainId] || []; @@ -1246,7 +1225,7 @@ export class NftController extends BaseController< asset: NftAsset, type: NFTStandardType, userAddress: string, - { networkClientId }: { networkClientId?: NetworkClientId } = {}, + networkClientId: NetworkClientId, ) { const { address: contractAddress, tokenId } = asset; @@ -1281,7 +1260,7 @@ export class NftController extends BaseController< userAddress, contractAddress, tokenId, - { networkClientId }, + networkClientId, ); if (!isOwner) { throw rpcErrors.invalidInput( @@ -1297,25 +1276,6 @@ export class NftController extends BaseController< } } - // temporary method to get the correct chainId until we remove chainId from the config & the chainId arg from the detection logic - // Just a helper method to prefer the networkClient chainId first then the chainId argument and then finally the config chainId - #getCorrectChainId({ - networkClientId, - }: { - networkClientId?: NetworkClientId; - }) { - if (networkClientId) { - const { - configuration: { chainId }, - } = this.messagingSystem.call( - 'NetworkController:getNetworkClientById', - networkClientId, - ); - return chainId; - } - return this.#chainId; - } - /** * Adds a new suggestedAsset to state. Parameters will be validated according to * asset type being watched. A `:pending` hub event will be emitted once added. @@ -1325,8 +1285,8 @@ export class NftController extends BaseController< * @param asset.tokenId - The ID of the asset. * @param type - The asset type. * @param origin - Domain origin to register the asset from. + * @param networkClientId - The networkClientId that can be used to identify the network client to use for this request. * @param options - Options bag. - * @param options.networkClientId - The networkClientId that can be used to identify the network client to use for this request. * @param options.userAddress - The address of the account where the NFT is being added. * @returns Object containing a Promise resolving to the suggestedAsset address if accepted. */ @@ -1334,11 +1294,10 @@ export class NftController extends BaseController< asset: NftAsset, type: NFTStandardType, origin: string, + networkClientId: NetworkClientId, { - networkClientId, userAddress, }: { - networkClientId?: NetworkClientId; userAddress?: string; } = {}, ) { @@ -1346,8 +1305,11 @@ export class NftController extends BaseController< if (!addressToSearch) { return; } + if (!networkClientId) { + throw rpcErrors.invalidParams('Network client id is required'); + } - await this.#validateWatchNft(asset, type, addressToSearch); + await this.#validateWatchNft(asset, type, addressToSearch, networkClientId); const nftMetadata = await this.#getNftInformation( asset.address, @@ -1372,8 +1334,7 @@ export class NftController extends BaseController< await this._requestApproval(suggestedNftMeta); const { address, tokenId } = asset; const { name, standard, description, image } = nftMetadata; - - await this.addNft(address, tokenId, { + await this.addNft(address, tokenId, networkClientId, { nftMetadata: { name: name ?? null, description: description ?? null, @@ -1382,7 +1343,6 @@ export class NftController extends BaseController< }, userAddress, source: Source.Dapp, - networkClientId, }); } @@ -1401,19 +1361,14 @@ export class NftController extends BaseController< * @param ownerAddress - User public address. * @param nftAddress - NFT contract address. * @param tokenId - NFT token ID. - * @param options - Options bag. - * @param options.networkClientId - The networkClientId that can be used to identify the network client to use for this request. + * @param networkClientId - The networkClientId that can be used to identify the network client to use for this request. * @returns Promise resolving the NFT ownership. */ async isNftOwner( ownerAddress: string, nftAddress: string, tokenId: string, - { - networkClientId, - }: { - networkClientId?: NetworkClientId; - } = {}, + networkClientId: NetworkClientId, ): Promise { // Checks the ownership for ERC-721. try { @@ -1455,35 +1410,37 @@ export class NftController extends BaseController< * * @param address - Hex address of the NFT contract. * @param tokenId - The NFT identifier. + * @param networkClientId - The networkClientId that can be used to identify the network client to use for this request. * @param options - an object of arguments * @param options.userAddress - The address of the current user. - * @param options.networkClientId - The networkClientId that can be used to identify the network client to use for this request. * @param options.source - Whether the NFT was detected, added manually or suggested by a dapp. */ async addNftVerifyOwnership( address: string, tokenId: string, + networkClientId: NetworkClientId, { userAddress, - networkClientId, source, }: { userAddress?: string; - networkClientId?: NetworkClientId; source?: Source; } = {}, ) { const addressToSearch = this.#getAddressOrSelectedAddress(userAddress); if ( - !(await this.isNftOwner(addressToSearch, address, tokenId, { + !(await this.isNftOwner( + addressToSearch, + address, + tokenId, networkClientId, - })) + )) ) { throw new Error('This NFT is not owned by the user'); } - await this.addNft(address, tokenId, { - networkClientId, + + await this.addNft(address, tokenId, networkClientId, { userAddress: addressToSearch, source, }); @@ -1494,29 +1451,25 @@ export class NftController extends BaseController< * * @param tokenAddress - Hex address of the NFT contract. * @param tokenId - The NFT identifier. + * @param networkClientId - The networkClientId that can be used to identify the network client to use for this request. * @param options - an object of arguments * @param options.nftMetadata - NFT optional metadata. * @param options.userAddress - The address of the current user. * @param options.source - Whether the NFT was detected, added manually or suggested by a dapp. - * @param options.networkClientId - The networkClientId that can be used to identify the network client to use for this request. - * @param options.chainId - The chain ID to add the NFT to. * @returns Promise resolving to the current NFT list. */ async addNft( tokenAddress: string, tokenId: string, + networkClientId: NetworkClientId, { nftMetadata, userAddress, source = Source.Custom, - networkClientId, - chainId, }: { nftMetadata?: NftMetadata; userAddress?: string; source?: Source; - networkClientId?: NetworkClientId; - chainId?: Hex; } = {}, ) { const addressToSearch = this.#getAddressOrSelectedAddress(userAddress); @@ -1526,10 +1479,6 @@ export class NftController extends BaseController< const checksumHexAddress = toChecksumHexAddress(tokenAddress); - // TODO: revisit this with Solana support and instead of passing chainId, make sure chainId is read from nftMetadata - const chainIdToAddTo = - chainId || this.#getCorrectChainId({ networkClientId }); - nftMetadata = nftMetadata || (await this.#getNftInformation( @@ -1538,13 +1487,11 @@ export class NftController extends BaseController< networkClientId, )); - const newNftContracts = await this.#addNftContract({ + const newNftContracts = await this.#addNftContract(networkClientId, { tokenAddress: checksumHexAddress, userAddress: addressToSearch, - networkClientId, source, nftMetadata, - chainIdHex: source === Source.Detected ? chainIdToAddTo : undefined, }); // If NFT contract was not added, do not add individual NFT @@ -1552,9 +1499,16 @@ export class NftController extends BaseController< (contract) => contract.address.toLowerCase() === checksumHexAddress.toLowerCase(), ); + const { + configuration: { chainId }, + } = this.messagingSystem.call( + 'NetworkController:getNetworkClientById', + networkClientId, + ); // This is the case when the NFT is added manually and not detected automatically + // TODO: An improvement would be to make the chainId a required field and return it when getting the NFT information if (!nftMetadata.chainId) { - nftMetadata.chainId = convertHexToDecimal(chainIdToAddTo); + nftMetadata.chainId = convertHexToDecimal(chainId); } // If NFT contract information, add individual NFT @@ -1564,7 +1518,7 @@ export class NftController extends BaseController< tokenId, nftMetadata, nftContract, - chainIdToAddTo, + chainId, addressToSearch, source, ); @@ -1577,24 +1531,19 @@ export class NftController extends BaseController< * @param options - Options for refetching NFT metadata * @param options.nfts - nfts to update metadata for. * @param options.userAddress - The current user address - * @param options.networkClientId - The networkClientId that can be used to identify the network client to use for this request. */ async updateNftMetadata({ nfts, userAddress, - networkClientId, }: { nfts: Nft[]; userAddress?: string; - networkClientId?: NetworkClientId; }) { const addressToSearch = this.#getAddressOrSelectedAddress(userAddress); const releaseLock = await this.#mutex.acquire(); try { - const chainId = this.#getCorrectChainId({ networkClientId }); - const nftsWithChecksumAdr = nfts.map((nft) => { return { ...nft, @@ -1603,11 +1552,18 @@ export class NftController extends BaseController< }); const nftMetadataResults = await Promise.all( nftsWithChecksumAdr.map(async (nft) => { - const resMetadata = await this.#getNftInformation( - nft.address, - nft.tokenId, - networkClientId, + // Each NFT should have a chainId; convert nft.chainId to networkClientId + const networkClientId = this.messagingSystem.call( + 'NetworkController:getNetworkClientIdByChainId', + toHex(nft.chainId as number), ); + const resMetadata = networkClientId + ? await this.#getNftInformation( + nft.address, + nft.tokenId, + networkClientId, + ) + : undefined; return { nft, newMetadata: resMetadata, @@ -1618,30 +1574,48 @@ export class NftController extends BaseController< // We want to avoid updating the state if the state and fetched nft info are the same const nftsWithDifferentMetadata: NftUpdate[] = []; const { allNfts } = this.state; - const stateNfts = allNfts[addressToSearch]?.[chainId] || []; - - nftMetadataResults.forEach((singleNft) => { - const existingEntry: Nft | undefined = stateNfts.find( - (nft) => - nft.address.toLowerCase() === singleNft.nft.address.toLowerCase() && - nft.tokenId === singleNft.nft.tokenId, + // get from state allNfts that match nftsWithChecksumAdr + const stateNfts = nftsWithChecksumAdr.map((nft) => { + return allNfts[addressToSearch]?.[toHex(nft.chainId as number)]?.find( + (nftElement) => + nftElement.address.toLowerCase() === nft.address.toLowerCase() && + nftElement.tokenId === nft.tokenId, ); + }); - if (existingEntry) { - const differentMetadata = compareNftMetadata( - singleNft.newMetadata, - existingEntry, + nftMetadataResults.forEach( + (singleNft: { nft: Nft; newMetadata: NftMetadata | undefined }) => { + const existingEntry: Nft | undefined = stateNfts.find( + (nft) => + nft?.address.toLowerCase() === + singleNft.nft.address.toLowerCase() && + nft?.tokenId === singleNft.nft.tokenId, ); - if (differentMetadata) { - nftsWithDifferentMetadata.push(singleNft); + if (existingEntry && singleNft.newMetadata) { + const differentMetadata = compareNftMetadata( + singleNft.newMetadata, + existingEntry, + ); + + if (differentMetadata) { + nftsWithDifferentMetadata.push({ + nft: singleNft.nft, + newMetadata: singleNft.newMetadata, + }); + } } - } - }); + }, + ); if (nftsWithDifferentMetadata.length !== 0) { nftsWithDifferentMetadata.forEach((elm) => - this.updateNft(elm.nft, elm.newMetadata, addressToSearch, chainId), + this.updateNft( + elm.nft, + elm.newMetadata, + addressToSearch, + toHex(elm.nft.chainId as number), + ), ); } } finally { @@ -1654,20 +1628,25 @@ export class NftController extends BaseController< * * @param address - Hex address of the NFT contract. * @param tokenId - Token identifier of the NFT. + * @param networkClientId - The networkClientId that can be used to identify the network client to use for this request. * @param options - an object of arguments - * @param options.networkClientId - The networkClientId that can be used to identify the network client to use for this request. * @param options.userAddress - The address of the account where the NFT is being removed. */ removeNft( address: string, tokenId: string, - { - networkClientId, - userAddress, - }: { networkClientId?: NetworkClientId; userAddress?: string } = {}, + networkClientId: NetworkClientId, + { userAddress }: { userAddress?: string } = {}, ) { const addressToSearch = this.#getAddressOrSelectedAddress(userAddress); - const chainId = this.#getCorrectChainId({ networkClientId }); + + const { + configuration: { chainId }, + } = this.messagingSystem.call( + 'NetworkController:getNetworkClientById', + networkClientId as NetworkClientId, + ); + const checksumHexAddress = toChecksumHexAddress(address); this.#removeIndividualNft(checksumHexAddress, tokenId, { chainId, @@ -1692,20 +1671,23 @@ export class NftController extends BaseController< * * @param address - Hex address of the NFT contract. * @param tokenId - Token identifier of the NFT. + * @param networkClientId - The networkClientId that can be used to identify the network client to use for this request. * @param options - an object of arguments - * @param options.networkClientId - The networkClientId that can be used to identify the network client to use for this request. * @param options.userAddress - The address of the account where the NFT is being removed. */ removeAndIgnoreNft( address: string, tokenId: string, - { - networkClientId, - userAddress, - }: { networkClientId?: NetworkClientId; userAddress?: string } = {}, + networkClientId: NetworkClientId, + { userAddress }: { userAddress?: string } = {}, ) { const addressToSearch = this.#getAddressOrSelectedAddress(userAddress); - const chainId = this.#getCorrectChainId({ networkClientId }); + const { + configuration: { chainId }, + } = this.messagingSystem.call( + 'NetworkController:getNetworkClientById', + networkClientId as NetworkClientId, + ); const checksumHexAddress = toChecksumHexAddress(address); this.#removeAndIgnoreIndividualNft(checksumHexAddress, tokenId, { chainId, @@ -1739,27 +1721,33 @@ export class NftController extends BaseController< * * @param nft - The NFT object to check and update. * @param batch - A boolean indicating whether this method is being called as part of a batch or single update. + * @param networkClientId - The networkClientId that can be used to identify the network client to use for this request. * @param accountParams - The userAddress and chainId to check ownership against * @param accountParams.userAddress - the address passed through the confirmed transaction flow to ensure assets are stored to the correct account - * @param accountParams.networkClientId - The networkClientId that can be used to identify the network client to use for this request. * @returns the NFT with the updated isCurrentlyOwned value */ async checkAndUpdateSingleNftOwnershipStatus( nft: Nft, batch: boolean, - { - userAddress, - networkClientId, - }: { networkClientId?: NetworkClientId; userAddress?: string } = {}, + networkClientId: NetworkClientId, + { userAddress }: { userAddress?: string } = {}, ) { const addressToSearch = this.#getAddressOrSelectedAddress(userAddress); - const chainId = this.#getCorrectChainId({ networkClientId }); + const { + configuration: { chainId }, + } = this.messagingSystem.call( + 'NetworkController:getNetworkClientById', + networkClientId as NetworkClientId, + ); const { address, tokenId } = nft; let isOwned = nft.isCurrentlyOwned; try { - isOwned = await this.isNftOwner(addressToSearch, address, tokenId, { + isOwned = await this.isNftOwner( + addressToSearch, + address, + tokenId, networkClientId, - }); + ); } catch { // ignore error // this will only throw an error 'Unable to verify ownership' in which case @@ -1807,28 +1795,39 @@ export class NftController extends BaseController< /** * Checks whether NFTs associated with current selectedAddress/chainId combination are still owned by the user * And updates the isCurrentlyOwned value on each accordingly. + * + * @param networkClientId - The networkClientId that can be used to identify the network client to use for this request. * @param options - an object of arguments - * @param options.networkClientId - The networkClientId that can be used to identify the network client to use for this request. * @param options.userAddress - The address of the account where the NFT ownership status is checked/updated. */ - async checkAndUpdateAllNftsOwnershipStatus({ - networkClientId, - userAddress, - }: { - networkClientId?: NetworkClientId; - userAddress?: string; - } = {}) { + async checkAndUpdateAllNftsOwnershipStatus( + networkClientId: NetworkClientId, + { + userAddress, + }: { + userAddress?: string; + } = {}, + ) { const addressToSearch = this.#getAddressOrSelectedAddress(userAddress); - const chainId = this.#getCorrectChainId({ networkClientId }); + const { + configuration: { chainId }, + } = this.messagingSystem.call( + 'NetworkController:getNetworkClientById', + networkClientId as NetworkClientId, + ); const { allNfts } = this.state; const nfts = allNfts[addressToSearch]?.[chainId] || []; const updatedNfts = await Promise.all( nfts.map(async (nft) => { return ( - (await this.checkAndUpdateSingleNftOwnershipStatus(nft, true, { + (await this.checkAndUpdateSingleNftOwnershipStatus( + nft, + true, networkClientId, - userAddress, - })) ?? nft + { + userAddress, + }, + )) ?? nft ); }), ); @@ -1845,24 +1844,28 @@ export class NftController extends BaseController< * @param address - Hex address of the NFT contract. * @param tokenId - Hex address of the NFT contract. * @param favorite - NFT new favorite status. + * @param networkClientId - The networkClientId that can be used to identify the network client to use for this request. * @param options - an object of arguments - * @param options.networkClientId - The networkClientId that can be used to identify the network client to use for this request. * @param options.userAddress - The address of the account where the NFT is being removed. */ updateNftFavoriteStatus( address: string, tokenId: string, favorite: boolean, + networkClientId: NetworkClientId, { - networkClientId, userAddress, }: { - networkClientId?: NetworkClientId; userAddress?: string; } = {}, ) { const addressToSearch = this.#getAddressOrSelectedAddress(userAddress); - const chainId = this.#getCorrectChainId({ networkClientId }); + const { + configuration: { chainId }, + } = this.messagingSystem.call( + 'NetworkController:getNetworkClientById', + networkClientId as NetworkClientId, + ); const { allNfts } = this.state; const nfts = [...(allNfts[addressToSearch]?.[chainId] || [])]; const index: number = nfts.findIndex( @@ -2066,9 +2069,17 @@ export class NftController extends BaseController< return selectedAccount?.address || ''; } + /** + * Updates the all nfts in state for the account. + * Nfts will be updated if they don't have a name, description or image. + * + * @param account - The account to update the NFT metadata for. + */ async #updateNftUpdateForAccount(account: InternalAccount) { - const nfts: Nft[] = - this.state.allNfts[account.address]?.[this.#chainId] ?? []; + // get all nfts for the account for all chains + const nfts: Nft[] = Object.values( + this.state.allNfts[account.address] || {}, + ).flat(); // Filter only nfts const nftsToUpdate = nfts.filter( @@ -2079,6 +2090,7 @@ export class NftController extends BaseController< nftsToUpdate.length !== 0 && nftsToUpdate.length < NFT_UPDATE_THRESHOLD ) { + // TODO: get the chainId for the NFT await this.updateNftMetadata({ nfts: nftsToUpdate, userAddress: account.address, diff --git a/packages/assets-controllers/src/NftDetectionController.test.ts b/packages/assets-controllers/src/NftDetectionController.test.ts index 07f4165d8f6..bd98f7cc76f 100644 --- a/packages/assets-controllers/src/NftDetectionController.test.ts +++ b/packages/assets-controllers/src/NftDetectionController.test.ts @@ -13,6 +13,7 @@ import type { NetworkClient, NetworkClientConfiguration, NetworkClientId, + NetworkController, NetworkState, } from '@metamask/network-controller'; import { @@ -26,7 +27,10 @@ import { FakeBlockTracker } from '../../../tests/fake-block-tracker'; import { FakeProvider } from '../../../tests/fake-provider'; import { advanceTime } from '../../../tests/helpers'; import { createMockInternalAccount } from '../../accounts-controller/src/tests/mocks'; -import { buildMockGetNetworkClientById } from '../../network-controller/tests/helpers'; +import { + buildMockGetNetworkClientByChainId, + buildMockGetNetworkClientById, +} from '../../network-controller/tests/helpers'; import { Source } from './constants'; import { getDefaultNftControllerState } from './NftController'; import { @@ -551,6 +555,7 @@ describe('NftDetectionController', () => { expect(mockAddNft).toHaveBeenCalledWith( '0xebE4e5E773AFD2bAc25De0cFafa084CFb3cBf1eD', '2574', + 'mainnet', { nftMetadata: { description: 'Description 2574', @@ -562,7 +567,6 @@ describe('NftDetectionController', () => { }, userAddress: selectedAccount.address, source: Source.Detected, - chainId: '0x1', }, ); }, @@ -608,6 +612,7 @@ describe('NftDetectionController', () => { 1, '0xebE4e5E773AFD2bAc25De0cFafa084CFb3cBf1e5', '2', + 'linea-mainnet', { nftMetadata: { description: 'Description 2', @@ -619,13 +624,13 @@ describe('NftDetectionController', () => { }, userAddress: selectedAccount.address, source: Source.Detected, - chainId: '0xe708', }, ); expect(mockAddNft).toHaveBeenNthCalledWith( 2, '0xebE4e5E773AFD2bAc25De0cFafa084CFb3cBf1eD', '2574', + 'mainnet', { nftMetadata: { description: 'Description 2574', @@ -637,7 +642,6 @@ describe('NftDetectionController', () => { }, userAddress: selectedAccount.address, source: Source.Detected, - chainId: '0x1', }, ); }, @@ -732,6 +736,7 @@ describe('NftDetectionController', () => { 1, '0xtestCollection1', '1', + 'mainnet', { nftMetadata: { description: 'Description 1', @@ -746,13 +751,13 @@ describe('NftDetectionController', () => { }, userAddress: selectedAccount.address, source: Source.Detected, - chainId: '0x1', }, ); expect(mockAddNft).toHaveBeenNthCalledWith( 2, '0xtestCollection1', '2', + 'mainnet', { nftMetadata: { description: 'Description 2', @@ -767,7 +772,6 @@ describe('NftDetectionController', () => { }, userAddress: selectedAccount.address, source: Source.Detected, - chainId: '0x1', }, ); }, @@ -805,38 +809,48 @@ describe('NftDetectionController', () => { await controller.detectNfts(['0x1']); // Expect to be called twice - expect(mockAddNft).toHaveBeenNthCalledWith(1, '0xtest1', '2574', { - nftMetadata: { - description: 'Description 2574', - image: 'image/2574.png', - name: 'ID 2574', - standard: 'ERC721', - imageOriginal: 'imageOriginal/2574.png', - collection: { - id: '0xtest1', + expect(mockAddNft).toHaveBeenNthCalledWith( + 1, + '0xtest1', + '2574', + 'mainnet', + { + nftMetadata: { + description: 'Description 2574', + image: 'image/2574.png', + name: 'ID 2574', + standard: 'ERC721', + imageOriginal: 'imageOriginal/2574.png', + collection: { + id: '0xtest1', + }, + chainId: 1, }, - chainId: 1, + userAddress: selectedAccount.address, + source: Source.Detected, }, - userAddress: selectedAccount.address, - source: Source.Detected, - chainId: '0x1', - }); - expect(mockAddNft).toHaveBeenNthCalledWith(2, '0xtest2', '2575', { - nftMetadata: { - description: 'Description 2575', - image: 'image/2575.png', - name: 'ID 2575', - standard: 'ERC721', - imageOriginal: 'imageOriginal/2575.png', - collection: { - id: '0xtest2', + ); + expect(mockAddNft).toHaveBeenNthCalledWith( + 2, + '0xtest2', + '2575', + 'mainnet', + { + nftMetadata: { + description: 'Description 2575', + image: 'image/2575.png', + name: 'ID 2575', + standard: 'ERC721', + imageOriginal: 'imageOriginal/2575.png', + collection: { + id: '0xtest2', + }, + chainId: 1, }, - chainId: 1, + userAddress: selectedAccount.address, + source: Source.Detected, }, - userAddress: selectedAccount.address, - source: Source.Detected, - chainId: '0x1', - }); + ); }, ); }); @@ -916,49 +930,59 @@ describe('NftDetectionController', () => { await controller.detectNfts(['0x1']); // Expect to be called twice - expect(mockAddNft).toHaveBeenNthCalledWith(1, '0xtest1', '2574', { - nftMetadata: { - description: 'Description 2574', - image: 'image/2574.png', - name: 'ID 2574', - standard: 'ERC721', - imageOriginal: 'imageOriginal/2574.png', - collection: { - id: '0xtest1', - contractDeployedAt: undefined, - creator: '0xcreator1', - openseaVerificationStatus: 'verified', - ownerCount: undefined, - tokenCount: undefined, - topBid: testTopBid, + expect(mockAddNft).toHaveBeenNthCalledWith( + 1, + '0xtest1', + '2574', + 'mainnet', + { + nftMetadata: { + description: 'Description 2574', + image: 'image/2574.png', + name: 'ID 2574', + standard: 'ERC721', + imageOriginal: 'imageOriginal/2574.png', + collection: { + id: '0xtest1', + contractDeployedAt: undefined, + creator: '0xcreator1', + openseaVerificationStatus: 'verified', + ownerCount: undefined, + tokenCount: undefined, + topBid: testTopBid, + }, + chainId: 1, }, - chainId: 1, + userAddress: selectedAccount.address, + source: Source.Detected, }, - userAddress: selectedAccount.address, - source: Source.Detected, - chainId: '0x1', - }); - expect(mockAddNft).toHaveBeenNthCalledWith(2, '0xtest2', '2575', { - nftMetadata: { - description: 'Description 2575', - image: 'image/2575.png', - name: 'ID 2575', - standard: 'ERC721', - imageOriginal: 'imageOriginal/2575.png', - collection: { - id: '0xtest2', - contractDeployedAt: undefined, - creator: '0xcreator2', - openseaVerificationStatus: 'verified', - ownerCount: undefined, - tokenCount: undefined, + ); + expect(mockAddNft).toHaveBeenNthCalledWith( + 2, + '0xtest2', + '2575', + 'mainnet', + { + nftMetadata: { + description: 'Description 2575', + image: 'image/2575.png', + name: 'ID 2575', + standard: 'ERC721', + imageOriginal: 'imageOriginal/2575.png', + collection: { + id: '0xtest2', + contractDeployedAt: undefined, + creator: '0xcreator2', + openseaVerificationStatus: 'verified', + ownerCount: undefined, + tokenCount: undefined, + }, + chainId: 1, }, - chainId: 1, + userAddress: selectedAccount.address, + source: Source.Detected, }, - userAddress: selectedAccount.address, - source: Source.Detected, - chainId: '0x1', - }); + ); }, ); }); @@ -1017,6 +1041,7 @@ describe('NftDetectionController', () => { 1, '0xtestCollection1', '1', + 'mainnet', { nftMetadata: { description: 'Description 1', @@ -1036,13 +1061,13 @@ describe('NftDetectionController', () => { }, userAddress: selectedAccount.address, source: Source.Detected, - chainId: '0x1', }, ); expect(mockAddNft).toHaveBeenNthCalledWith( 2, '0xtestCollection2', '2', + 'mainnet', { nftMetadata: { description: 'Description 2', @@ -1062,7 +1087,6 @@ describe('NftDetectionController', () => { }, userAddress: selectedAccount.address, source: Source.Detected, - chainId: '0x1', }, ); }, @@ -1170,6 +1194,7 @@ describe('NftDetectionController', () => { 1, '0xtestCollection1', '1', + 'mainnet', { nftMetadata: { description: 'Description 1', @@ -1189,13 +1214,13 @@ describe('NftDetectionController', () => { }, userAddress: selectedAccount.address, source: Source.Detected, - chainId: '0x1', }, ); expect(mockAddNft).toHaveBeenNthCalledWith( 2, '0xtestCollection1', '2', + 'mainnet', { nftMetadata: { description: 'Description 2', @@ -1215,7 +1240,6 @@ describe('NftDetectionController', () => { }, userAddress: selectedAccount.address, source: Source.Detected, - chainId: '0x1', }, ); }, @@ -1297,6 +1321,7 @@ describe('NftDetectionController', () => { 1, '0xtestCollection1', '1', + 'mainnet', { nftMetadata: { chainId: 1, @@ -1315,7 +1340,6 @@ describe('NftDetectionController', () => { }, userAddress: selectedAccount.address, source: Source.Detected, - chainId: '0x1', }, ); }, @@ -1372,43 +1396,53 @@ describe('NftDetectionController', () => { await controller.detectNfts(['0x1']); // Expect to be called twice - expect(mockAddNft).toHaveBeenNthCalledWith(1, '0xtest1', '2574', { - nftMetadata: { - description: 'Description 2574', - image: 'image/2574.png', - name: 'ID 2574', - standard: 'ERC721', - imageOriginal: 'imageOriginal/2574.png', - collection: { - id: '0xtest1', - contractDeployedAt: undefined, - creator: '0xcreator1', - openseaVerificationStatus: 'verified', - ownerCount: undefined, - tokenCount: undefined, + expect(mockAddNft).toHaveBeenNthCalledWith( + 1, + '0xtest1', + '2574', + 'mainnet', + { + nftMetadata: { + description: 'Description 2574', + image: 'image/2574.png', + name: 'ID 2574', + standard: 'ERC721', + imageOriginal: 'imageOriginal/2574.png', + collection: { + id: '0xtest1', + contractDeployedAt: undefined, + creator: '0xcreator1', + openseaVerificationStatus: 'verified', + ownerCount: undefined, + tokenCount: undefined, + }, + chainId: 1, }, - chainId: 1, + userAddress: selectedAccount.address, + source: Source.Detected, }, - userAddress: selectedAccount.address, - source: Source.Detected, - chainId: '0x1', - }); - expect(mockAddNft).toHaveBeenNthCalledWith(2, '0xtest2', '2575', { - nftMetadata: { - description: 'Description 2575', - image: 'image/2575.png', - name: 'ID 2575', - standard: 'ERC721', - imageOriginal: 'imageOriginal/2575.png', - collection: { - id: '0xtest2', + ); + expect(mockAddNft).toHaveBeenNthCalledWith( + 2, + '0xtest2', + '2575', + 'mainnet', + { + nftMetadata: { + description: 'Description 2575', + image: 'image/2575.png', + name: 'ID 2575', + standard: 'ERC721', + imageOriginal: 'imageOriginal/2575.png', + collection: { + id: '0xtest2', + }, + chainId: 1, }, - chainId: 1, + userAddress: selectedAccount.address, + source: Source.Detected, }, - userAddress: selectedAccount.address, - source: Source.Detected, - chainId: '0x1', - }); + ); Object.defineProperty(constants, 'MAX_GET_COLLECTION_BATCH_SIZE', { value: 20, @@ -1458,6 +1492,7 @@ describe('NftDetectionController', () => { expect(mockAddNft).toHaveBeenCalledWith( '0xebE4e5E773AFD2bAc25De0cFafa084CFb3cBf1eD', '2574', + 'mainnet', { nftMetadata: { description: 'Description 2574', @@ -1469,7 +1504,6 @@ describe('NftDetectionController', () => { }, userAddress: '0x9', source: Source.Detected, - chainId: '0x1', }, ); }, @@ -1845,6 +1879,9 @@ type WithControllerOptions = { mockNetworkState?: Partial; mockPreferencesState?: Partial; mockGetSelectedAccount?: jest.Mock; + mockGetNetworkClientIdByChainId?: jest.Mock< + NetworkController['getNetworkClientIdByChainId'] + >; }; type WithControllerArgs = @@ -1867,6 +1904,7 @@ async function withController( { options = {}, mockNetworkClientConfigurationsByNetworkClientId = {}, + mockGetNetworkClientIdByChainId = {}, mockNetworkState = {}, mockPreferencesState = {}, mockGetSelectedAccount = jest @@ -1894,11 +1932,20 @@ async function withController( const getNetworkClientById = buildMockGetNetworkClientById( mockNetworkClientConfigurationsByNetworkClientId, ); + const getNetworkClientIdByChainId = buildMockGetNetworkClientByChainId( + mockGetNetworkClientIdByChainId, + ); + messenger.registerActionHandler( 'NetworkController:getNetworkClientById', getNetworkClientById, ); + messenger.registerActionHandler( + 'NetworkController:getNetworkClientIdByChainId', + getNetworkClientIdByChainId, + ); + messenger.registerActionHandler( 'PreferencesController:getState', jest.fn().mockReturnValue({ @@ -1915,6 +1962,7 @@ async function withController( 'NetworkController:getNetworkClientById', 'PreferencesController:getState', 'AccountsController:getSelectedAccount', + 'NetworkController:getNetworkClientIdByChainId', ], allowedEvents: [ 'NetworkController:stateChange', diff --git a/packages/assets-controllers/src/NftDetectionController.ts b/packages/assets-controllers/src/NftDetectionController.ts index 31d6e91f67c..5eecef68b5c 100644 --- a/packages/assets-controllers/src/NftDetectionController.ts +++ b/packages/assets-controllers/src/NftDetectionController.ts @@ -33,6 +33,10 @@ import { type NftControllerState, type NftMetadata, } from './NftController'; +import type { + NetworkClientId, + NetworkControllerGetNetworkClientIdByChainIdAction, +} from '../../network-controller/src/NetworkController'; const controllerName = 'NftDetectionController'; @@ -43,7 +47,8 @@ export type AllowedActions = | NetworkControllerGetStateAction | NetworkControllerGetNetworkClientByIdAction | PreferencesControllerGetStateAction - | AccountsControllerGetSelectedAccountAction; + | AccountsControllerGetSelectedAccountAction + | NetworkControllerGetNetworkClientIdByChainIdAction; export type AllowedEvents = | PreferencesControllerStateChangeEvent @@ -794,12 +799,20 @@ export class NftDetectionController extends BaseController< collection && { collection }, chainId && { chainId }, ); - await this.#addNft(contract, tokenId, { - nftMetadata, - userAddress, - source: Source.Detected, - chainId: toHex(chainId), - }); + const networkClientId = this.messagingSystem.call( + 'NetworkController:getNetworkClientIdByChainId', + toHex(chainId as number), + ); + await this.#addNft( + contract, + tokenId, + networkClientId as NetworkClientId, + { + nftMetadata, + userAddress, + source: Source.Detected, + }, + ); } }); await Promise.all(addNftPromises); diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index c8ab5d227f3..8db9e7d259a 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -512,6 +512,11 @@ export type NetworkControllerGetNetworkClientByIdAction = { handler: NetworkController['getNetworkClientById']; }; +export type NetworkControllerGetNetworkClientIdByChainIdAction = { + type: `NetworkController:getNetworkClientIdByChainId`; + handler: NetworkController['getNetworkClientIdByChainId']; +}; + export type NetworkControllerGetSelectedNetworkClientAction = { type: `NetworkController:getSelectedNetworkClient`; handler: NetworkController['getSelectedNetworkClient']; @@ -587,7 +592,8 @@ export type NetworkControllerActions = | NetworkControllerGetNetworkConfigurationByNetworkClientId | NetworkControllerAddNetworkAction | NetworkControllerRemoveNetworkAction - | NetworkControllerUpdateNetworkAction; + | NetworkControllerUpdateNetworkAction + | NetworkControllerGetNetworkClientIdByChainIdAction; export type NetworkControllerMessenger = RestrictedMessenger< typeof controllerName, @@ -1155,6 +1161,11 @@ export class NetworkController extends BaseController< this.getNetworkClientById.bind(this), ); + this.messagingSystem.registerActionHandler( + `${this.name}:getNetworkClientIdByChainId`, + this.getNetworkClientIdByChainId.bind(this), + ); + this.messagingSystem.registerActionHandler( // TODO: Either fix this lint violation or explain why it's necessary to ignore. // eslint-disable-next-line @typescript-eslint/restrict-template-expressions @@ -1827,6 +1838,16 @@ export class NetworkController extends BaseController< return this.state.networkConfigurationsByChainId[chainId]; } + getNetworkClientIdByChainId(chainId: Hex): NetworkClientId | undefined { + const networkConfiguration = this.getNetworkConfigurationByChainId(chainId); + if (networkConfiguration) { + return networkConfiguration.rpcEndpoints[ + networkConfiguration.defaultRpcEndpointIndex + ]?.networkClientId; + } + return undefined; + } + /** * Returns the network configuration that contains an RPC endpoint with the * given network client ID. diff --git a/packages/network-controller/src/index.ts b/packages/network-controller/src/index.ts index 96d93fb02d9..85d62001873 100644 --- a/packages/network-controller/src/index.ts +++ b/packages/network-controller/src/index.ts @@ -39,6 +39,7 @@ export type { NetworkControllerRpcEndpointUnavailableEvent, NetworkControllerRpcEndpointDegradedEvent, NetworkControllerRpcEndpointRequestRetriedEvent, + NetworkControllerGetNetworkClientIdByChainIdAction, } from './NetworkController'; export { getDefaultNetworkControllerState, diff --git a/packages/network-controller/tests/NetworkController.test.ts b/packages/network-controller/tests/NetworkController.test.ts index e40de31fe2b..e2e6bbea62b 100644 --- a/packages/network-controller/tests/NetworkController.test.ts +++ b/packages/network-controller/tests/NetworkController.test.ts @@ -1206,6 +1206,72 @@ describe('NetworkController', () => { }); }); + describe('getNetworkClientIdByChainId', () => { + it('returns undefined if no networkConfiguration found for the given chainId', async () => { + const infuraProjectId = 'some-infura-project-id'; + + await withController( + { + state: { + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeCurrency: 'TOKEN1', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + failoverUrls: ['https://first.failover.endpoint'], + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network/1', + }), + ], + }), + }, + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + }, + infuraProjectId, + }, + async ({ controller }) => { + const networkClientId = + controller.getNetworkClientIdByChainId('0x1111'); + + expect(networkClientId).toBeUndefined(); + }, + ); + }); + + it('returns the correct network client ID for the given chainId', async () => { + const infuraProjectId = 'some-infura-project-id'; + + await withController( + { + state: { + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeCurrency: 'TOKEN1', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + failoverUrls: ['https://first.failover.endpoint'], + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network/1', + }), + ], + }), + }, + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + }, + infuraProjectId, + }, + async ({ controller }) => { + const networkClientId = + controller.getNetworkClientIdByChainId('0x1337'); + + expect(networkClientId).toBe('AAAA-AAAA-AAAA-AAAA'); + }, + ); + }); + }); + describe('getNetworkClientRegistry', () => { describe('if no network configurations were specified at initialization', () => { it('returns network clients for default RPC endpoints, keyed by network client ID', async () => { diff --git a/packages/network-controller/tests/helpers.ts b/packages/network-controller/tests/helpers.ts index 30def58c9ef..9aa85ff5612 100644 --- a/packages/network-controller/tests/helpers.ts +++ b/packages/network-controller/tests/helpers.ts @@ -6,6 +6,7 @@ import { NetworksTicker, toHex, } from '@metamask/controller-utils'; +import type { Hex } from '@metamask/utils'; import { v4 as uuidV4 } from 'uuid'; import { FakeBlockTracker } from '../../../tests/fake-block-tracker'; @@ -192,6 +193,55 @@ export function buildMockGetNetworkClientById( return getNetworkClientById; } +/** + * Builds a mock version of the `getNetworkClientIdByChainId` method on + * NetworkController. + * + * @param mockNetworkClientConfigurationsByNetworkClientId - Allows for defining + * the network client configuration — and thus the network client itself — that + * belongs to a particular network client ID. + * @returns The mock version of `getNetworkClientIdByChainId`. + */ +export function buildMockGetNetworkClientByChainId( + mockNetworkClientConfigurationsByNetworkClientId: Record< + Hex, + NetworkClientConfiguration + > = {}, +): NetworkController['getNetworkClientIdByChainId'] { + const defaultMockNetworkClientConfigurationsByNetworkClientId = Object.values( + InfuraNetworkType, + ).reduce((obj, infuraNetworkType) => { + return { + ...obj, + [infuraNetworkType]: + buildInfuraNetworkClientConfiguration(infuraNetworkType), + }; + }, {}); + const mergedMockNetworkClientConfigurationsByNetworkClientId: Record< + NetworkClientId, + NetworkClientConfiguration + > = { + ...defaultMockNetworkClientConfigurationsByNetworkClientId, + ...mockNetworkClientConfigurationsByNetworkClientId, + }; + + function getNetworkClientIdByChainId(chainId: Hex): NetworkClientId; + // eslint-disable-next-line jsdoc/require-jsdoc + function getNetworkClientIdByChainId(chainId: Hex): NetworkClientId { + const networkClientConfigForChainId = Object.entries( + mergedMockNetworkClientConfigurationsByNetworkClientId, + ).find(([_, value]) => value.chainId === chainId); + if (!networkClientConfigForChainId) { + throw new Error( + `Unknown chainId '${chainId}'. Please add it to mockNetworkClientConfigurationsByNetworkClientId.`, + ); + } + return networkClientConfigForChainId[0]; + } + + return getNetworkClientIdByChainId; +} + /** * Builds a configuration object for an Infura network client based on the name * of an Infura network.