mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 01:47:00 +01:00
Integrating the TokenListController to Extension (#11398)
This commit is contained in:
parent
5cf3e19910
commit
eb987a47b5
@ -1,10 +1,9 @@
|
|||||||
import Web3 from 'web3';
|
import Web3 from 'web3';
|
||||||
import contracts from '@metamask/contract-metadata';
|
|
||||||
import { warn } from 'loglevel';
|
import { warn } from 'loglevel';
|
||||||
import SINGLE_CALL_BALANCES_ABI from 'single-call-balance-checker-abi';
|
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 { SINGLE_CALL_BALANCES_ADDRESS } from '../constants/contracts';
|
||||||
import { MINUTE } from '../../../shared/constants/time';
|
import { MINUTE } from '../../../shared/constants/time';
|
||||||
|
import { isEqualCaseInsensitive } from '../../../ui/helpers/utils/util';
|
||||||
|
|
||||||
// By default, poll every 3 minutes
|
// By default, poll every 3 minutes
|
||||||
const DEFAULT_INTERVAL = MINUTE * 3;
|
const DEFAULT_INTERVAL = MINUTE * 3;
|
||||||
@ -24,57 +23,13 @@ export default class DetectTokensController {
|
|||||||
preferences,
|
preferences,
|
||||||
network,
|
network,
|
||||||
keyringMemStore,
|
keyringMemStore,
|
||||||
|
tokenList,
|
||||||
} = {}) {
|
} = {}) {
|
||||||
this.preferences = preferences;
|
this.preferences = preferences;
|
||||||
this.interval = interval;
|
this.interval = interval;
|
||||||
this.network = network;
|
this.network = network;
|
||||||
this.keyringMemStore = keyringMemStore;
|
this.keyringMemStore = keyringMemStore;
|
||||||
}
|
this.tokenList = tokenList;
|
||||||
|
|
||||||
/**
|
|
||||||
* 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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async _getTokenBalances(tokens) {
|
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
|
* Restart token detection polling period and call detectNewTokens
|
||||||
* in case of address change or user session initialization.
|
* in case of address change or user session initialization.
|
||||||
@ -138,9 +150,13 @@ export default class DetectTokensController {
|
|||||||
});
|
});
|
||||||
this.hiddenTokens = hiddenTokens;
|
this.hiddenTokens = hiddenTokens;
|
||||||
});
|
});
|
||||||
preferences.store.subscribe(({ selectedAddress }) => {
|
preferences.store.subscribe(({ selectedAddress, useTokenDetection }) => {
|
||||||
if (this.selectedAddress !== selectedAddress) {
|
if (
|
||||||
|
this.selectedAddress !== selectedAddress ||
|
||||||
|
this.useTokenDetection !== useTokenDetection
|
||||||
|
) {
|
||||||
this.selectedAddress = selectedAddress;
|
this.selectedAddress = selectedAddress;
|
||||||
|
this.useTokenDetection = useTokenDetection;
|
||||||
this.restartTokenDetection();
|
this.restartTokenDetection();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -176,6 +192,16 @@ export default class DetectTokensController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
set tokenList(tokenList) {
|
||||||
|
if (!tokenList) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._tokenList = tokenList;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal isActive state
|
* Internal isActive state
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
|
@ -1,15 +1,19 @@
|
|||||||
import { strict as assert } from 'assert';
|
import { strict as assert } from 'assert';
|
||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
|
import nock from 'nock';
|
||||||
import { ObservableStore } from '@metamask/obs-store';
|
import { ObservableStore } from '@metamask/obs-store';
|
||||||
import contracts from '@metamask/contract-metadata';
|
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
|
import {
|
||||||
|
ControllerMessenger,
|
||||||
|
TokenListController,
|
||||||
|
} from '@metamask/controllers';
|
||||||
import { MAINNET, ROPSTEN } from '../../../shared/constants/network';
|
import { MAINNET, ROPSTEN } from '../../../shared/constants/network';
|
||||||
import DetectTokensController from './detect-tokens';
|
import DetectTokensController from './detect-tokens';
|
||||||
import NetworkController from './network';
|
import NetworkController from './network';
|
||||||
import PreferencesController from './preferences';
|
import PreferencesController from './preferences';
|
||||||
|
|
||||||
describe('DetectTokensController', function () {
|
describe('DetectTokensController', function () {
|
||||||
|
let tokenListController;
|
||||||
const sandbox = sinon.createSandbox();
|
const sandbox = sinon.createSandbox();
|
||||||
let keyringMemStore, network, preferences, provider;
|
let keyringMemStore, network, preferences, provider;
|
||||||
|
|
||||||
@ -36,6 +40,87 @@ describe('DetectTokensController', function () {
|
|||||||
sandbox
|
sandbox
|
||||||
.stub(preferences, '_detectIsERC721')
|
.stub(preferences, '_detectIsERC721')
|
||||||
.returns(Promise.resolve(false));
|
.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 () {
|
after(function () {
|
||||||
@ -56,6 +141,7 @@ describe('DetectTokensController', function () {
|
|||||||
preferences,
|
preferences,
|
||||||
network,
|
network,
|
||||||
keyringMemStore,
|
keyringMemStore,
|
||||||
|
tokenList: tokenListController,
|
||||||
});
|
});
|
||||||
controller.isOpen = true;
|
controller.isOpen = true;
|
||||||
controller.isUnlocked = true;
|
controller.isUnlocked = true;
|
||||||
@ -75,10 +161,22 @@ describe('DetectTokensController', function () {
|
|||||||
it('should not check tokens while on test network', async function () {
|
it('should not check tokens while on test network', async function () {
|
||||||
sandbox.useFakeTimers();
|
sandbox.useFakeTimers();
|
||||||
network.setProviderType(ROPSTEN);
|
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({
|
const controller = new DetectTokensController({
|
||||||
preferences,
|
preferences,
|
||||||
network,
|
network,
|
||||||
keyringMemStore,
|
keyringMemStore,
|
||||||
|
tokenList: tokenListController,
|
||||||
});
|
});
|
||||||
controller.isOpen = true;
|
controller.isOpen = true;
|
||||||
controller.isUnlocked = true;
|
controller.isUnlocked = true;
|
||||||
@ -96,17 +194,16 @@ describe('DetectTokensController', function () {
|
|||||||
preferences,
|
preferences,
|
||||||
network,
|
network,
|
||||||
keyringMemStore,
|
keyringMemStore,
|
||||||
|
tokenList: tokenListController,
|
||||||
});
|
});
|
||||||
controller.isOpen = true;
|
controller.isOpen = true;
|
||||||
controller.isUnlocked = true;
|
controller.isUnlocked = true;
|
||||||
|
|
||||||
const contractAddresses = Object.keys(contracts);
|
const { tokenList } = tokenListController.state;
|
||||||
const erc20ContractAddresses = contractAddresses.filter(
|
const erc20ContractAddresses = Object.keys(tokenList);
|
||||||
(contractAddress) => contracts[contractAddress].erc20 === true,
|
|
||||||
);
|
|
||||||
|
|
||||||
const existingTokenAddress = erc20ContractAddresses[0];
|
const existingTokenAddress = erc20ContractAddresses[0];
|
||||||
const existingToken = contracts[existingTokenAddress];
|
const existingToken = tokenList[existingTokenAddress];
|
||||||
await preferences.addToken(
|
await preferences.addToken(
|
||||||
existingTokenAddress,
|
existingTokenAddress,
|
||||||
existingToken.symbol,
|
existingToken.symbol,
|
||||||
@ -144,17 +241,16 @@ describe('DetectTokensController', function () {
|
|||||||
preferences,
|
preferences,
|
||||||
network,
|
network,
|
||||||
keyringMemStore,
|
keyringMemStore,
|
||||||
|
tokenList: tokenListController,
|
||||||
});
|
});
|
||||||
controller.isOpen = true;
|
controller.isOpen = true;
|
||||||
controller.isUnlocked = true;
|
controller.isUnlocked = true;
|
||||||
|
|
||||||
const contractAddresses = Object.keys(contracts);
|
const { tokenList } = tokenListController.state;
|
||||||
const erc20ContractAddresses = contractAddresses.filter(
|
const erc20ContractAddresses = Object.keys(tokenList);
|
||||||
(contractAddress) => contracts[contractAddress].erc20 === true,
|
|
||||||
);
|
|
||||||
|
|
||||||
const existingTokenAddress = erc20ContractAddresses[0];
|
const existingTokenAddress = erc20ContractAddresses[0];
|
||||||
const existingToken = contracts[existingTokenAddress];
|
const existingToken = tokenList[existingTokenAddress];
|
||||||
await preferences.addToken(
|
await preferences.addToken(
|
||||||
existingTokenAddress,
|
existingTokenAddress,
|
||||||
existingToken.symbol,
|
existingToken.symbol,
|
||||||
@ -162,16 +258,16 @@ describe('DetectTokensController', function () {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const tokenAddressToAdd = erc20ContractAddresses[1];
|
const tokenAddressToAdd = erc20ContractAddresses[1];
|
||||||
const tokenToAdd = contracts[tokenAddressToAdd];
|
const tokenToAdd = tokenList[tokenAddressToAdd];
|
||||||
|
|
||||||
const contractAddresssesToDetect = contractAddresses.filter(
|
const contractAddressesToDetect = erc20ContractAddresses.filter(
|
||||||
(address) => address !== existingTokenAddress,
|
(address) => address !== existingTokenAddress,
|
||||||
);
|
);
|
||||||
const indexOfTokenToAdd = contractAddresssesToDetect.indexOf(
|
const indexOfTokenToAdd = contractAddressesToDetect.indexOf(
|
||||||
tokenAddressToAdd,
|
tokenAddressToAdd,
|
||||||
);
|
);
|
||||||
|
|
||||||
const balances = new Array(contractAddresssesToDetect.length);
|
const balances = new Array(contractAddressesToDetect.length);
|
||||||
balances[indexOfTokenToAdd] = new BigNumber(10);
|
balances[indexOfTokenToAdd] = new BigNumber(10);
|
||||||
|
|
||||||
sandbox
|
sandbox
|
||||||
@ -203,17 +299,16 @@ describe('DetectTokensController', function () {
|
|||||||
preferences,
|
preferences,
|
||||||
network,
|
network,
|
||||||
keyringMemStore,
|
keyringMemStore,
|
||||||
|
tokenList: tokenListController,
|
||||||
});
|
});
|
||||||
controller.isOpen = true;
|
controller.isOpen = true;
|
||||||
controller.isUnlocked = true;
|
controller.isUnlocked = true;
|
||||||
|
|
||||||
const contractAddresses = Object.keys(contracts);
|
const { tokenList } = tokenListController.state;
|
||||||
const erc20ContractAddresses = contractAddresses.filter(
|
const erc20ContractAddresses = Object.keys(tokenList);
|
||||||
(contractAddress) => contracts[contractAddress].erc20 === true,
|
|
||||||
);
|
|
||||||
|
|
||||||
const existingTokenAddress = erc20ContractAddresses[0];
|
const existingTokenAddress = erc20ContractAddresses[0];
|
||||||
const existingToken = contracts[existingTokenAddress];
|
const existingToken = tokenList[existingTokenAddress];
|
||||||
await preferences.addToken(
|
await preferences.addToken(
|
||||||
existingTokenAddress,
|
existingTokenAddress,
|
||||||
existingToken.symbol,
|
existingToken.symbol,
|
||||||
@ -221,16 +316,16 @@ describe('DetectTokensController', function () {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const tokenAddressToAdd = erc20ContractAddresses[1];
|
const tokenAddressToAdd = erc20ContractAddresses[1];
|
||||||
const tokenToAdd = contracts[tokenAddressToAdd];
|
const tokenToAdd = tokenList[tokenAddressToAdd];
|
||||||
|
|
||||||
const contractAddresssesToDetect = contractAddresses.filter(
|
const contractAddressesToDetect = erc20ContractAddresses.filter(
|
||||||
(address) => address !== existingTokenAddress,
|
(address) => address !== existingTokenAddress,
|
||||||
);
|
);
|
||||||
const indexOfTokenToAdd = contractAddresssesToDetect.indexOf(
|
const indexOfTokenToAdd = contractAddressesToDetect.indexOf(
|
||||||
tokenAddressToAdd,
|
tokenAddressToAdd,
|
||||||
);
|
);
|
||||||
|
|
||||||
const balances = new Array(contractAddresssesToDetect.length);
|
const balances = new Array(contractAddressesToDetect.length);
|
||||||
balances[indexOfTokenToAdd] = new BigNumber(10);
|
balances[indexOfTokenToAdd] = new BigNumber(10);
|
||||||
|
|
||||||
sandbox
|
sandbox
|
||||||
@ -261,6 +356,7 @@ describe('DetectTokensController', function () {
|
|||||||
preferences,
|
preferences,
|
||||||
network,
|
network,
|
||||||
keyringMemStore,
|
keyringMemStore,
|
||||||
|
tokenList: tokenListController,
|
||||||
});
|
});
|
||||||
controller.isOpen = true;
|
controller.isOpen = true;
|
||||||
controller.isUnlocked = true;
|
controller.isUnlocked = true;
|
||||||
@ -277,6 +373,7 @@ describe('DetectTokensController', function () {
|
|||||||
preferences,
|
preferences,
|
||||||
network,
|
network,
|
||||||
keyringMemStore,
|
keyringMemStore,
|
||||||
|
tokenList: tokenListController,
|
||||||
});
|
});
|
||||||
controller.isOpen = true;
|
controller.isOpen = true;
|
||||||
controller.selectedAddress = '0x0';
|
controller.selectedAddress = '0x0';
|
||||||
@ -292,6 +389,7 @@ describe('DetectTokensController', function () {
|
|||||||
preferences,
|
preferences,
|
||||||
network,
|
network,
|
||||||
keyringMemStore,
|
keyringMemStore,
|
||||||
|
tokenList: tokenListController,
|
||||||
});
|
});
|
||||||
controller.isOpen = true;
|
controller.isOpen = true;
|
||||||
controller.isUnlocked = false;
|
controller.isUnlocked = false;
|
||||||
|
@ -51,7 +51,10 @@ export default class PreferencesController {
|
|||||||
useNonceField: false,
|
useNonceField: false,
|
||||||
usePhishDetect: true,
|
usePhishDetect: true,
|
||||||
dismissSeedBackUpReminder: false,
|
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.
|
// WARNING: Do not use feature flags for security-sensitive things.
|
||||||
// Feature flag toggling is available in the global namespace
|
// 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
|
* @param {boolean} val - Whether or not the user prefers to use the static token list or dynamic token list from the API
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
setUseStaticTokenList(val) {
|
setUseTokenDetection(val) {
|
||||||
this.store.updateState({ useStaticTokenList: val });
|
this.store.updateState({ useTokenDetection: val });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -869,22 +869,22 @@ describe('preferences controller', function () {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('setUseStaticTokenList', function () {
|
describe('setUseTokenDetection', function () {
|
||||||
it('should default to false', function () {
|
it('should default to true', function () {
|
||||||
const state = preferencesController.store.getState();
|
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(
|
assert.equal(
|
||||||
preferencesController.store.getState().useStaticTokenList,
|
preferencesController.store.getState().useTokenDetection,
|
||||||
false,
|
|
||||||
);
|
|
||||||
preferencesController.setUseStaticTokenList(true);
|
|
||||||
assert.equal(
|
|
||||||
preferencesController.store.getState().useStaticTokenList,
|
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
preferencesController.setUseTokenDetection(false);
|
||||||
|
assert.equal(
|
||||||
|
preferencesController.store.getState().useTokenDetection,
|
||||||
|
false,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -16,7 +16,6 @@ import TrezorKeyring from 'eth-trezor-keyring';
|
|||||||
import LedgerBridgeKeyring from '@metamask/eth-ledger-bridge-keyring';
|
import LedgerBridgeKeyring from '@metamask/eth-ledger-bridge-keyring';
|
||||||
import EthQuery from 'eth-query';
|
import EthQuery from 'eth-query';
|
||||||
import nanoid from 'nanoid';
|
import nanoid from 'nanoid';
|
||||||
import contractMap from '@metamask/contract-metadata';
|
|
||||||
import {
|
import {
|
||||||
AddressBookController,
|
AddressBookController,
|
||||||
ApprovalController,
|
ApprovalController,
|
||||||
@ -238,8 +237,8 @@ export default class MetamaskController extends EventEmitter {
|
|||||||
});
|
});
|
||||||
this.tokenListController = new TokenListController({
|
this.tokenListController = new TokenListController({
|
||||||
chainId: hexToDecimal(this.networkController.getCurrentChainId()),
|
chainId: hexToDecimal(this.networkController.getCurrentChainId()),
|
||||||
useStaticTokenList: this.preferencesController.store.getState()
|
useStaticTokenList: !this.preferencesController.store.getState()
|
||||||
.useStaticTokenList,
|
.useTokenDetection,
|
||||||
onNetworkStateChange: (cb) =>
|
onNetworkStateChange: (cb) =>
|
||||||
this.networkController.store.subscribe((networkState) => {
|
this.networkController.store.subscribe((networkState) => {
|
||||||
const modifiedNetworkState = {
|
const modifiedNetworkState = {
|
||||||
@ -251,11 +250,17 @@ export default class MetamaskController extends EventEmitter {
|
|||||||
};
|
};
|
||||||
return cb(modifiedNetworkState);
|
return cb(modifiedNetworkState);
|
||||||
}),
|
}),
|
||||||
onPreferencesStateChange: this.preferencesController.store.subscribe.bind(
|
onPreferencesStateChange: (cb) =>
|
||||||
this.preferencesController.store,
|
this.preferencesController.store.subscribe((preferencesState) => {
|
||||||
),
|
const modifiedPreferencesState = {
|
||||||
|
...preferencesState,
|
||||||
|
useStaticTokenList: !this.preferencesController.store.getState()
|
||||||
|
.useTokenDetection,
|
||||||
|
};
|
||||||
|
return cb(modifiedPreferencesState);
|
||||||
|
}),
|
||||||
messenger: tokenListMessenger,
|
messenger: tokenListMessenger,
|
||||||
state: initState.tokenListController,
|
state: initState.TokenListController,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.phishingController = new PhishingController();
|
this.phishingController = new PhishingController();
|
||||||
@ -372,6 +377,7 @@ export default class MetamaskController extends EventEmitter {
|
|||||||
preferences: this.preferencesController,
|
preferences: this.preferencesController,
|
||||||
network: this.networkController,
|
network: this.networkController,
|
||||||
keyringMemStore: this.keyringController.memStore,
|
keyringMemStore: this.keyringController.memStore,
|
||||||
|
tokenList: this.tokenListController,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.addressBookController = new AddressBookController(
|
this.addressBookController = new AddressBookController(
|
||||||
@ -775,8 +781,8 @@ export default class MetamaskController extends EventEmitter {
|
|||||||
setUseBlockie: this.setUseBlockie.bind(this),
|
setUseBlockie: this.setUseBlockie.bind(this),
|
||||||
setUseNonceField: this.setUseNonceField.bind(this),
|
setUseNonceField: this.setUseNonceField.bind(this),
|
||||||
setUsePhishDetect: this.setUsePhishDetect.bind(this),
|
setUsePhishDetect: this.setUsePhishDetect.bind(this),
|
||||||
setUseStaticTokenList: nodeify(
|
setUseTokenDetection: nodeify(
|
||||||
this.preferencesController.setUseStaticTokenList,
|
this.preferencesController.setUseTokenDetection,
|
||||||
this.preferencesController,
|
this.preferencesController,
|
||||||
),
|
),
|
||||||
setIpfsGateway: this.setIpfsGateway.bind(this),
|
setIpfsGateway: this.setIpfsGateway.bind(this),
|
||||||
@ -1297,28 +1303,8 @@ export default class MetamaskController extends EventEmitter {
|
|||||||
tokens,
|
tokens,
|
||||||
} = this.preferencesController.store.getState();
|
} = 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 = {
|
const preferences = {
|
||||||
accountTokens: filteredAccountTokens,
|
accountTokens,
|
||||||
currentLocale,
|
currentLocale,
|
||||||
frequentRpcList,
|
frequentRpcList,
|
||||||
identities,
|
identities,
|
||||||
|
@ -25,5 +25,49 @@
|
|||||||
"fallback_to_v1": false
|
"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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,6 +154,53 @@
|
|||||||
"editingTransactionId": null,
|
"editingTransactionId": null,
|
||||||
"toNickname": ""
|
"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": [
|
"currentNetworkTxList": [
|
||||||
{
|
{
|
||||||
"id": 3387511061307736,
|
"id": 3387511061307736,
|
||||||
|
@ -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": {
|
"PreferencesController": {
|
||||||
"accountTokens": {
|
"accountTokens": {
|
||||||
"0x5cfe73b6021e818b776b421b1c4db2474086a7e1": {
|
"0x5cfe73b6021e818b776b421b1c4db2474086a7e1": {
|
||||||
@ -123,7 +478,8 @@
|
|||||||
"tokens": [],
|
"tokens": [],
|
||||||
"useBlockie": false,
|
"useBlockie": false,
|
||||||
"useNonceField": false,
|
"useNonceField": false,
|
||||||
"usePhishDetect": true
|
"usePhishDetect": true,
|
||||||
|
"useTokenDetection": true
|
||||||
},
|
},
|
||||||
"config": {},
|
"config": {},
|
||||||
"firstTimeInfo": {
|
"firstTimeInfo": {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
const { strict: assert } = require('assert');
|
const { strict: assert } = require('assert');
|
||||||
const { withFixtures, xxLargeDelayMs } = require('../helpers');
|
const { withFixtures, xxLargeDelayMs, xLargeDelayMs } = require('../helpers');
|
||||||
|
|
||||||
describe('Permissions', function () {
|
describe('Permissions', function () {
|
||||||
it('sets permissions and connect to Dapp', async function () {
|
it('sets permissions and connect to Dapp', async function () {
|
||||||
@ -62,7 +62,7 @@ describe('Permissions', function () {
|
|||||||
text: 'Connected sites',
|
text: 'Connected sites',
|
||||||
tag: 'h2',
|
tag: 'h2',
|
||||||
});
|
});
|
||||||
|
await driver.delay(xLargeDelayMs);
|
||||||
const domains = await driver.findClickableElements(
|
const domains = await driver.findClickableElements(
|
||||||
'.connected-sites-list__domain-name',
|
'.connected-sites-list__domain-name',
|
||||||
);
|
);
|
||||||
|
@ -52,6 +52,12 @@ async function setupFetchMocking(driver) {
|
|||||||
if (url.match(/featureFlags$/u)) {
|
if (url.match(/featureFlags$/u)) {
|
||||||
return { json: async () => clone(mockResponses.swaps.featureFlags) };
|
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);
|
return window.origFetch(...args);
|
||||||
};
|
};
|
||||||
|
@ -222,6 +222,76 @@ export const createSwapsMockStore = () => {
|
|||||||
swapsFeatureIsLive: false,
|
swapsFeatureIsLive: false,
|
||||||
useNewSwapsApi: 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: {
|
appState: {
|
||||||
modal: {
|
modal: {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import contractMap from '@metamask/contract-metadata';
|
|
||||||
import { toChecksumHexAddress } from '../../../../shared/modules/hexstring-utils';
|
import { toChecksumHexAddress } from '../../../../shared/modules/hexstring-utils';
|
||||||
|
|
||||||
import Jazzicon from '../jazzicon';
|
import Jazzicon from '../jazzicon';
|
||||||
@ -23,6 +22,8 @@ export default class Identicon extends PureComponent {
|
|||||||
useBlockie: PropTypes.bool,
|
useBlockie: PropTypes.bool,
|
||||||
alt: PropTypes.string,
|
alt: PropTypes.string,
|
||||||
imageBorder: PropTypes.bool,
|
imageBorder: PropTypes.bool,
|
||||||
|
useTokenDetection: PropTypes.bool,
|
||||||
|
tokenList: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
@ -33,6 +34,7 @@ export default class Identicon extends PureComponent {
|
|||||||
image: undefined,
|
image: undefined,
|
||||||
useBlockie: false,
|
useBlockie: false,
|
||||||
alt: '',
|
alt: '',
|
||||||
|
tokenList: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
renderImage() {
|
renderImage() {
|
||||||
@ -51,8 +53,14 @@ export default class Identicon extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderJazzicon() {
|
renderJazzicon() {
|
||||||
const { address, className, diameter, alt } = this.props;
|
const {
|
||||||
|
address,
|
||||||
|
className,
|
||||||
|
diameter,
|
||||||
|
alt,
|
||||||
|
useTokenDetection,
|
||||||
|
tokenList,
|
||||||
|
} = this.props;
|
||||||
return (
|
return (
|
||||||
<Jazzicon
|
<Jazzicon
|
||||||
address={address}
|
address={address}
|
||||||
@ -60,6 +68,8 @@ export default class Identicon extends PureComponent {
|
|||||||
className={classnames('identicon', className)}
|
className={classnames('identicon', className)}
|
||||||
style={getStyles(diameter)}
|
style={getStyles(diameter)}
|
||||||
alt={alt}
|
alt={alt}
|
||||||
|
useTokenDetection={useTokenDetection}
|
||||||
|
tokenList={tokenList}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -78,16 +88,25 @@ export default class Identicon extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { address, image, useBlockie, addBorder, diameter } = this.props;
|
const {
|
||||||
|
address,
|
||||||
|
image,
|
||||||
|
useBlockie,
|
||||||
|
addBorder,
|
||||||
|
diameter,
|
||||||
|
useTokenDetection,
|
||||||
|
tokenList,
|
||||||
|
} = this.props;
|
||||||
if (image) {
|
if (image) {
|
||||||
return this.renderImage();
|
return this.renderImage();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (address) {
|
if (address) {
|
||||||
const checksummedAddress = toChecksumHexAddress(address);
|
// token from dynamic api list is fetched when useTokenDetection is true
|
||||||
|
const tokenAddress = useTokenDetection
|
||||||
if (checksummedAddress && contractMap[checksummedAddress]?.logo) {
|
? address
|
||||||
|
: toChecksumHexAddress(address);
|
||||||
|
if (tokenAddress && tokenList[tokenAddress]?.iconUrl) {
|
||||||
return this.renderJazzicon();
|
return this.renderJazzicon();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,11 +3,13 @@ import Identicon from './identicon.component';
|
|||||||
|
|
||||||
const mapStateToProps = (state) => {
|
const mapStateToProps = (state) => {
|
||||||
const {
|
const {
|
||||||
metamask: { useBlockie },
|
metamask: { useBlockie, useTokenDetection, tokenList },
|
||||||
} = state;
|
} = state;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
useBlockie,
|
useBlockie,
|
||||||
|
useTokenDetection,
|
||||||
|
tokenList,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -15,6 +15,8 @@ export default class Jazzicon extends PureComponent {
|
|||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
diameter: PropTypes.number,
|
diameter: PropTypes.number,
|
||||||
style: PropTypes.object,
|
style: PropTypes.object,
|
||||||
|
useTokenDetection: PropTypes.bool,
|
||||||
|
tokenList: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
@ -46,8 +48,13 @@ export default class Jazzicon extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
appendJazzicon() {
|
appendJazzicon() {
|
||||||
const { address, diameter } = this.props;
|
const { address, diameter, useTokenDetection, tokenList } = this.props;
|
||||||
const image = iconFactory.iconForAddress(address, diameter);
|
const image = iconFactory.iconForAddress(
|
||||||
|
address,
|
||||||
|
diameter,
|
||||||
|
useTokenDetection,
|
||||||
|
tokenList,
|
||||||
|
);
|
||||||
this.container.current.appendChild(image);
|
this.container.current.appendChild(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
|
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
|
||||||
import abi from 'human-standard-token-abi';
|
import abi from 'human-standard-token-abi';
|
||||||
import contractMap from '@metamask/contract-metadata';
|
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import { addHexPrefix, toChecksumAddress } from 'ethereumjs-util';
|
import { addHexPrefix } from 'ethereumjs-util';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import {
|
import {
|
||||||
conversionGreaterThan,
|
conversionGreaterThan,
|
||||||
@ -39,6 +38,8 @@ import {
|
|||||||
getTargetAccount,
|
getTargetAccount,
|
||||||
getIsNonStandardEthChain,
|
getIsNonStandardEthChain,
|
||||||
checkNetworkAndAccountSupports1559,
|
checkNetworkAndAccountSupports1559,
|
||||||
|
getUseTokenDetection,
|
||||||
|
getTokenList,
|
||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
import {
|
import {
|
||||||
disconnectGasFeeEstimatePoller,
|
disconnectGasFeeEstimatePoller,
|
||||||
@ -71,6 +72,7 @@ import {
|
|||||||
isDefaultMetaMaskChain,
|
isDefaultMetaMaskChain,
|
||||||
isOriginContractAddress,
|
isOriginContractAddress,
|
||||||
isValidDomainName,
|
isValidDomainName,
|
||||||
|
isEqualCaseInsensitive,
|
||||||
} from '../../helpers/utils/util';
|
} from '../../helpers/utils/util';
|
||||||
import {
|
import {
|
||||||
getGasEstimateType,
|
getGasEstimateType,
|
||||||
@ -517,6 +519,8 @@ export const initializeSendState = createAsyncThunk(
|
|||||||
gasTotal: addHexPrefix(calcGasTotal(gasLimit, gasPrice)),
|
gasTotal: addHexPrefix(calcGasTotal(gasLimit, gasPrice)),
|
||||||
gasEstimatePollToken,
|
gasEstimatePollToken,
|
||||||
eip1559support,
|
eip1559support,
|
||||||
|
useTokenDetection: getUseTokenDetection(state),
|
||||||
|
tokenAddressList: Object.keys(getTokenList(state)),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -986,7 +990,7 @@ const slice = createSlice({
|
|||||||
recipient.warning = null;
|
recipient.warning = null;
|
||||||
} else {
|
} else {
|
||||||
const isSendingToken = asset.type === ASSET_TYPES.TOKEN;
|
const isSendingToken = asset.type === ASSET_TYPES.TOKEN;
|
||||||
const { chainId, tokens } = action.payload;
|
const { chainId, tokens, tokenAddressList } = action.payload;
|
||||||
if (
|
if (
|
||||||
isBurnAddress(recipient.userInput) ||
|
isBurnAddress(recipient.userInput) ||
|
||||||
(!isValidHexAddress(recipient.userInput, {
|
(!isValidHexAddress(recipient.userInput, {
|
||||||
@ -1005,11 +1009,12 @@ const slice = createSlice({
|
|||||||
} else {
|
} else {
|
||||||
recipient.error = null;
|
recipient.error = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isSendingToken &&
|
isSendingToken &&
|
||||||
isValidHexAddress(recipient.userInput) &&
|
isValidHexAddress(recipient.userInput) &&
|
||||||
(toChecksumAddress(recipient.userInput) in contractMap ||
|
(tokenAddressList.find((address) =>
|
||||||
|
isEqualCaseInsensitive(address, recipient.userInput),
|
||||||
|
) ||
|
||||||
checkExistingAddresses(recipient.userInput, tokens))
|
checkExistingAddresses(recipient.userInput, tokens))
|
||||||
) {
|
) {
|
||||||
recipient.warning = KNOWN_RECIPIENT_ADDRESS_WARNING;
|
recipient.warning = KNOWN_RECIPIENT_ADDRESS_WARNING;
|
||||||
@ -1210,6 +1215,8 @@ const slice = createSlice({
|
|||||||
payload: {
|
payload: {
|
||||||
chainId: action.payload.chainId,
|
chainId: action.payload.chainId,
|
||||||
tokens: action.payload.tokens,
|
tokens: action.payload.tokens,
|
||||||
|
useTokenDetection: action.payload.useTokenDetection,
|
||||||
|
tokenAddressList: action.payload.tokenAddressList,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1395,7 +1402,14 @@ export function updateRecipientUserInput(userInput) {
|
|||||||
const state = getState();
|
const state = getState();
|
||||||
const chainId = getCurrentChainId(state);
|
const chainId = getCurrentChainId(state);
|
||||||
const tokens = getTokens(state);
|
const tokens = getTokens(state);
|
||||||
debouncedValidateRecipientUserInput(dispatch, { chainId, tokens });
|
const useTokenDetection = getUseTokenDetection(state);
|
||||||
|
const tokenAddressList = Object.keys(getTokenList(state));
|
||||||
|
debouncedValidateRecipientUserInput(dispatch, {
|
||||||
|
chainId,
|
||||||
|
tokens,
|
||||||
|
useTokenDetection,
|
||||||
|
tokenAddressList,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -628,6 +628,8 @@ describe('Send Slice', () => {
|
|||||||
payload: {
|
payload: {
|
||||||
chainId: '',
|
chainId: '',
|
||||||
tokens: [],
|
tokens: [],
|
||||||
|
useTokenDetection: true,
|
||||||
|
tokenAddressList: [],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -649,6 +651,8 @@ describe('Send Slice', () => {
|
|||||||
payload: {
|
payload: {
|
||||||
chainId: '0x55',
|
chainId: '0x55',
|
||||||
tokens: [],
|
tokens: [],
|
||||||
|
useTokenDetection: true,
|
||||||
|
tokenAddressList: [],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -671,6 +675,8 @@ describe('Send Slice', () => {
|
|||||||
payload: {
|
payload: {
|
||||||
chainId: '',
|
chainId: '',
|
||||||
tokens: [],
|
tokens: [],
|
||||||
|
useTokenDetection: true,
|
||||||
|
tokenAddressList: [],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -698,6 +704,8 @@ describe('Send Slice', () => {
|
|||||||
payload: {
|
payload: {
|
||||||
chainId: '0x4',
|
chainId: '0x4',
|
||||||
tokens: [],
|
tokens: [],
|
||||||
|
useTokenDetection: true,
|
||||||
|
tokenAddressList: ['0x514910771af9ca656af840dff83e8264ecf986ca'],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1111,6 +1119,32 @@ describe('Send Slice', () => {
|
|||||||
provider: {
|
provider: {
|
||||||
chainId: '0x4',
|
chainId: '0x4',
|
||||||
},
|
},
|
||||||
|
useTokenDetection: true,
|
||||||
|
tokenList: {
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
send: initialState,
|
send: initialState,
|
||||||
gas: {
|
gas: {
|
||||||
@ -1484,6 +1518,31 @@ describe('Send Slice', () => {
|
|||||||
chainId: '',
|
chainId: '',
|
||||||
},
|
},
|
||||||
tokens: [],
|
tokens: [],
|
||||||
|
useTokenDetection: true,
|
||||||
|
tokenList: {
|
||||||
|
'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,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1512,6 +1571,8 @@ describe('Send Slice', () => {
|
|||||||
expect(store.getActions()[1].payload).toStrictEqual({
|
expect(store.getActions()[1].payload).toStrictEqual({
|
||||||
chainId: '',
|
chainId: '',
|
||||||
tokens: [],
|
tokens: [],
|
||||||
|
useTokenDetection: true,
|
||||||
|
tokenAddressList: ['0x514910771af9ca656af840dff83e8264ecf986ca'],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -1736,6 +1797,32 @@ describe('Send Slice', () => {
|
|||||||
chainId: '',
|
chainId: '',
|
||||||
},
|
},
|
||||||
tokens: [],
|
tokens: [],
|
||||||
|
useTokenDetection: true,
|
||||||
|
tokenList: {
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
send: {
|
send: {
|
||||||
asset: {
|
asset: {
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import contractMap from '@metamask/contract-metadata';
|
|
||||||
import {
|
import {
|
||||||
isValidHexAddress,
|
isValidHexAddress,
|
||||||
toChecksumHexAddress,
|
toChecksumHexAddress,
|
||||||
@ -18,11 +17,18 @@ function IconFactory(jazzicon) {
|
|||||||
this.cache = {};
|
this.cache = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
IconFactory.prototype.iconForAddress = function (address, diameter) {
|
IconFactory.prototype.iconForAddress = function (
|
||||||
const addr = toChecksumHexAddress(address);
|
address,
|
||||||
|
diameter,
|
||||||
if (iconExistsFor(addr)) {
|
useTokenDetection,
|
||||||
return imageElFor(addr);
|
tokenList,
|
||||||
|
) {
|
||||||
|
// When useTokenDetection flag is true the tokenList contains tokens with non-checksum address from the dynamic token service api,
|
||||||
|
// When useTokenDetection flag is false the tokenList contains tokens with checksum addresses from contract-metadata.
|
||||||
|
// So the flag indicates whether the address of tokens currently on the tokenList is checksum or not.
|
||||||
|
const addr = useTokenDetection ? address : toChecksumHexAddress(address);
|
||||||
|
if (iconExistsFor(addr, tokenList)) {
|
||||||
|
return imageElFor(addr, useTokenDetection, tokenList);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.generateIdenticonSvg(address, diameter);
|
return this.generateIdenticonSvg(address, diameter);
|
||||||
@ -49,18 +55,22 @@ IconFactory.prototype.generateNewIdenticon = function (address, diameter) {
|
|||||||
|
|
||||||
// util
|
// util
|
||||||
|
|
||||||
function iconExistsFor(address) {
|
function iconExistsFor(address, tokenList) {
|
||||||
return (
|
return (
|
||||||
contractMap[address] &&
|
tokenList[address] &&
|
||||||
isValidHexAddress(address, { allowNonPrefixed: false }) &&
|
isValidHexAddress(address, { allowNonPrefixed: false }) &&
|
||||||
contractMap[address].logo
|
tokenList[address].iconUrl
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function imageElFor(address) {
|
function imageElFor(address, useTokenDetection, tokenList) {
|
||||||
const contract = contractMap[address];
|
const tokenMetadata = tokenList[address];
|
||||||
const fileName = contract.logo;
|
const fileName = tokenMetadata?.iconUrl;
|
||||||
const path = `images/contract/${fileName}`;
|
// token from dynamic api list is fetched when useTokenDetection is true
|
||||||
|
// In the static list, the iconUrl will be holding only a filename for the image,
|
||||||
|
// the corresponding images will be available in the `images/contract/` location when the contract-metadata package was added to the extension
|
||||||
|
// so that it can be accessed using the filename in iconUrl.
|
||||||
|
const path = useTokenDetection ? fileName : `images/contract/${fileName}`;
|
||||||
const img = document.createElement('img');
|
const img = document.createElement('img');
|
||||||
img.src = path;
|
img.src = path;
|
||||||
img.style.width = '100%';
|
img.style.width = '100%';
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import log from 'loglevel';
|
import log from 'loglevel';
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import contractMap from '@metamask/contract-metadata';
|
|
||||||
import {
|
import {
|
||||||
conversionUtil,
|
conversionUtil,
|
||||||
multiplyCurrencies,
|
multiplyCurrencies,
|
||||||
@ -8,13 +7,6 @@ import {
|
|||||||
import * as util from './util';
|
import * as util from './util';
|
||||||
import { formatCurrency } from './confirm-tx.util';
|
import { formatCurrency } from './confirm-tx.util';
|
||||||
|
|
||||||
const casedContractMap = Object.keys(contractMap).reduce((acc, base) => {
|
|
||||||
return {
|
|
||||||
...acc,
|
|
||||||
[base.toLowerCase()]: contractMap[base],
|
|
||||||
};
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
const DEFAULT_SYMBOL = '';
|
const DEFAULT_SYMBOL = '';
|
||||||
|
|
||||||
async function getSymbolFromContract(tokenAddress) {
|
async function getSymbolFromContract(tokenAddress) {
|
||||||
@ -48,15 +40,21 @@ async function getDecimalsFromContract(tokenAddress) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getContractMetadata(tokenAddress) {
|
function getTokenMetadata(tokenAddress, tokenList) {
|
||||||
return tokenAddress && casedContractMap[tokenAddress.toLowerCase()];
|
const casedTokenList = Object.keys(tokenList).reduce((acc, base) => {
|
||||||
|
return {
|
||||||
|
...acc,
|
||||||
|
[base.toLowerCase()]: tokenList[base],
|
||||||
|
};
|
||||||
|
}, {});
|
||||||
|
return tokenAddress && casedTokenList[tokenAddress.toLowerCase()];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getSymbol(tokenAddress) {
|
async function getSymbol(tokenAddress, tokenList) {
|
||||||
let symbol = await getSymbolFromContract(tokenAddress);
|
let symbol = await getSymbolFromContract(tokenAddress);
|
||||||
|
|
||||||
if (!symbol) {
|
if (!symbol) {
|
||||||
const contractMetadataInfo = getContractMetadata(tokenAddress);
|
const contractMetadataInfo = getTokenMetadata(tokenAddress, tokenList);
|
||||||
|
|
||||||
if (contractMetadataInfo) {
|
if (contractMetadataInfo) {
|
||||||
symbol = contractMetadataInfo.symbol;
|
symbol = contractMetadataInfo.symbol;
|
||||||
@ -66,11 +64,11 @@ async function getSymbol(tokenAddress) {
|
|||||||
return symbol;
|
return symbol;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getDecimals(tokenAddress) {
|
async function getDecimals(tokenAddress, tokenList) {
|
||||||
let decimals = await getDecimalsFromContract(tokenAddress);
|
let decimals = await getDecimalsFromContract(tokenAddress);
|
||||||
|
|
||||||
if (!decimals || decimals === '0') {
|
if (!decimals || decimals === '0') {
|
||||||
const contractMetadataInfo = getContractMetadata(tokenAddress);
|
const contractMetadataInfo = getTokenMetadata(tokenAddress, tokenList);
|
||||||
|
|
||||||
if (contractMetadataInfo) {
|
if (contractMetadataInfo) {
|
||||||
decimals = contractMetadataInfo.decimals?.toString();
|
decimals = contractMetadataInfo.decimals?.toString();
|
||||||
@ -80,23 +78,12 @@ async function getDecimals(tokenAddress) {
|
|||||||
return decimals;
|
return decimals;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getSymbolAndDecimals(tokenAddress, existingTokens = []) {
|
export async function getSymbolAndDecimals(tokenAddress, tokenList) {
|
||||||
const existingToken = existingTokens.find(
|
|
||||||
({ address }) => tokenAddress === address,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (existingToken) {
|
|
||||||
return {
|
|
||||||
symbol: existingToken.symbol,
|
|
||||||
decimals: existingToken.decimals,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let symbol, decimals;
|
let symbol, decimals;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
symbol = await getSymbol(tokenAddress);
|
symbol = await getSymbol(tokenAddress, tokenList);
|
||||||
decimals = await getDecimals(tokenAddress);
|
decimals = await getDecimals(tokenAddress, tokenList);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.warn(
|
log.warn(
|
||||||
`symbol() and decimal() calls for token at address ${tokenAddress} resulted in error:`,
|
`symbol() and decimal() calls for token at address ${tokenAddress} resulted in error:`,
|
||||||
@ -113,12 +100,12 @@ export async function getSymbolAndDecimals(tokenAddress, existingTokens = []) {
|
|||||||
export function tokenInfoGetter() {
|
export function tokenInfoGetter() {
|
||||||
const tokens = {};
|
const tokens = {};
|
||||||
|
|
||||||
return async (address) => {
|
return async (address, tokenList) => {
|
||||||
if (tokens[address]) {
|
if (tokens[address]) {
|
||||||
return tokens[address];
|
return tokens[address];
|
||||||
}
|
}
|
||||||
|
|
||||||
tokens[address] = await getSymbolAndDecimals(address);
|
tokens[address] = await getSymbolAndDecimals(address, tokenList);
|
||||||
|
|
||||||
return tokens[address];
|
return tokens[address];
|
||||||
};
|
};
|
||||||
|
@ -56,6 +56,12 @@ export function isDefaultMetaMaskChain(chainId) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Both inputs should be strings. This method is currently used to compare tokenAddress hex strings.
|
||||||
|
export function isEqualCaseInsensitive(value1, value2) {
|
||||||
|
if (typeof value1 !== 'string' || typeof value2 !== 'string') return false;
|
||||||
|
return value1.toLowerCase() === value2.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
export function valuesFor(obj) {
|
export function valuesFor(obj) {
|
||||||
if (!obj) {
|
if (!obj) {
|
||||||
return [];
|
return [];
|
||||||
|
@ -9,6 +9,8 @@ import {
|
|||||||
getCurrentCurrency,
|
getCurrentCurrency,
|
||||||
getSwapsDefaultToken,
|
getSwapsDefaultToken,
|
||||||
getCurrentChainId,
|
getCurrentChainId,
|
||||||
|
getUseTokenDetection,
|
||||||
|
getTokenList,
|
||||||
} from '../selectors';
|
} from '../selectors';
|
||||||
import { getConversionRate } from '../ducks/metamask/metamask';
|
import { getConversionRate } from '../ducks/metamask/metamask';
|
||||||
|
|
||||||
@ -17,7 +19,7 @@ import { isSwapsDefaultTokenSymbol } from '../../shared/modules/swaps.utils';
|
|||||||
import { toChecksumHexAddress } from '../../shared/modules/hexstring-utils';
|
import { toChecksumHexAddress } from '../../shared/modules/hexstring-utils';
|
||||||
import { useEqualityCheck } from './useEqualityCheck';
|
import { useEqualityCheck } from './useEqualityCheck';
|
||||||
|
|
||||||
const tokenList = shuffle(
|
const shuffledContractMap = shuffle(
|
||||||
Object.entries(contractMap)
|
Object.entries(contractMap)
|
||||||
.map(([address, tokenData]) => ({
|
.map(([address, tokenData]) => ({
|
||||||
...tokenData,
|
...tokenData,
|
||||||
@ -32,9 +34,14 @@ export function getRenderableTokenData(
|
|||||||
conversionRate,
|
conversionRate,
|
||||||
currentCurrency,
|
currentCurrency,
|
||||||
chainId,
|
chainId,
|
||||||
|
tokenList,
|
||||||
|
useTokenDetection,
|
||||||
) {
|
) {
|
||||||
const { symbol, name, address, iconUrl, string, balance, decimals } = token;
|
const { symbol, name, address, iconUrl, string, balance, decimals } = token;
|
||||||
|
// token from dynamic api list is fetched when useTokenDetection is true
|
||||||
|
const tokenAddress = useTokenDetection
|
||||||
|
? address
|
||||||
|
: toChecksumHexAddress(address);
|
||||||
const formattedFiat =
|
const formattedFiat =
|
||||||
getTokenFiatAmount(
|
getTokenFiatAmount(
|
||||||
isSwapsDefaultTokenSymbol(symbol, chainId)
|
isSwapsDefaultTokenSymbol(symbol, chainId)
|
||||||
@ -59,12 +66,12 @@ export function getRenderableTokenData(
|
|||||||
) || '';
|
) || '';
|
||||||
const usedIconUrl =
|
const usedIconUrl =
|
||||||
iconUrl ||
|
iconUrl ||
|
||||||
(contractMap[toChecksumHexAddress(address)] &&
|
(tokenList[tokenAddress] &&
|
||||||
`images/contract/${contractMap[toChecksumHexAddress(address)].logo}`);
|
`images/contract/${tokenList[tokenAddress].iconUrl}`);
|
||||||
return {
|
return {
|
||||||
...token,
|
...token,
|
||||||
primaryLabel: symbol,
|
primaryLabel: symbol,
|
||||||
secondaryLabel: name || contractMap[toChecksumHexAddress(address)]?.name,
|
secondaryLabel: name || tokenList[tokenAddress]?.name,
|
||||||
rightPrimaryLabel:
|
rightPrimaryLabel:
|
||||||
string && `${new BigNumber(string).round(6).toString()} ${symbol}`,
|
string && `${new BigNumber(string).round(6).toString()} ${symbol}`,
|
||||||
rightSecondaryLabel: formattedFiat,
|
rightSecondaryLabel: formattedFiat,
|
||||||
@ -72,18 +79,27 @@ export function getRenderableTokenData(
|
|||||||
identiconAddress: usedIconUrl ? null : address,
|
identiconAddress: usedIconUrl ? null : address,
|
||||||
balance,
|
balance,
|
||||||
decimals,
|
decimals,
|
||||||
name: name || contractMap[toChecksumHexAddress(address)]?.name,
|
name: name || tokenList[tokenAddress]?.name,
|
||||||
rawFiat,
|
rawFiat,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useTokensToSearch({ usersTokens = [], topTokens = {} }) {
|
export function useTokensToSearch({
|
||||||
|
usersTokens = [],
|
||||||
|
topTokens = {},
|
||||||
|
shuffledTokensList,
|
||||||
|
}) {
|
||||||
const chainId = useSelector(getCurrentChainId);
|
const chainId = useSelector(getCurrentChainId);
|
||||||
const tokenConversionRates = useSelector(getTokenExchangeRates, isEqual);
|
const tokenConversionRates = useSelector(getTokenExchangeRates, isEqual);
|
||||||
const conversionRate = useSelector(getConversionRate);
|
const conversionRate = useSelector(getConversionRate);
|
||||||
const currentCurrency = useSelector(getCurrentCurrency);
|
const currentCurrency = useSelector(getCurrentCurrency);
|
||||||
const defaultSwapsToken = useSelector(getSwapsDefaultToken);
|
const defaultSwapsToken = useSelector(getSwapsDefaultToken);
|
||||||
|
const tokenList = useSelector(getTokenList);
|
||||||
|
const useTokenDetection = useSelector(getUseTokenDetection);
|
||||||
|
// token from dynamic api list is fetched when useTokenDetection is true
|
||||||
|
const shuffledTokenList = useTokenDetection
|
||||||
|
? shuffledTokensList
|
||||||
|
: shuffledContractMap;
|
||||||
const memoizedTopTokens = useEqualityCheck(topTokens);
|
const memoizedTopTokens = useEqualityCheck(topTokens);
|
||||||
const memoizedUsersToken = useEqualityCheck(usersTokens);
|
const memoizedUsersToken = useEqualityCheck(usersTokens);
|
||||||
|
|
||||||
@ -93,6 +109,8 @@ export function useTokensToSearch({ usersTokens = [], topTokens = {} }) {
|
|||||||
conversionRate,
|
conversionRate,
|
||||||
currentCurrency,
|
currentCurrency,
|
||||||
chainId,
|
chainId,
|
||||||
|
tokenList,
|
||||||
|
useTokenDetection,
|
||||||
);
|
);
|
||||||
const memoizedDefaultToken = useEqualityCheck(defaultToken);
|
const memoizedDefaultToken = useEqualityCheck(defaultToken);
|
||||||
|
|
||||||
@ -102,7 +120,7 @@ export function useTokensToSearch({ usersTokens = [], topTokens = {} }) {
|
|||||||
? swapsTokens
|
? swapsTokens
|
||||||
: [
|
: [
|
||||||
memoizedDefaultToken,
|
memoizedDefaultToken,
|
||||||
...tokenList.filter(
|
...shuffledTokenList.filter(
|
||||||
(token) => token.symbol !== memoizedDefaultToken.symbol,
|
(token) => token.symbol !== memoizedDefaultToken.symbol,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
@ -132,6 +150,8 @@ export function useTokensToSearch({ usersTokens = [], topTokens = {} }) {
|
|||||||
conversionRate,
|
conversionRate,
|
||||||
currentCurrency,
|
currentCurrency,
|
||||||
chainId,
|
chainId,
|
||||||
|
tokenList,
|
||||||
|
useTokenDetection,
|
||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
isSwapsDefaultTokenSymbol(renderableDataToken.symbol, chainId) ||
|
isSwapsDefaultTokenSymbol(renderableDataToken.symbol, chainId) ||
|
||||||
@ -166,5 +186,7 @@ export function useTokensToSearch({ usersTokens = [], topTokens = {} }) {
|
|||||||
currentCurrency,
|
currentCurrency,
|
||||||
memoizedTopTokens,
|
memoizedTopTokens,
|
||||||
chainId,
|
chainId,
|
||||||
|
tokenList,
|
||||||
|
useTokenDetection,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,11 @@ class AddToken extends Component {
|
|||||||
mostRecentOverviewPage: PropTypes.string.isRequired,
|
mostRecentOverviewPage: PropTypes.string.isRequired,
|
||||||
chainId: PropTypes.string,
|
chainId: PropTypes.string,
|
||||||
rpcPrefs: PropTypes.object,
|
rpcPrefs: PropTypes.object,
|
||||||
|
tokenList: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
tokenList: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -140,7 +145,10 @@ class AddToken extends Component {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { setPendingTokens, history } = this.props;
|
const { setPendingTokens, history, tokenList } = this.props;
|
||||||
|
const tokenAddressList = Object.keys(tokenList).map((address) =>
|
||||||
|
address.toLowerCase(),
|
||||||
|
);
|
||||||
const {
|
const {
|
||||||
customAddress: address,
|
customAddress: address,
|
||||||
customSymbol: symbol,
|
customSymbol: symbol,
|
||||||
@ -154,12 +162,16 @@ class AddToken extends Component {
|
|||||||
decimals,
|
decimals,
|
||||||
};
|
};
|
||||||
|
|
||||||
setPendingTokens({ customToken, selectedTokens });
|
setPendingTokens({ customToken, selectedTokens, tokenAddressList });
|
||||||
history.push(CONFIRM_ADD_TOKEN_ROUTE);
|
history.push(CONFIRM_ADD_TOKEN_ROUTE);
|
||||||
}
|
}
|
||||||
|
|
||||||
async attemptToAutoFillTokenParams(address) {
|
async attemptToAutoFillTokenParams(address) {
|
||||||
const { symbol = '', decimals } = await this.tokenInfoGetter(address);
|
const { tokenList } = this.props;
|
||||||
|
const { symbol = '', decimals } = await this.tokenInfoGetter(
|
||||||
|
address,
|
||||||
|
tokenList,
|
||||||
|
);
|
||||||
|
|
||||||
const symbolAutoFilled = Boolean(symbol);
|
const symbolAutoFilled = Boolean(symbol);
|
||||||
const decimalAutoFilled = Boolean(decimals);
|
const decimalAutoFilled = Boolean(decimals);
|
||||||
@ -358,8 +370,8 @@ class AddToken extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderSearchToken() {
|
renderSearchToken() {
|
||||||
|
const { tokenList } = this.props;
|
||||||
const { tokenSelectorError, selectedTokens, searchResults } = this.state;
|
const { tokenSelectorError, selectedTokens, searchResults } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="add-token__search-token">
|
<div className="add-token__search-token">
|
||||||
<TokenSearch
|
<TokenSearch
|
||||||
@ -367,6 +379,7 @@ class AddToken extends Component {
|
|||||||
this.setState({ searchResults: results })
|
this.setState({ searchResults: results })
|
||||||
}
|
}
|
||||||
error={tokenSelectorError}
|
error={tokenSelectorError}
|
||||||
|
tokenList={tokenList}
|
||||||
/>
|
/>
|
||||||
<div className="add-token__token-list">
|
<div className="add-token__token-list">
|
||||||
<TokenList
|
<TokenList
|
||||||
|
@ -5,6 +5,7 @@ import { getMostRecentOverviewPage } from '../../ducks/history/history';
|
|||||||
import {
|
import {
|
||||||
getIsMainnet,
|
getIsMainnet,
|
||||||
getRpcPrefsForCurrentProvider,
|
getRpcPrefsForCurrentProvider,
|
||||||
|
getTokenList,
|
||||||
} from '../../selectors/selectors';
|
} from '../../selectors/selectors';
|
||||||
import AddToken from './add-token.component';
|
import AddToken from './add-token.component';
|
||||||
|
|
||||||
@ -25,6 +26,7 @@ const mapStateToProps = (state) => {
|
|||||||
showSearchTab: getIsMainnet(state) || process.env.IN_TEST === 'true',
|
showSearchTab: getIsMainnet(state) || process.env.IN_TEST === 'true',
|
||||||
chainId,
|
chainId,
|
||||||
rpcPrefs: getRpcPrefsForCurrentProvider(state),
|
rpcPrefs: getRpcPrefsForCurrentProvider(state),
|
||||||
|
tokenList: getTokenList(state),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ describe('Add Token', () => {
|
|||||||
identities: {},
|
identities: {},
|
||||||
mostRecentOverviewPage: '/',
|
mostRecentOverviewPage: '/',
|
||||||
showSearchTab: true,
|
showSearchTab: true,
|
||||||
|
tokenList: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('Add Token', () => {
|
describe('Add Token', () => {
|
||||||
|
@ -14,6 +14,7 @@ export default class TokenList extends Component {
|
|||||||
results: PropTypes.array,
|
results: PropTypes.array,
|
||||||
selectedTokens: PropTypes.object,
|
selectedTokens: PropTypes.object,
|
||||||
onToggleToken: PropTypes.func,
|
onToggleToken: PropTypes.func,
|
||||||
|
useTokenDetection: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -22,6 +23,7 @@ export default class TokenList extends Component {
|
|||||||
selectedTokens = {},
|
selectedTokens = {},
|
||||||
onToggleToken,
|
onToggleToken,
|
||||||
tokens = [],
|
tokens = [],
|
||||||
|
useTokenDetection,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return results.length === 0 ? (
|
return results.length === 0 ? (
|
||||||
@ -35,13 +37,17 @@ export default class TokenList extends Component {
|
|||||||
{Array(6)
|
{Array(6)
|
||||||
.fill(undefined)
|
.fill(undefined)
|
||||||
.map((_, i) => {
|
.map((_, i) => {
|
||||||
const { logo, symbol, name, address } = results[i] || {};
|
const { iconUrl, symbol, name, address } = results[i] || {};
|
||||||
|
// token from dynamic api list is fetched when useTokenDetection is true
|
||||||
|
const iconPath = useTokenDetection
|
||||||
|
? iconUrl
|
||||||
|
: `images/contract/${iconUrl}`;
|
||||||
const tokenAlreadyAdded = checkExistingAddresses(address, tokens);
|
const tokenAlreadyAdded = checkExistingAddresses(address, tokens);
|
||||||
const onClick = () =>
|
const onClick = () =>
|
||||||
!tokenAlreadyAdded && onToggleToken(results[i]);
|
!tokenAlreadyAdded && onToggleToken(results[i]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
Boolean(logo || symbol || name) && (
|
Boolean(iconUrl || symbol || name) && (
|
||||||
<div
|
<div
|
||||||
className={classnames('token-list__token', {
|
className={classnames('token-list__token', {
|
||||||
'token-list__token--selected': selectedTokens[address],
|
'token-list__token--selected': selectedTokens[address],
|
||||||
@ -55,7 +61,7 @@ export default class TokenList extends Component {
|
|||||||
<div
|
<div
|
||||||
className="token-list__token-icon"
|
className="token-list__token-icon"
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: logo && `url(images/contract/${logo})`,
|
backgroundImage: iconUrl && `url(${iconPath})`,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="token-list__token-data">
|
<div className="token-list__token-data">
|
||||||
|
@ -2,9 +2,10 @@ import { connect } from 'react-redux';
|
|||||||
import TokenList from './token-list.component';
|
import TokenList from './token-list.component';
|
||||||
|
|
||||||
const mapStateToProps = ({ metamask }) => {
|
const mapStateToProps = ({ metamask }) => {
|
||||||
const { tokens } = metamask;
|
const { tokens, useTokenDetection } = metamask;
|
||||||
return {
|
return {
|
||||||
tokens,
|
tokens,
|
||||||
|
useTokenDetection,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,26 +1,9 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import contractMap from '@metamask/contract-metadata';
|
|
||||||
import Fuse from 'fuse.js';
|
import Fuse from 'fuse.js';
|
||||||
import InputAdornment from '@material-ui/core/InputAdornment';
|
import InputAdornment from '@material-ui/core/InputAdornment';
|
||||||
import TextField from '../../../components/ui/text-field';
|
import TextField from '../../../components/ui/text-field';
|
||||||
|
import { isEqualCaseInsensitive } from '../../../helpers/utils/util';
|
||||||
const contractList = Object.entries(contractMap)
|
|
||||||
.map(([address, tokenData]) => ({ ...tokenData, address }))
|
|
||||||
.filter((tokenData) => Boolean(tokenData.erc20));
|
|
||||||
|
|
||||||
const fuse = new Fuse(contractList, {
|
|
||||||
shouldSort: true,
|
|
||||||
threshold: 0.45,
|
|
||||||
location: 0,
|
|
||||||
distance: 100,
|
|
||||||
maxPatternLength: 32,
|
|
||||||
minMatchCharLength: 1,
|
|
||||||
keys: [
|
|
||||||
{ name: 'name', weight: 0.5 },
|
|
||||||
{ name: 'symbol', weight: 0.5 },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
export default class TokenSearch extends Component {
|
export default class TokenSearch extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
@ -34,17 +17,40 @@ export default class TokenSearch extends Component {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
onSearch: PropTypes.func,
|
onSearch: PropTypes.func,
|
||||||
error: PropTypes.string,
|
error: PropTypes.string,
|
||||||
|
tokenList: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
searchQuery: '',
|
searchQuery: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
const { tokenList } = this.props;
|
||||||
|
this.tokenList = Object.values(tokenList);
|
||||||
|
this.tokenSearchFuse = new Fuse(this.tokenList, {
|
||||||
|
shouldSort: true,
|
||||||
|
threshold: 0.45,
|
||||||
|
location: 0,
|
||||||
|
distance: 100,
|
||||||
|
maxPatternLength: 32,
|
||||||
|
minMatchCharLength: 1,
|
||||||
|
keys: [
|
||||||
|
{ name: 'name', weight: 0.5 },
|
||||||
|
{ name: 'symbol', weight: 0.5 },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
handleSearch(searchQuery) {
|
handleSearch(searchQuery) {
|
||||||
this.setState({ searchQuery });
|
this.setState({ searchQuery });
|
||||||
const fuseSearchResult = fuse.search(searchQuery);
|
const fuseSearchResult = this.tokenSearchFuse.search(searchQuery);
|
||||||
const addressSearchResult = contractList.filter((token) => {
|
const addressSearchResult = this.tokenList.filter((token) => {
|
||||||
return token.address.toLowerCase() === searchQuery.toLowerCase();
|
return (
|
||||||
|
token.address &&
|
||||||
|
searchQuery &&
|
||||||
|
isEqualCaseInsensitive(token.address, searchQuery)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
const results = [...addressSearchResult, ...fuseSearchResult];
|
const results = [...addressSearchResult, ...fuseSearchResult];
|
||||||
this.props.onSearch({ searchQuery, results });
|
this.props.onSearch({ searchQuery, results });
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { compose } from 'redux';
|
import { compose } from 'redux';
|
||||||
import { withRouter } from 'react-router-dom';
|
import { withRouter } from 'react-router-dom';
|
||||||
import contractMap from '@metamask/contract-metadata';
|
|
||||||
import { clearConfirmTransaction } from '../../ducks/confirm-transaction/confirm-transaction.duck';
|
import { clearConfirmTransaction } from '../../ducks/confirm-transaction/confirm-transaction.duck';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -30,6 +29,8 @@ import {
|
|||||||
checkNetworkAndAccountSupports1559,
|
checkNetworkAndAccountSupports1559,
|
||||||
getPreferences,
|
getPreferences,
|
||||||
getAccountType,
|
getAccountType,
|
||||||
|
getUseTokenDetection,
|
||||||
|
getTokenList,
|
||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
import { getMostRecentOverviewPage } from '../../ducks/history/history';
|
import { getMostRecentOverviewPage } from '../../ducks/history/history';
|
||||||
import {
|
import {
|
||||||
@ -47,13 +48,6 @@ import {
|
|||||||
import { getGasLoadingAnimationIsShowing } from '../../ducks/app/app';
|
import { getGasLoadingAnimationIsShowing } from '../../ducks/app/app';
|
||||||
import ConfirmTransactionBase from './confirm-transaction-base.component';
|
import ConfirmTransactionBase from './confirm-transaction-base.component';
|
||||||
|
|
||||||
const casedContractMap = Object.keys(contractMap).reduce((acc, base) => {
|
|
||||||
return {
|
|
||||||
...acc,
|
|
||||||
[base.toLowerCase()]: contractMap[base],
|
|
||||||
};
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
let customNonceValue = '';
|
let customNonceValue = '';
|
||||||
const customNonceMerge = (txData) =>
|
const customNonceMerge = (txData) =>
|
||||||
customNonceValue
|
customNonceValue
|
||||||
@ -109,9 +103,19 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
const { name: fromName } = identities[fromAddress];
|
const { name: fromName } = identities[fromAddress];
|
||||||
const toAddress = propsToAddress || txParamsToAddress;
|
const toAddress = propsToAddress || txParamsToAddress;
|
||||||
|
|
||||||
|
const tokenList = getTokenList(state);
|
||||||
|
const useTokenDetection = getUseTokenDetection(state);
|
||||||
|
const casedTokenList = useTokenDetection
|
||||||
|
? tokenList
|
||||||
|
: Object.keys(tokenList).reduce((acc, base) => {
|
||||||
|
return {
|
||||||
|
...acc,
|
||||||
|
[base.toLowerCase()]: tokenList[base],
|
||||||
|
};
|
||||||
|
}, {});
|
||||||
const toName =
|
const toName =
|
||||||
identities[toAddress]?.name ||
|
identities[toAddress]?.name ||
|
||||||
casedContractMap[toAddress]?.name ||
|
casedTokenList[toAddress]?.name ||
|
||||||
shortenAddress(toChecksumHexAddress(toAddress));
|
shortenAddress(toChecksumHexAddress(toAddress));
|
||||||
|
|
||||||
const checksummedAddress = toChecksumHexAddress(toAddress);
|
const checksummedAddress = toChecksumHexAddress(toAddress);
|
||||||
|
@ -36,6 +36,8 @@ import {
|
|||||||
getCurrentCurrency,
|
getCurrentCurrency,
|
||||||
getCurrentChainId,
|
getCurrentChainId,
|
||||||
getRpcPrefsForCurrentProvider,
|
getRpcPrefsForCurrentProvider,
|
||||||
|
getUseTokenDetection,
|
||||||
|
getTokenList,
|
||||||
} from '../../../selectors';
|
} from '../../../selectors';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -83,6 +85,7 @@ export default function BuildQuote({
|
|||||||
selectedAccountAddress,
|
selectedAccountAddress,
|
||||||
isFeatureFlagLoaded,
|
isFeatureFlagLoaded,
|
||||||
tokenFromError,
|
tokenFromError,
|
||||||
|
shuffledTokensList,
|
||||||
}) {
|
}) {
|
||||||
const t = useContext(I18nContext);
|
const t = useContext(I18nContext);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -105,6 +108,8 @@ export default function BuildQuote({
|
|||||||
const defaultSwapsToken = useSelector(getSwapsDefaultToken);
|
const defaultSwapsToken = useSelector(getSwapsDefaultToken);
|
||||||
const chainId = useSelector(getCurrentChainId);
|
const chainId = useSelector(getCurrentChainId);
|
||||||
const rpcPrefs = useSelector(getRpcPrefsForCurrentProvider);
|
const rpcPrefs = useSelector(getRpcPrefsForCurrentProvider);
|
||||||
|
const tokenList = useSelector(getTokenList);
|
||||||
|
const useTokenDetection = useSelector(getUseTokenDetection);
|
||||||
|
|
||||||
const tokenConversionRates = useSelector(getTokenExchangeRates, isEqual);
|
const tokenConversionRates = useSelector(getTokenExchangeRates, isEqual);
|
||||||
const conversionRate = useSelector(getConversionRate);
|
const conversionRate = useSelector(getConversionRate);
|
||||||
@ -138,11 +143,14 @@ export default function BuildQuote({
|
|||||||
conversionRate,
|
conversionRate,
|
||||||
currentCurrency,
|
currentCurrency,
|
||||||
chainId,
|
chainId,
|
||||||
|
tokenList,
|
||||||
|
useTokenDetection,
|
||||||
);
|
);
|
||||||
|
|
||||||
const tokensToSearch = useTokensToSearch({
|
const tokensToSearch = useTokensToSearch({
|
||||||
usersTokens: memoizedUsersTokens,
|
usersTokens: memoizedUsersTokens,
|
||||||
topTokens: topAssets,
|
topTokens: topAssets,
|
||||||
|
shuffledTokensList,
|
||||||
});
|
});
|
||||||
const selectedToToken =
|
const selectedToToken =
|
||||||
tokensToSearch.find(({ address }) => address === toToken?.address) ||
|
tokensToSearch.find(({ address }) => address === toToken?.address) ||
|
||||||
@ -611,4 +619,5 @@ BuildQuote.propTypes = {
|
|||||||
selectedAccountAddress: PropTypes.string,
|
selectedAccountAddress: PropTypes.string,
|
||||||
isFeatureFlagLoaded: PropTypes.bool.isRequired,
|
isFeatureFlagLoaded: PropTypes.bool.isRequired,
|
||||||
tokenFromError: PropTypes.string,
|
tokenFromError: PropTypes.string,
|
||||||
|
shuffledTokensList: PropTypes.array,
|
||||||
};
|
};
|
||||||
|
@ -19,6 +19,7 @@ const createProps = (customProps = {}) => {
|
|||||||
maxSlippage: 15,
|
maxSlippage: 15,
|
||||||
selectedAccountAddress: 'selectedAccountAddress',
|
selectedAccountAddress: 'selectedAccountAddress',
|
||||||
isFeatureFlagLoaded: false,
|
isFeatureFlagLoaded: false,
|
||||||
|
shuffledTokensList: [],
|
||||||
...customProps,
|
...customProps,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
Redirect,
|
Redirect,
|
||||||
} from 'react-router-dom';
|
} from 'react-router-dom';
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
|
import { shuffle } from 'lodash';
|
||||||
import { I18nContext } from '../../contexts/i18n';
|
import { I18nContext } from '../../contexts/i18n';
|
||||||
import {
|
import {
|
||||||
getSelectedAccount,
|
getSelectedAccount,
|
||||||
@ -15,6 +16,7 @@ import {
|
|||||||
getIsSwapsChain,
|
getIsSwapsChain,
|
||||||
isHardwareWallet,
|
isHardwareWallet,
|
||||||
getHardwareWalletType,
|
getHardwareWalletType,
|
||||||
|
getTokenList,
|
||||||
} from '../../selectors/selectors';
|
} from '../../selectors/selectors';
|
||||||
import {
|
import {
|
||||||
getQuotes,
|
getQuotes,
|
||||||
@ -119,6 +121,8 @@ export default function Swap() {
|
|||||||
checkNetworkAndAccountSupports1559,
|
checkNetworkAndAccountSupports1559,
|
||||||
);
|
);
|
||||||
const fromToken = useSelector(getFromToken);
|
const fromToken = useSelector(getFromToken);
|
||||||
|
const tokenList = useSelector(getTokenList);
|
||||||
|
const listTokenValues = shuffle(Object.values(tokenList));
|
||||||
|
|
||||||
if (networkAndAccountSupports1559) {
|
if (networkAndAccountSupports1559) {
|
||||||
// This will pre-load gas fees before going to the View Quote page.
|
// This will pre-load gas fees before going to the View Quote page.
|
||||||
@ -336,6 +340,7 @@ export default function Swap() {
|
|||||||
maxSlippage={maxSlippage}
|
maxSlippage={maxSlippage}
|
||||||
isFeatureFlagLoaded={isFeatureFlagLoaded}
|
isFeatureFlagLoaded={isFeatureFlagLoaded}
|
||||||
tokenFromError={tokenFromError}
|
tokenFromError={tokenFromError}
|
||||||
|
shuffledTokensList={listTokenValues}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
@ -599,3 +599,21 @@ export function getShowRecoveryPhraseReminder(state) {
|
|||||||
|
|
||||||
return currentTime - recoveryPhraseReminderLastShown >= frequency;
|
return currentTime - recoveryPhraseReminderLastShown >= frequency;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To get the useTokenDetection flag which determines whether a static or dynamic token list is used
|
||||||
|
* @param {*} state
|
||||||
|
* @returns Boolean
|
||||||
|
*/
|
||||||
|
export function getUseTokenDetection(state) {
|
||||||
|
return Boolean(state.metamask.useTokenDetection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To retrieve the tokenList produced by TokenListcontroller
|
||||||
|
* @param {*} state
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
export function getTokenList(state) {
|
||||||
|
return state.metamask.tokenList;
|
||||||
|
}
|
||||||
|
@ -129,4 +129,60 @@ describe('Selectors', () => {
|
|||||||
const totalUnapprovedCount = selectors.getTotalUnapprovedCount(mockState);
|
const totalUnapprovedCount = selectors.getTotalUnapprovedCount(mockState);
|
||||||
expect(totalUnapprovedCount).toStrictEqual(1);
|
expect(totalUnapprovedCount).toStrictEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('#getUseTokenDetection', () => {
|
||||||
|
const useTokenDetection = selectors.getUseTokenDetection(mockState);
|
||||||
|
expect(useTokenDetection).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('#getTokenList', () => {
|
||||||
|
const tokenList = selectors.getTokenList(mockState);
|
||||||
|
expect(tokenList).toStrictEqual({
|
||||||
|
'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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
} from '../helpers/utils/i18n-helper';
|
} from '../helpers/utils/i18n-helper';
|
||||||
import { getMethodDataAsync } from '../helpers/utils/transactions.util';
|
import { getMethodDataAsync } from '../helpers/utils/transactions.util';
|
||||||
import { getSymbolAndDecimals } from '../helpers/utils/token-util';
|
import { getSymbolAndDecimals } from '../helpers/utils/token-util';
|
||||||
|
import { isEqualCaseInsensitive } from '../helpers/utils/util';
|
||||||
import switchDirection from '../helpers/utils/switch-direction';
|
import switchDirection from '../helpers/utils/switch-direction';
|
||||||
import {
|
import {
|
||||||
ENVIRONMENT_TYPE_NOTIFICATION,
|
ENVIRONMENT_TYPE_NOTIFICATION,
|
||||||
@ -21,11 +22,11 @@ import {
|
|||||||
getMetaMaskAccounts,
|
getMetaMaskAccounts,
|
||||||
getPermittedAccountsForCurrentTab,
|
getPermittedAccountsForCurrentTab,
|
||||||
getSelectedAddress,
|
getSelectedAddress,
|
||||||
|
getTokenList,
|
||||||
} from '../selectors';
|
} from '../selectors';
|
||||||
import { computeEstimatedGasLimit, resetSendState } from '../ducks/send';
|
import { computeEstimatedGasLimit, resetSendState } from '../ducks/send';
|
||||||
import { switchedToUnconnectedAccount } from '../ducks/alerts/unconnected-account';
|
import { switchedToUnconnectedAccount } from '../ducks/alerts/unconnected-account';
|
||||||
import { getUnconnectedAccountAlertEnabledness } from '../ducks/metamask/metamask';
|
import { getUnconnectedAccountAlertEnabledness } from '../ducks/metamask/metamask';
|
||||||
import { LISTED_CONTRACT_ADDRESSES } from '../../shared/constants/tokens';
|
|
||||||
import { toChecksumHexAddress } from '../../shared/modules/hexstring-utils';
|
import { toChecksumHexAddress } from '../../shared/modules/hexstring-utils';
|
||||||
import * as actionConstants from './actionConstants';
|
import * as actionConstants from './actionConstants';
|
||||||
|
|
||||||
@ -2039,11 +2040,11 @@ export function setUsePhishDetect(val) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setUseStaticTokenList(val) {
|
export function setUseTokenDetection(val) {
|
||||||
return (dispatch) => {
|
return (dispatch) => {
|
||||||
dispatch(showLoadingIndication());
|
dispatch(showLoadingIndication());
|
||||||
log.debug(`background.setUseStaticTokenList`);
|
log.debug(`background.setUseTokenDetection`);
|
||||||
background.setUseStaticTokenList(val, (err) => {
|
background.setUseTokenDetection(val, (err) => {
|
||||||
dispatch(hideLoadingIndication());
|
dispatch(hideLoadingIndication());
|
||||||
if (err) {
|
if (err) {
|
||||||
dispatch(displayWarning(err.message));
|
dispatch(displayWarning(err.message));
|
||||||
@ -2100,7 +2101,11 @@ export function setCurrentLocale(locale, messages) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function setPendingTokens(pendingTokens) {
|
export function setPendingTokens(pendingTokens) {
|
||||||
const { customToken = {}, selectedTokens = {} } = pendingTokens;
|
const {
|
||||||
|
customToken = {},
|
||||||
|
selectedTokens = {},
|
||||||
|
tokenAddressList = [],
|
||||||
|
} = pendingTokens;
|
||||||
const { address, symbol, decimals } = customToken;
|
const { address, symbol, decimals } = customToken;
|
||||||
const tokens =
|
const tokens =
|
||||||
address && symbol && decimals >= 0 <= 36
|
address && symbol && decimals >= 0 <= 36
|
||||||
@ -2114,8 +2119,8 @@ export function setPendingTokens(pendingTokens) {
|
|||||||
: selectedTokens;
|
: selectedTokens;
|
||||||
|
|
||||||
Object.keys(tokens).forEach((tokenAddress) => {
|
Object.keys(tokens).forEach((tokenAddress) => {
|
||||||
tokens[tokenAddress].unlisted = !LISTED_CONTRACT_ADDRESSES.includes(
|
tokens[tokenAddress].unlisted = !tokenAddressList.find((addr) =>
|
||||||
tokenAddress.toLowerCase(),
|
isEqualCaseInsensitive(addr, tokenAddress),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -2522,6 +2527,7 @@ export function loadingTokenParamsFinished() {
|
|||||||
|
|
||||||
export function getTokenParams(tokenAddress) {
|
export function getTokenParams(tokenAddress) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
|
const tokenList = getTokenList(getState());
|
||||||
const existingTokens = getState().metamask.tokens;
|
const existingTokens = getState().metamask.tokens;
|
||||||
const existingToken = existingTokens.find(
|
const existingToken = existingTokens.find(
|
||||||
({ address }) => tokenAddress === address,
|
({ address }) => tokenAddress === address,
|
||||||
@ -2537,10 +2543,12 @@ export function getTokenParams(tokenAddress) {
|
|||||||
dispatch(loadingTokenParamsStarted());
|
dispatch(loadingTokenParamsStarted());
|
||||||
log.debug(`loadingTokenParams`);
|
log.debug(`loadingTokenParams`);
|
||||||
|
|
||||||
return getSymbolAndDecimals(tokenAddress).then(({ symbol, decimals }) => {
|
return getSymbolAndDecimals(tokenAddress, tokenList).then(
|
||||||
dispatch(addToken(tokenAddress, symbol, Number(decimals)));
|
({ symbol, decimals }) => {
|
||||||
dispatch(loadingTokenParamsFinished());
|
dispatch(addToken(tokenAddress, symbol, Number(decimals)));
|
||||||
});
|
dispatch(loadingTokenParamsFinished());
|
||||||
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user