diff --git a/packages/assets-controllers/CHANGELOG.md b/packages/assets-controllers/CHANGELOG.md index ccceaea663b..1d02384c41c 100644 --- a/packages/assets-controllers/CHANGELOG.md +++ b/packages/assets-controllers/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Fix duplicate token in state.allTokens after calling addTokens function with the same token twice ([#6829](https://github.com/MetaMask/core/pull/6829) + ## [80.0.0] ### Added diff --git a/packages/assets-controllers/src/TokensController.test.ts b/packages/assets-controllers/src/TokensController.test.ts index 5bfd1fedd6a..4cc0a1f3f09 100644 --- a/packages/assets-controllers/src/TokensController.test.ts +++ b/packages/assets-controllers/src/TokensController.test.ts @@ -1878,6 +1878,71 @@ describe('TokensController', () => { }, ); }); + + it('should not add duplicate tokens to state', async () => { + await withController( + { + mockNetworkClientConfigurationsByNetworkClientId: { + networkClientId1: buildCustomNetworkClientConfiguration({ + chainId: '0x5', + }), + }, + }, + async ({ controller, changeNetwork }) => { + changeNetwork({ selectedNetworkClientId: InfuraNetworkType.goerli }); + + const tokensToImport = [ + { + address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // lowercase USDC + symbol: 'USDC', + decimals: 6, + name: 'USDC', + image: 'test', + aggregators: [ + 'Metamask', + '1inch', + 'LiFi', + 'XSwap', + 'Socket', + 'Rubic', + 'Squid', + 'Rango', + 'Sonarwatch', + 'SushiSwap', + 'PMM', + 'Bancor', + ], + }, + ]; + // Add tokens for first time in state + await controller.addTokens(tokensToImport, 'mainnet'); + expect( + controller.state.allTokens[ChainId.mainnet][ + defaultMockInternalAccount.address + ], + ).toStrictEqual([ + { + ...tokensToImport[0], + address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // checksummed USDC + }, + ]); + + // Add same tokens again and check the state does not have duplicate entries + await controller.addTokens(tokensToImport, 'mainnet'); + + expect( + controller.state.allTokens[ChainId.mainnet][ + defaultMockInternalAccount.address + ], + ).toStrictEqual([ + { + ...tokensToImport[0], + address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // checksummed USDC + }, + ]); + }, + ); + }); }); describe('watchAsset', () => { diff --git a/packages/assets-controllers/src/TokensController.ts b/packages/assets-controllers/src/TokensController.ts index 571e28188de..4c79b375952 100644 --- a/packages/assets-controllers/src/TokensController.ts +++ b/packages/assets-controllers/src/TokensController.ts @@ -510,11 +510,18 @@ export class TokensController extends BaseController< networkClientId, ).configuration.chainId; + const tokensToImportChecksummed = tokensToImport.map((token) => { + return { + ...token, + address: toChecksumHexAddress(token.address), + }; + }); + // Used later to dedupe imported tokens const newTokensMap = [ ...(allTokens[interactingChainId]?.[this.#getSelectedAccount().address] || []), - ...tokensToImport, + ...tokensToImportChecksummed, ].reduce( (output, token) => { output[token.address] = token; @@ -523,7 +530,7 @@ export class TokensController extends BaseController< {} as { [address: string]: Token }, ); try { - tokensToImport.forEach((tokenToAdd) => { + tokensToImportChecksummed.forEach((tokenToAdd) => { const { address, symbol, decimals, image, aggregators, name } = tokenToAdd; const checksumAddress = toChecksumHexAddress(address);