diff --git a/src/assets/AssetsController.test.ts b/src/assets/AssetsController.test.ts index c6150562c0c..a361e0e478b 100644 --- a/src/assets/AssetsController.test.ts +++ b/src/assets/AssetsController.test.ts @@ -110,6 +110,37 @@ describe('AssetsController', () => { }); }); + it('should add tokens', async () => { + await assetsController.addTokens([ + { address: 'addressA', symbol: 'barA', decimals: 2 }, + { address: 'addressB', symbol: 'barB', decimals: 2 }, + ]); + expect(assetsController.state.tokens[0]).toEqual({ + address: '0xAdDRessA', + decimals: 2, + symbol: 'barA', + }); + expect(assetsController.state.tokens[1]).toEqual({ + address: '0xAddReSSB', + decimals: 2, + symbol: 'barB', + }); + await assetsController.addTokens([ + { address: 'addressA', symbol: 'bazA', decimals: 2 }, + { address: 'addressB', symbol: 'bazB', decimals: 2 }, + ]); + expect(assetsController.state.tokens[0]).toEqual({ + address: '0xAdDRessA', + decimals: 2, + symbol: 'bazA', + }); + expect(assetsController.state.tokens[1]).toEqual({ + address: '0xAddReSSB', + decimals: 2, + symbol: 'bazB', + }); + }); + it('should add token by selected address', async () => { const firstAddress = '0x123'; const secondAddress = '0x321'; diff --git a/src/assets/AssetsController.ts b/src/assets/AssetsController.ts index 61a504b1981..0c4c98e62bd 100644 --- a/src/assets/AssetsController.ts +++ b/src/assets/AssetsController.ts @@ -586,6 +586,43 @@ export class AssetsController extends BaseController } } + /** + * Adds a batch of tokens to the stored token list + * + * @param tokens - Array of Tokens to be added or updated + * @returns - Current token list + */ + async addTokens(tokensToAdd: Token[]): Promise { + const releaseLock = await this.mutex.acquire(); + const { allTokens, tokens } = this.state; + const { networkType, selectedAddress } = this.config; + + try { + tokensToAdd.forEach((tokenToAdd) => { + const { address, symbol, decimals, image } = tokenToAdd; + const checksumAddress = toChecksumAddress(address); + + const newEntry: Token = { address: checksumAddress, symbol, decimals, image }; + const previousEntry = tokens.find((token) => token.address === checksumAddress); + if (previousEntry) { + const previousIndex = tokens.indexOf(previousEntry); + tokens[previousIndex] = newEntry; + } else { + tokens.push(newEntry); + } + }); + + const addressTokens = allTokens[selectedAddress]; + const newAddressTokens = { ...addressTokens, ...{ [networkType]: tokens } }; + const newAllTokens = { ...allTokens, ...{ [selectedAddress]: newAddressTokens } }; + const newTokens = [...tokens]; + this.update({ allTokens: newAllTokens, tokens: newTokens }); + return newTokens; + } finally { + releaseLock(); + } + } + /** * 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. diff --git a/src/assets/AssetsDetectionController.ts b/src/assets/AssetsDetectionController.ts index 200ebb1a29f..9375b0fcfa3 100644 --- a/src/assets/AssetsDetectionController.ts +++ b/src/assets/AssetsDetectionController.ts @@ -173,6 +173,7 @@ export class AssetsDetectionController extends BaseController token.address === toChecksumAddress(tokenAddress)); } if (!ignored) { - await assetsController.addToken( - tokenAddress, - contractMap[tokenAddress].symbol, - contractMap[tokenAddress].decimals, - ); + tokensToAdd.push({ + address: tokenAddress, + decimals: contractMap[tokenAddress].decimals, + symbol: contractMap[tokenAddress].symbol, + }); } } + if (tokensToAdd.length) { + await assetsController.addTokens(tokensToAdd); + } }); } diff --git a/src/transaction/TransactionController.test.ts b/src/transaction/TransactionController.test.ts index 96d481f831f..f9b9fea503b 100644 --- a/src/transaction/TransactionController.test.ts +++ b/src/transaction/TransactionController.test.ts @@ -560,6 +560,7 @@ describe('TransactionController', () => { }); expect(controller.state.transactions[0].transaction.from).toBe(from); expect(controller.state.transactions[0].networkID).toBe(MOCK_NETWORK.state.network); + expect(controller.state.transactions[0].chainId).toBe(MOCK_NETWORK.state.provider.chainId); expect(controller.state.transactions[0].status).toBe('unapproved'); }); @@ -604,6 +605,25 @@ describe('TransactionController', () => { expect(controller.state.transactions).toHaveLength(0); }); + // This tests the fallback to networkID only when there is no chainId present. Should be removed when networkID is completely removed. + it('should wipe transactions using networkID when there is no chainId', async () => { + const controller = new TransactionController({ provider: PROVIDER }); + controller.wipeTransactions(); + controller.context = { + NetworkController: MOCK_NETWORK, + } as any; + controller.onComposed(); + controller.state.transactions.push({ + from: MOCK_PRFERENCES.state.selectedAddress, + id: 'foo', + networkID: '3', + status: 'submitted', + transactionHash: '1337', + } as any); + controller.wipeTransactions(); + expect(controller.state.transactions).toHaveLength(0); + }); + it('should fail to approve an invalid transaction', () => { return new Promise(async (resolve) => { const controller = new TransactionController({ @@ -730,6 +750,35 @@ describe('TransactionController', () => { }); it('should query transaction statuses', () => { + return new Promise((resolve) => { + const controller = new TransactionController({ + provider: PROVIDER, + sign: async (transaction: any) => transaction, + }); + controller.context = { + NetworkController: MOCK_NETWORK, + } as any; + controller.onComposed(); + controller.state.transactions.push({ + from: MOCK_PRFERENCES.state.selectedAddress, + id: 'foo', + networkID: '3', + chainId: '3', + status: 'submitted', + transactionHash: '1337', + } as any); + controller.state.transactions.push({} as any); + + controller.hub.once(`${controller.state.transactions[0].id}:confirmed`, () => { + expect(controller.state.transactions[0].status).toBe('confirmed'); + resolve(''); + }); + controller.queryTransactionStatuses(); + }); + }); + + // This tests the fallback to networkID only when there is no chainId present. Should be removed when networkID is completely removed. + it('should query transaction statuses with networkID only when there is no chainId', () => { return new Promise((resolve) => { const controller = new TransactionController({ provider: PROVIDER, diff --git a/src/transaction/TransactionController.ts b/src/transaction/TransactionController.ts index de4075ee8d5..7955c3de3f5 100644 --- a/src/transaction/TransactionController.ts +++ b/src/transaction/TransactionController.ts @@ -100,6 +100,7 @@ export interface TransactionMeta { }; id: string; networkID?: string; + chainId?: string; origin?: string; rawTransaction?: string; status: string; @@ -237,9 +238,10 @@ export class TransactionController extends BaseController { + private normalizeTokenTx = (txMeta: EtherscanTransactionMeta, currentNetworkID: string, currentChainId: string): TransactionMeta => { const time = parseInt(txMeta.timeStamp, 10) * 1000; const { to, from, gas, gasPrice, hash, contractAddress, tokenDecimal, tokenSymbol, value, @@ -271,6 +274,7 @@ export class TransactionController extends BaseController Promise.all( transactions.map(async (meta, index) => { - if (meta.status === 'submitted' && meta.networkID === currentNetworkID) { + // Using fallback to networkID only when there is no chainId present. Should be removed when networkID is completely removed. + if (meta.status === 'submitted' && (meta.chainId === currentChainId || (!meta.chainId && meta.networkID === currentNetworkID))) { const txObj = await query(this.ethQuery, 'getTransactionByHash', [meta.transactionHash]); /* istanbul ignore else */ if (txObj && txObj.blockNumber) { @@ -701,8 +712,14 @@ export class TransactionController extends BaseController networkID !== currentNetworkID); + const currentChainId = network.state.provider.chainId; + const currentNetworkID = network.state.network; + const newTransactions = this.state.transactions.filter(({ networkID, chainId }) => { + // Using fallback to networkID only when there is no chainId present. Should be removed when networkID is completely removed. + const isCurrentNetwork = (chainId === currentChainId || (!chainId && networkID === currentNetworkID)); + return !isCurrentNetwork; + }); + this.update({ transactions: newTransactions }); } @@ -719,7 +736,7 @@ export class TransactionController extends BaseController { /* istanbul ignore next */ if (!remoteTxList[tx.hash]) { - const cleanTx = this.normalizeTx(tx, currentNetworkID); + const cleanTx = this.normalizeTx(tx, currentNetworkID, currentChainId); remoteTxs.push(cleanTx); remoteTxList[tx.hash] = 1; } }); etherscanTokenResponse.result.forEach((tx: EtherscanTransactionMeta) => { - const cleanTx = this.normalizeTokenTx(tx, currentNetworkID); + const cleanTx = this.normalizeTokenTx(tx, currentNetworkID, currentChainId); remoteTxs.push(cleanTx); /* istanbul ignore next */ remoteTxList[cleanTx.transactionHash || ''] = 1; @@ -761,7 +778,8 @@ export class TransactionController extends BaseController { /* istanbul ignore next */ if ( - tx.networkID === currentNetworkID && + // Using fallback to networkID only when there is no chainId present. Should be removed when networkID is completely removed. + (tx.chainId === currentChainId || (!tx.chainId && tx.networkID === currentNetworkID)) && tx.transaction.to && tx.transaction.to.toLowerCase() === address.toLowerCase() ) {