diff --git a/app/scripts/controllers/detect-tokens.js b/app/scripts/controllers/detect-tokens.js
index c9ea77f2d..1777d28ca 100644
--- a/app/scripts/controllers/detect-tokens.js
+++ b/app/scripts/controllers/detect-tokens.js
@@ -1,10 +1,9 @@
import Web3 from 'web3';
-import contracts from '@metamask/contract-metadata';
import { warn } from 'loglevel';
import SINGLE_CALL_BALANCES_ABI from 'single-call-balance-checker-abi';
-import { MAINNET_CHAIN_ID } from '../../../shared/constants/network';
import { SINGLE_CALL_BALANCES_ADDRESS } from '../constants/contracts';
import { MINUTE } from '../../../shared/constants/time';
+import { isEqualCaseInsensitive } from '../../../ui/helpers/utils/util';
// By default, poll every 3 minutes
const DEFAULT_INTERVAL = MINUTE * 3;
@@ -24,57 +23,13 @@ export default class DetectTokensController {
preferences,
network,
keyringMemStore,
+ tokenList,
} = {}) {
this.preferences = preferences;
this.interval = interval;
this.network = network;
this.keyringMemStore = keyringMemStore;
- }
-
- /**
- * For each token in @metamask/contract-metadata, find check selectedAddress balance.
- */
- async detectNewTokens() {
- if (!this.isActive) {
- return;
- }
- if (this._network.store.getState().provider.chainId !== MAINNET_CHAIN_ID) {
- return;
- }
-
- const tokensToDetect = [];
- this.web3.setProvider(this._network._provider);
- for (const contractAddress in contracts) {
- if (
- contracts[contractAddress].erc20 &&
- !this.tokenAddresses.includes(contractAddress.toLowerCase()) &&
- !this.hiddenTokens.includes(contractAddress.toLowerCase())
- ) {
- tokensToDetect.push(contractAddress);
- }
- }
-
- let result;
- try {
- result = await this._getTokenBalances(tokensToDetect);
- } catch (error) {
- warn(
- `MetaMask - DetectTokensController single call balance fetch failed`,
- error,
- );
- return;
- }
-
- tokensToDetect.forEach((tokenAddress, index) => {
- const balance = result[index];
- if (balance && !balance.isZero()) {
- this._preferences.addToken(
- tokenAddress,
- contracts[tokenAddress].symbol,
- contracts[tokenAddress].decimals,
- );
- }
- });
+ this.tokenList = tokenList;
}
async _getTokenBalances(tokens) {
@@ -91,6 +46,63 @@ export default class DetectTokensController {
});
}
+ /**
+ * For each token in the tokenlist provided by the TokenListController, check selectedAddress balance.
+ */
+ async detectNewTokens() {
+ if (!this.isActive) {
+ return;
+ }
+
+ const { tokenList } = this._tokenList.state;
+ if (Object.keys(tokenList).length === 0) {
+ return;
+ }
+
+ const tokensToDetect = [];
+ this.web3.setProvider(this._network._provider);
+ for (const tokenAddress in tokenList) {
+ if (
+ !this.tokenAddresses.find((address) =>
+ isEqualCaseInsensitive(address, tokenAddress),
+ ) &&
+ !this.hiddenTokens.find((address) =>
+ isEqualCaseInsensitive(address, tokenAddress),
+ )
+ ) {
+ tokensToDetect.push(tokenAddress);
+ }
+ }
+ const sliceOfTokensToDetect = [
+ tokensToDetect.slice(0, 1000),
+ tokensToDetect.slice(1000, tokensToDetect.length - 1),
+ ];
+ for (const tokensSlice of sliceOfTokensToDetect) {
+ let result;
+ try {
+ result = await this._getTokenBalances(tokensSlice);
+ } catch (error) {
+ warn(
+ `MetaMask - DetectTokensController single call balance fetch failed`,
+ error,
+ );
+ return;
+ }
+ await Promise.all(
+ tokensSlice.map(async (tokenAddress, index) => {
+ const balance = result[index];
+ if (balance && !balance.isZero()) {
+ await this._preferences.addToken(
+ tokenAddress,
+ tokenList[tokenAddress].symbol,
+ tokenList[tokenAddress].decimals,
+ );
+ }
+ }),
+ );
+ }
+ }
+
/**
* Restart token detection polling period and call detectNewTokens
* in case of address change or user session initialization.
@@ -138,9 +150,13 @@ export default class DetectTokensController {
});
this.hiddenTokens = hiddenTokens;
});
- preferences.store.subscribe(({ selectedAddress }) => {
- if (this.selectedAddress !== selectedAddress) {
+ preferences.store.subscribe(({ selectedAddress, useTokenDetection }) => {
+ if (
+ this.selectedAddress !== selectedAddress ||
+ this.useTokenDetection !== useTokenDetection
+ ) {
this.selectedAddress = selectedAddress;
+ this.useTokenDetection = useTokenDetection;
this.restartTokenDetection();
}
});
@@ -176,6 +192,16 @@ export default class DetectTokensController {
});
}
+ /**
+ * @type {Object}
+ */
+ set tokenList(tokenList) {
+ if (!tokenList) {
+ return;
+ }
+ this._tokenList = tokenList;
+ }
+
/**
* Internal isActive state
* @type {Object}
diff --git a/app/scripts/controllers/detect-tokens.test.js b/app/scripts/controllers/detect-tokens.test.js
index 7bd788271..65f9af298 100644
--- a/app/scripts/controllers/detect-tokens.test.js
+++ b/app/scripts/controllers/detect-tokens.test.js
@@ -1,15 +1,19 @@
import { strict as assert } from 'assert';
import sinon from 'sinon';
+import nock from 'nock';
import { ObservableStore } from '@metamask/obs-store';
-import contracts from '@metamask/contract-metadata';
import BigNumber from 'bignumber.js';
-
+import {
+ ControllerMessenger,
+ TokenListController,
+} from '@metamask/controllers';
import { MAINNET, ROPSTEN } from '../../../shared/constants/network';
import DetectTokensController from './detect-tokens';
import NetworkController from './network';
import PreferencesController from './preferences';
describe('DetectTokensController', function () {
+ let tokenListController;
const sandbox = sinon.createSandbox();
let keyringMemStore, network, preferences, provider;
@@ -36,6 +40,87 @@ describe('DetectTokensController', function () {
sandbox
.stub(preferences, '_detectIsERC721')
.returns(Promise.resolve(false));
+ nock('https://token-api.metaswap.codefi.network')
+ .get(`/tokens/1`)
+ .reply(200, [
+ {
+ address: '0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f',
+ symbol: 'SNX',
+ decimals: 18,
+ occurrences: 11,
+ aggregators: [
+ 'paraswap',
+ 'pmm',
+ 'airswapLight',
+ 'zeroEx',
+ 'bancor',
+ 'coinGecko',
+ 'zapper',
+ 'kleros',
+ 'zerion',
+ 'cmc',
+ 'oneInch',
+ ],
+ name: 'Synthetix',
+ iconUrl: 'https://airswap-token-images.s3.amazonaws.com/SNX.png',
+ },
+ {
+ address: '0x514910771af9ca656af840dff83e8264ecf986ca',
+ symbol: 'LINK',
+ decimals: 18,
+ occurrences: 11,
+ aggregators: [
+ 'paraswap',
+ 'pmm',
+ 'airswapLight',
+ 'zeroEx',
+ 'bancor',
+ 'coinGecko',
+ 'zapper',
+ 'kleros',
+ 'zerion',
+ 'cmc',
+ 'oneInch',
+ ],
+ name: 'Chainlink',
+ iconUrl: 'https://s3.amazonaws.com/airswap-token-images/LINK.png',
+ },
+ {
+ address: '0x1f573d6fb3f13d689ff844b4ce37794d79a7ff1c',
+ symbol: 'BNT',
+ decimals: 18,
+ occurrences: 11,
+ aggregators: [
+ 'paraswap',
+ 'pmm',
+ 'airswapLight',
+ 'zeroEx',
+ 'bancor',
+ 'coinGecko',
+ 'zapper',
+ 'kleros',
+ 'zerion',
+ 'cmc',
+ 'oneInch',
+ ],
+ name: 'Bancor',
+ iconUrl: 'https://s3.amazonaws.com/airswap-token-images/BNT.png',
+ },
+ ])
+ .get(`/tokens/3`)
+ .reply(200, { error: 'ChainId 3 is not supported' })
+ .persist();
+ const tokenListMessenger = new ControllerMessenger().getRestricted({
+ name: 'TokenListController',
+ });
+ tokenListController = new TokenListController({
+ chainId: '1',
+ useStaticTokenList: false,
+ onNetworkStateChange: sinon.spy(),
+ onPreferencesStateChange: sinon.spy(),
+ messenger: tokenListMessenger,
+ });
+ await tokenListController.start();
});
after(function () {
@@ -56,6 +141,7 @@ describe('DetectTokensController', function () {
preferences,
network,
keyringMemStore,
+ tokenList: tokenListController,
});
controller.isOpen = true;
controller.isUnlocked = true;
@@ -75,10 +161,22 @@ describe('DetectTokensController', function () {
it('should not check tokens while on test network', async function () {
sandbox.useFakeTimers();
network.setProviderType(ROPSTEN);
+ const tokenListMessengerRopsten = new ControllerMessenger().getRestricted({
+ name: 'TokenListController',
+ });
+ tokenListController = new TokenListController({
+ chainId: '3',
+ useStaticTokenList: false,
+ onNetworkStateChange: sinon.spy(),
+ onPreferencesStateChange: sinon.spy(),
+ messenger: tokenListMessengerRopsten,
+ });
+ await tokenListController.start();
const controller = new DetectTokensController({
preferences,
network,
keyringMemStore,
+ tokenList: tokenListController,
});
controller.isOpen = true;
controller.isUnlocked = true;
@@ -96,17 +194,16 @@ describe('DetectTokensController', function () {
preferences,
network,
keyringMemStore,
+ tokenList: tokenListController,
});
controller.isOpen = true;
controller.isUnlocked = true;
- const contractAddresses = Object.keys(contracts);
- const erc20ContractAddresses = contractAddresses.filter(
- (contractAddress) => contracts[contractAddress].erc20 === true,
- );
+ const { tokenList } = tokenListController.state;
+ const erc20ContractAddresses = Object.keys(tokenList);
const existingTokenAddress = erc20ContractAddresses[0];
- const existingToken = contracts[existingTokenAddress];
+ const existingToken = tokenList[existingTokenAddress];
await preferences.addToken(
existingTokenAddress,
existingToken.symbol,
@@ -144,17 +241,16 @@ describe('DetectTokensController', function () {
preferences,
network,
keyringMemStore,
+ tokenList: tokenListController,
});
controller.isOpen = true;
controller.isUnlocked = true;
- const contractAddresses = Object.keys(contracts);
- const erc20ContractAddresses = contractAddresses.filter(
- (contractAddress) => contracts[contractAddress].erc20 === true,
- );
+ const { tokenList } = tokenListController.state;
+ const erc20ContractAddresses = Object.keys(tokenList);
const existingTokenAddress = erc20ContractAddresses[0];
- const existingToken = contracts[existingTokenAddress];
+ const existingToken = tokenList[existingTokenAddress];
await preferences.addToken(
existingTokenAddress,
existingToken.symbol,
@@ -162,16 +258,16 @@ describe('DetectTokensController', function () {
);
const tokenAddressToAdd = erc20ContractAddresses[1];
- const tokenToAdd = contracts[tokenAddressToAdd];
+ const tokenToAdd = tokenList[tokenAddressToAdd];
- const contractAddresssesToDetect = contractAddresses.filter(
+ const contractAddressesToDetect = erc20ContractAddresses.filter(
(address) => address !== existingTokenAddress,
);
- const indexOfTokenToAdd = contractAddresssesToDetect.indexOf(
+ const indexOfTokenToAdd = contractAddressesToDetect.indexOf(
tokenAddressToAdd,
);
- const balances = new Array(contractAddresssesToDetect.length);
+ const balances = new Array(contractAddressesToDetect.length);
balances[indexOfTokenToAdd] = new BigNumber(10);
sandbox
@@ -203,17 +299,16 @@ describe('DetectTokensController', function () {
preferences,
network,
keyringMemStore,
+ tokenList: tokenListController,
});
controller.isOpen = true;
controller.isUnlocked = true;
- const contractAddresses = Object.keys(contracts);
- const erc20ContractAddresses = contractAddresses.filter(
- (contractAddress) => contracts[contractAddress].erc20 === true,
- );
+ const { tokenList } = tokenListController.state;
+ const erc20ContractAddresses = Object.keys(tokenList);
const existingTokenAddress = erc20ContractAddresses[0];
- const existingToken = contracts[existingTokenAddress];
+ const existingToken = tokenList[existingTokenAddress];
await preferences.addToken(
existingTokenAddress,
existingToken.symbol,
@@ -221,16 +316,16 @@ describe('DetectTokensController', function () {
);
const tokenAddressToAdd = erc20ContractAddresses[1];
- const tokenToAdd = contracts[tokenAddressToAdd];
+ const tokenToAdd = tokenList[tokenAddressToAdd];
- const contractAddresssesToDetect = contractAddresses.filter(
+ const contractAddressesToDetect = erc20ContractAddresses.filter(
(address) => address !== existingTokenAddress,
);
- const indexOfTokenToAdd = contractAddresssesToDetect.indexOf(
+ const indexOfTokenToAdd = contractAddressesToDetect.indexOf(
tokenAddressToAdd,
);
- const balances = new Array(contractAddresssesToDetect.length);
+ const balances = new Array(contractAddressesToDetect.length);
balances[indexOfTokenToAdd] = new BigNumber(10);
sandbox
@@ -261,6 +356,7 @@ describe('DetectTokensController', function () {
preferences,
network,
keyringMemStore,
+ tokenList: tokenListController,
});
controller.isOpen = true;
controller.isUnlocked = true;
@@ -277,6 +373,7 @@ describe('DetectTokensController', function () {
preferences,
network,
keyringMemStore,
+ tokenList: tokenListController,
});
controller.isOpen = true;
controller.selectedAddress = '0x0';
@@ -292,6 +389,7 @@ describe('DetectTokensController', function () {
preferences,
network,
keyringMemStore,
+ tokenList: tokenListController,
});
controller.isOpen = true;
controller.isUnlocked = false;
diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js
index 11b38a478..b7b981ec3 100644
--- a/app/scripts/controllers/preferences.js
+++ b/app/scripts/controllers/preferences.js
@@ -51,7 +51,10 @@ export default class PreferencesController {
useNonceField: false,
usePhishDetect: true,
dismissSeedBackUpReminder: false,
- useStaticTokenList: false,
+
+ // set to true means the dynamic list from the API is being used
+ // set to false will be using the static list from contract-metadata
+ useTokenDetection: true,
// WARNING: Do not use feature flags for security-sensitive things.
// Feature flag toggling is available in the global namespace
@@ -140,13 +143,13 @@ export default class PreferencesController {
}
/**
- * Setter for the `useStaticTokenList` property
+ * Setter for the `useTokenDetection` property
*
* @param {boolean} val - Whether or not the user prefers to use the static token list or dynamic token list from the API
*
*/
- setUseStaticTokenList(val) {
- this.store.updateState({ useStaticTokenList: val });
+ setUseTokenDetection(val) {
+ this.store.updateState({ useTokenDetection: val });
}
/**
diff --git a/app/scripts/controllers/preferences.test.js b/app/scripts/controllers/preferences.test.js
index f088199ed..0abf8c202 100644
--- a/app/scripts/controllers/preferences.test.js
+++ b/app/scripts/controllers/preferences.test.js
@@ -869,22 +869,22 @@ describe('preferences controller', function () {
);
});
});
- describe('setUseStaticTokenList', function () {
- it('should default to false', function () {
+ describe('setUseTokenDetection', function () {
+ it('should default to true', function () {
const state = preferencesController.store.getState();
- assert.equal(state.useStaticTokenList, false);
+ assert.equal(state.useTokenDetection, true);
});
- it('should set the useStaticTokenList property in state', function () {
+ it('should set the useTokenDetection property in state', function () {
assert.equal(
- preferencesController.store.getState().useStaticTokenList,
- false,
- );
- preferencesController.setUseStaticTokenList(true);
- assert.equal(
- preferencesController.store.getState().useStaticTokenList,
+ preferencesController.store.getState().useTokenDetection,
true,
);
+ preferencesController.setUseTokenDetection(false);
+ assert.equal(
+ preferencesController.store.getState().useTokenDetection,
+ false,
+ );
});
});
});
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index 5c4bb09cf..0ee57bbf8 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -16,7 +16,6 @@ import TrezorKeyring from 'eth-trezor-keyring';
import LedgerBridgeKeyring from '@metamask/eth-ledger-bridge-keyring';
import EthQuery from 'eth-query';
import nanoid from 'nanoid';
-import contractMap from '@metamask/contract-metadata';
import {
AddressBookController,
ApprovalController,
@@ -238,8 +237,8 @@ export default class MetamaskController extends EventEmitter {
});
this.tokenListController = new TokenListController({
chainId: hexToDecimal(this.networkController.getCurrentChainId()),
- useStaticTokenList: this.preferencesController.store.getState()
- .useStaticTokenList,
+ useStaticTokenList: !this.preferencesController.store.getState()
+ .useTokenDetection,
onNetworkStateChange: (cb) =>
this.networkController.store.subscribe((networkState) => {
const modifiedNetworkState = {
@@ -251,11 +250,17 @@ export default class MetamaskController extends EventEmitter {
};
return cb(modifiedNetworkState);
}),
- onPreferencesStateChange: this.preferencesController.store.subscribe.bind(
- this.preferencesController.store,
- ),
+ onPreferencesStateChange: (cb) =>
+ this.preferencesController.store.subscribe((preferencesState) => {
+ const modifiedPreferencesState = {
+ ...preferencesState,
+ useStaticTokenList: !this.preferencesController.store.getState()
+ .useTokenDetection,
+ };
+ return cb(modifiedPreferencesState);
+ }),
messenger: tokenListMessenger,
- state: initState.tokenListController,
+ state: initState.TokenListController,
});
this.phishingController = new PhishingController();
@@ -372,6 +377,7 @@ export default class MetamaskController extends EventEmitter {
preferences: this.preferencesController,
network: this.networkController,
keyringMemStore: this.keyringController.memStore,
+ tokenList: this.tokenListController,
});
this.addressBookController = new AddressBookController(
@@ -775,8 +781,8 @@ export default class MetamaskController extends EventEmitter {
setUseBlockie: this.setUseBlockie.bind(this),
setUseNonceField: this.setUseNonceField.bind(this),
setUsePhishDetect: this.setUsePhishDetect.bind(this),
- setUseStaticTokenList: nodeify(
- this.preferencesController.setUseStaticTokenList,
+ setUseTokenDetection: nodeify(
+ this.preferencesController.setUseTokenDetection,
this.preferencesController,
),
setIpfsGateway: this.setIpfsGateway.bind(this),
@@ -1297,28 +1303,8 @@ export default class MetamaskController extends EventEmitter {
tokens,
} = this.preferencesController.store.getState();
- // Filter ERC20 tokens
- const filteredAccountTokens = {};
- Object.keys(accountTokens).forEach((address) => {
- const checksummedAddress = toChecksumHexAddress(address);
- filteredAccountTokens[checksummedAddress] = {};
- Object.keys(accountTokens[address]).forEach((chainId) => {
- filteredAccountTokens[checksummedAddress][chainId] =
- chainId === MAINNET_CHAIN_ID
- ? accountTokens[address][chainId].filter(
- ({ address: tokenAddress }) => {
- const checksumAddress = toChecksumHexAddress(tokenAddress);
- return contractMap[checksumAddress]
- ? contractMap[checksumAddress].erc20
- : true;
- },
- )
- : accountTokens[address][chainId];
- });
- });
-
const preferences = {
- accountTokens: filteredAccountTokens,
+ accountTokens,
currentLocale,
frequentRpcList,
identities,
diff --git a/test/data/fetch-mocks.json b/test/data/fetch-mocks.json
index 9299b4602..f9b2885f2 100644
--- a/test/data/fetch-mocks.json
+++ b/test/data/fetch-mocks.json
@@ -25,5 +25,49 @@
"fallback_to_v1": false
}
}
+ },
+ "tokenList": {
+ "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0": {
+ "address": "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0",
+ "symbol": "MATIC",
+ "decimals": 18,
+ "name": "Polygon",
+ "iconUrl": "https://raw.githubusercontent.com/MetaMask/eth-contract-metadata/master/images/matic-network-logo.svg",
+ "aggregators": [
+ "airswapLight",
+ "bancor",
+ "coinGecko",
+ "kleros",
+ "oneInch",
+ "paraswap",
+ "pmm",
+ "totle",
+ "zapper",
+ "zerion",
+ "zeroEx"
+ ],
+ "occurrences": 11
+ },
+ "0x0d8775f648430679a709e98d2b0cb6250d2887ef": {
+ "address": "0x0d8775f648430679a709e98d2b0cb6250d2887ef",
+ "symbol": "BAT",
+ "decimals": 18,
+ "name": "Basic Attention Tok",
+ "iconUrl": "https://s3.amazonaws.com/airswap-token-images/BAT.png",
+ "aggregators": [
+ "airswapLight",
+ "bancor",
+ "coinGecko",
+ "kleros",
+ "oneInch",
+ "paraswap",
+ "pmm",
+ "totle",
+ "zapper",
+ "zerion",
+ "zeroEx"
+ ],
+ "occurrences": 11
+ }
}
}
diff --git a/test/data/mock-state.json b/test/data/mock-state.json
index caaa378d8..8b6171339 100644
--- a/test/data/mock-state.json
+++ b/test/data/mock-state.json
@@ -154,6 +154,53 @@
"editingTransactionId": null,
"toNickname": ""
},
+ "useTokenDetection": true,
+ "tokenList": {
+ "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": {
+ "address": "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599",
+ "symbol": "WBTC",
+ "decimals": 8,
+ "name": "Wrapped Bitcoin",
+ "iconUrl": "https://s3.amazonaws.com/airswap-token-images/WBTC.png",
+ "aggregators": [
+ "airswapLight",
+ "bancor",
+ "cmc",
+ "coinGecko",
+ "kleros",
+ "oneInch",
+ "paraswap",
+ "pmm",
+ "totle",
+ "zapper",
+ "zerion",
+ "zeroEx"
+ ],
+ "occurrences": 12
+ },
+ "0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e": {
+ "address": "0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e",
+ "symbol": "YFI",
+ "decimals": 18,
+ "name": "yearn.finance",
+ "iconUrl": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e/logo.png",
+ "aggregators": [
+ "airswapLight",
+ "bancor",
+ "cmc",
+ "coinGecko",
+ "kleros",
+ "oneInch",
+ "paraswap",
+ "pmm",
+ "totle",
+ "zapper",
+ "zerion",
+ "zeroEx"
+ ],
+ "occurrences": 12
+ }
+ },
"currentNetworkTxList": [
{
"id": 3387511061307736,
diff --git a/test/e2e/fixtures/imported-account/state.json b/test/e2e/fixtures/imported-account/state.json
index 407bbbd83..aa2fc309b 100644
--- a/test/e2e/fixtures/imported-account/state.json
+++ b/test/e2e/fixtures/imported-account/state.json
@@ -88,6 +88,361 @@
}
]
},
+ "TokenListController": {
+ "tokenList": {
+ "0xbbbbca6a901c926f240b89eacb641d8aec7aeafd": {
+ "address": "0xbbbbca6a901c926f240b89eacb641d8aec7aeafd",
+ "symbol": "LRC",
+ "decimals": 18,
+ "name": "Loopring",
+ "iconUrl": "https://airswap-token-images.s3.amazonaws.com/LRC.png",
+ "aggregators": [
+ "airswapLight",
+ "bancor",
+ "cmc",
+ "coinGecko",
+ "kleros",
+ "oneInch",
+ "paraswap",
+ "pmm",
+ "totle",
+ "zapper",
+ "zerion",
+ "zeroEx"
+ ],
+ "occurrences": 12
+ },
+ "0x04fa0d235c4abf4bcf4787af4cf447de572ef828": {
+ "address": "0x04fa0d235c4abf4bcf4787af4cf447de572ef828",
+ "symbol": "UMA",
+ "decimals": 18,
+ "name": "UMA",
+ "iconUrl": "https://assets.coingecko.com/coins/images/10951/thumb/UMA.png?1586307916",
+ "aggregators": [
+ "bancor",
+ "cmc",
+ "coinGecko",
+ "kleros",
+ "oneInch",
+ "paraswap",
+ "pmm",
+ "totle",
+ "zapper",
+ "zerion",
+ "zeroEx"
+ ],
+ "occurrences": 11
+ },
+ "0x6b3595068778dd592e39a122f4f5a5cf09c90fe2": {
+ "address": "0x6b3595068778dd592e39a122f4f5a5cf09c90fe2",
+ "symbol": "SUSHI",
+ "decimals": 18,
+ "name": "SushiSwap",
+ "iconUrl": "https://assets.coingecko.com/coins/images/12271/thumb/512x512_Logo_no_chop.png?1606986688",
+ "aggregators": [
+ "bancor",
+ "cmc",
+ "coinGecko",
+ "kleros",
+ "oneInch",
+ "paraswap",
+ "pmm",
+ "totle",
+ "zapper",
+ "zerion",
+ "zeroEx"
+ ],
+ "occurrences": 11
+ },
+ "0xd533a949740bb3306d119cc777fa900ba034cd52": {
+ "address": "0xd533a949740bb3306d119cc777fa900ba034cd52",
+ "symbol": "CRV",
+ "decimals": 18,
+ "name": "Curve DAO Token",
+ "iconUrl": "https://assets.coingecko.com/coins/images/12124/thumb/Curve.png?1597369484",
+ "aggregators": [
+ "bancor",
+ "cmc",
+ "coinGecko",
+ "kleros",
+ "oneInch",
+ "paraswap",
+ "pmm",
+ "totle",
+ "zapper",
+ "zerion",
+ "zeroEx"
+ ],
+ "occurrences": 11
+ },
+ "0xc00e94cb662c3520282e6f5717214004a7f26888": {
+ "address": "0xc00e94cb662c3520282e6f5717214004a7f26888",
+ "symbol": "COMP",
+ "decimals": 18,
+ "name": "Compound",
+ "iconUrl": "https://assets.coingecko.com/coins/images/10775/thumb/COMP.png?1592625425",
+ "aggregators": [
+ "bancor",
+ "cmc",
+ "coinGecko",
+ "kleros",
+ "oneInch",
+ "paraswap",
+ "pmm",
+ "totle",
+ "zapper",
+ "zerion",
+ "zeroEx"
+ ],
+ "occurrences": 11
+ },
+ "0xba100000625a3754423978a60c9317c58a424e3d": {
+ "address": "0xba100000625a3754423978a60c9317c58a424e3d",
+ "symbol": "BAL",
+ "decimals": 18,
+ "name": "Balancer",
+ "iconUrl": "https://assets.coingecko.com/coins/images/11683/thumb/Balancer.png?1592792958",
+ "aggregators": [
+ "bancor",
+ "cmc",
+ "coinGecko",
+ "kleros",
+ "oneInch",
+ "paraswap",
+ "pmm",
+ "totle",
+ "zapper",
+ "zerion",
+ "zeroEx"
+ ],
+ "occurrences": 11
+ },
+ "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0": {
+ "address": "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0",
+ "symbol": "MATIC",
+ "decimals": 18,
+ "name": "Polygon",
+ "iconUrl": "https://raw.githubusercontent.com/MetaMask/eth-contract-metadata/master/images/matic-network-logo.svg",
+ "aggregators": [
+ "airswapLight",
+ "bancor",
+ "coinGecko",
+ "kleros",
+ "oneInch",
+ "paraswap",
+ "pmm",
+ "totle",
+ "zapper",
+ "zerion",
+ "zeroEx"
+ ],
+ "occurrences": 11
+ },
+ "0x0d8775f648430679a709e98d2b0cb6250d2887ef": {
+ "address": "0x0d8775f648430679a709e98d2b0cb6250d2887ef",
+ "symbol": "BAT",
+ "decimals": 18,
+ "name": "Basic Attention Tok",
+ "iconUrl": "https://s3.amazonaws.com/airswap-token-images/BAT.png",
+ "aggregators": [
+ "airswapLight",
+ "bancor",
+ "coinGecko",
+ "kleros",
+ "oneInch",
+ "paraswap",
+ "pmm",
+ "totle",
+ "zapper",
+ "zerion",
+ "zeroEx"
+ ],
+ "occurrences": 11
+ }
+ },
+ "tokensChainsCache": {
+ "1": {
+ "timestamp": 1628769574961,
+ "data": [
+ {
+ "address": "0xbbbbca6a901c926f240b89eacb641d8aec7aeafd",
+ "symbol": "LRC",
+ "decimals": 18,
+ "name": "Loopring",
+ "iconUrl": "https://airswap-token-images.s3.amazonaws.com/LRC.png",
+ "aggregators": [
+ "airswapLight",
+ "bancor",
+ "cmc",
+ "coinGecko",
+ "kleros",
+ "oneInch",
+ "paraswap",
+ "pmm",
+ "totle",
+ "zapper",
+ "zerion",
+ "zeroEx"
+ ],
+ "occurrences": 12
+ },
+ {
+ "address": "0x04fa0d235c4abf4bcf4787af4cf447de572ef828",
+ "symbol": "UMA",
+ "decimals": 18,
+ "name": "UMA",
+ "iconUrl": "https://assets.coingecko.com/coins/images/10951/thumb/UMA.png?1586307916",
+ "aggregators": [
+ "bancor",
+ "cmc",
+ "coinGecko",
+ "kleros",
+ "oneInch",
+ "paraswap",
+ "pmm",
+ "totle",
+ "zapper",
+ "zerion",
+ "zeroEx"
+ ],
+ "occurrences": 11
+ },
+ {
+ "address": "0x6b3595068778dd592e39a122f4f5a5cf09c90fe2",
+ "symbol": "SUSHI",
+ "decimals": 18,
+ "name": "SushiSwap",
+ "iconUrl": "https://assets.coingecko.com/coins/images/12271/thumb/512x512_Logo_no_chop.png?1606986688",
+ "aggregators": [
+ "bancor",
+ "cmc",
+ "coinGecko",
+ "kleros",
+ "oneInch",
+ "paraswap",
+ "pmm",
+ "totle",
+ "zapper",
+ "zerion",
+ "zeroEx"
+ ],
+ "occurrences": 11
+ },
+ {
+ "address": "0xd533a949740bb3306d119cc777fa900ba034cd52",
+ "symbol": "CRV",
+ "decimals": 18,
+ "name": "Curve DAO Token",
+ "iconUrl": "https://assets.coingecko.com/coins/images/12124/thumb/Curve.png?1597369484",
+ "aggregators": [
+ "bancor",
+ "cmc",
+ "coinGecko",
+ "kleros",
+ "oneInch",
+ "paraswap",
+ "pmm",
+ "totle",
+ "zapper",
+ "zerion",
+ "zeroEx"
+ ],
+ "occurrences": 11
+ },
+ {
+ "address": "0xc00e94cb662c3520282e6f5717214004a7f26888",
+ "symbol": "COMP",
+ "decimals": 18,
+ "name": "Compound",
+ "iconUrl": "https://assets.coingecko.com/coins/images/10775/thumb/COMP.png?1592625425",
+ "aggregators": [
+ "bancor",
+ "cmc",
+ "coinGecko",
+ "kleros",
+ "oneInch",
+ "paraswap",
+ "pmm",
+ "totle",
+ "zapper",
+ "zerion",
+ "zeroEx"
+ ],
+ "occurrences": 11
+ },
+ {
+ "address": "0xba100000625a3754423978a60c9317c58a424e3d",
+ "symbol": "BAL",
+ "decimals": 18,
+ "name": "Balancer",
+ "iconUrl": "https://assets.coingecko.com/coins/images/11683/thumb/Balancer.png?1592792958",
+ "aggregators": [
+ "bancor",
+ "cmc",
+ "coinGecko",
+ "kleros",
+ "oneInch",
+ "paraswap",
+ "pmm",
+ "totle",
+ "zapper",
+ "zerion",
+ "zeroEx"
+ ],
+ "occurrences": 11
+ },
+ {
+ "address": "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0",
+ "symbol": "MATIC",
+ "decimals": 18,
+ "name": "Polygon",
+ "iconUrl": "https://raw.githubusercontent.com/MetaMask/eth-contract-metadata/master/images/matic-network-logo.svg",
+ "aggregators": [
+ "airswapLight",
+ "bancor",
+ "coinGecko",
+ "kleros",
+ "oneInch",
+ "paraswap",
+ "pmm",
+ "totle",
+ "zapper",
+ "zerion",
+ "zeroEx"
+ ],
+ "occurrences": 11
+ },
+ {
+ "address": "0x0d8775f648430679a709e98d2b0cb6250d2887ef",
+ "symbol": "BAT",
+ "decimals": 18,
+ "name": "Basic Attention Tok",
+ "iconUrl": "https://s3.amazonaws.com/airswap-token-images/BAT.png",
+ "aggregators": [
+ "airswapLight",
+ "bancor",
+ "coinGecko",
+ "kleros",
+ "oneInch",
+ "paraswap",
+ "pmm",
+ "totle",
+ "zapper",
+ "zerion",
+ "zeroEx"
+ ],
+ "occurrences": 11
+ }
+ ]
+ },
+ "3": {
+ "timestamp": 1628769543620
+ },
+ "1337": {
+ "timestamp": 1628769513476
+ }
+ }
+ },
"PreferencesController": {
"accountTokens": {
"0x5cfe73b6021e818b776b421b1c4db2474086a7e1": {
@@ -123,7 +478,8 @@
"tokens": [],
"useBlockie": false,
"useNonceField": false,
- "usePhishDetect": true
+ "usePhishDetect": true,
+ "useTokenDetection": true
},
"config": {},
"firstTimeInfo": {
diff --git a/test/e2e/tests/permissions.spec.js b/test/e2e/tests/permissions.spec.js
index 503066626..50fefb3a5 100644
--- a/test/e2e/tests/permissions.spec.js
+++ b/test/e2e/tests/permissions.spec.js
@@ -1,5 +1,5 @@
const { strict: assert } = require('assert');
-const { withFixtures, xxLargeDelayMs } = require('../helpers');
+const { withFixtures, xxLargeDelayMs, xLargeDelayMs } = require('../helpers');
describe('Permissions', function () {
it('sets permissions and connect to Dapp', async function () {
@@ -62,7 +62,7 @@ describe('Permissions', function () {
text: 'Connected sites',
tag: 'h2',
});
-
+ await driver.delay(xLargeDelayMs);
const domains = await driver.findClickableElements(
'.connected-sites-list__domain-name',
);
diff --git a/test/e2e/webdriver/index.js b/test/e2e/webdriver/index.js
index 075718631..eb552f34d 100644
--- a/test/e2e/webdriver/index.js
+++ b/test/e2e/webdriver/index.js
@@ -52,6 +52,12 @@ async function setupFetchMocking(driver) {
if (url.match(/featureFlags$/u)) {
return { json: async () => clone(mockResponses.swaps.featureFlags) };
}
+ } else if (
+ url.match(/^https:\/\/(token-api\.airswap-prod\.codefi\.network)/u)
+ ) {
+ if (url.match(/tokens\/1337$/u)) {
+ return { json: async () => clone(mockResponses.tokenList) };
+ }
}
return window.origFetch(...args);
};
diff --git a/test/jest/mock-store.js b/test/jest/mock-store.js
index f57ae13ae..19dbcf48b 100644
--- a/test/jest/mock-store.js
+++ b/test/jest/mock-store.js
@@ -222,6 +222,76 @@ export const createSwapsMockStore = () => {
swapsFeatureIsLive: false,
useNewSwapsApi: false,
},
+ useTokenDetection: true,
+ tokenList: {
+ '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984': {
+ address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984',
+ symbol: 'UNI',
+ decimals: 18,
+ name: 'Uniswap',
+ iconUrl:
+ 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984/logo.png',
+ aggregators: [
+ 'airswapLight',
+ 'bancor',
+ 'cmc',
+ 'coinGecko',
+ 'kleros',
+ 'oneInch',
+ 'paraswap',
+ 'pmm',
+ 'totle',
+ 'zapper',
+ 'zerion',
+ 'zeroEx',
+ ],
+ occurrences: 12,
+ },
+ '0x514910771af9ca656af840dff83e8264ecf986ca': {
+ address: '0x514910771af9ca656af840dff83e8264ecf986ca',
+ symbol: 'LINK',
+ decimals: 18,
+ name: 'Chainlink',
+ iconUrl: 'https://s3.amazonaws.com/airswap-token-images/LINK.png',
+ aggregators: [
+ 'airswapLight',
+ 'bancor',
+ 'cmc',
+ 'coinGecko',
+ 'kleros',
+ 'oneInch',
+ 'paraswap',
+ 'pmm',
+ 'totle',
+ 'zapper',
+ 'zerion',
+ 'zeroEx',
+ ],
+ occurrences: 12,
+ },
+ '0x6b3595068778dd592e39a122f4f5a5cf09c90fe2': {
+ address: '0x6b3595068778dd592e39a122f4f5a5cf09c90fe2',
+ symbol: 'SUSHI',
+ decimals: 18,
+ name: 'SushiSwap',
+ iconUrl:
+ 'https://assets.coingecko.com/coins/images/12271/thumb/512x512_Logo_no_chop.png?1606986688',
+ aggregators: [
+ 'bancor',
+ 'cmc',
+ 'coinGecko',
+ 'kleros',
+ 'oneInch',
+ 'paraswap',
+ 'pmm',
+ 'totle',
+ 'zapper',
+ 'zerion',
+ 'zeroEx',
+ ],
+ occurrences: 11,
+ },
+ },
},
appState: {
modal: {
diff --git a/ui/components/ui/identicon/identicon.component.js b/ui/components/ui/identicon/identicon.component.js
index dd6eb5beb..b78f8e372 100644
--- a/ui/components/ui/identicon/identicon.component.js
+++ b/ui/components/ui/identicon/identicon.component.js
@@ -1,7 +1,6 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
-import contractMap from '@metamask/contract-metadata';
import { toChecksumHexAddress } from '../../../../shared/modules/hexstring-utils';
import Jazzicon from '../jazzicon';
@@ -23,6 +22,8 @@ export default class Identicon extends PureComponent {
useBlockie: PropTypes.bool,
alt: PropTypes.string,
imageBorder: PropTypes.bool,
+ useTokenDetection: PropTypes.bool,
+ tokenList: PropTypes.object,
};
static defaultProps = {
@@ -33,6 +34,7 @@ export default class Identicon extends PureComponent {
image: undefined,
useBlockie: false,
alt: '',
+ tokenList: {},
};
renderImage() {
@@ -51,8 +53,14 @@ export default class Identicon extends PureComponent {
}
renderJazzicon() {
- const { address, className, diameter, alt } = this.props;
-
+ const {
+ address,
+ className,
+ diameter,
+ alt,
+ useTokenDetection,
+ tokenList,
+ } = this.props;
return (