mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Add hidden tokens to store (#9320)
From a behavioral standpoint this PR fixes the issue with tracking, and persisting, tokens that the user hides. Whether we can/should optimize this to prevent duplicates of the accountHiddenTokens and hiddenToken is a point of contention, but it acts similiarly to how we track tokens and accountTokens. Also to note, for tokens under a custom network there is no way to distinguish two different custom network sets of hidden tokens, they are all under the `rpc` property, same as accountTokens.
This commit is contained in:
parent
3bf94164ac
commit
e4a77e1dc3
@ -47,7 +47,8 @@ export default class DetectTokensController {
|
|||||||
for (const contractAddress in contracts) {
|
for (const contractAddress in contracts) {
|
||||||
if (
|
if (
|
||||||
contracts[contractAddress].erc20 &&
|
contracts[contractAddress].erc20 &&
|
||||||
!this.tokenAddresses.includes(contractAddress.toLowerCase())
|
!this.tokenAddresses.includes(contractAddress.toLowerCase()) &&
|
||||||
|
!this.hiddenTokens.includes(contractAddress.toLowerCase())
|
||||||
) {
|
) {
|
||||||
tokensToDetect.push(contractAddress)
|
tokensToDetect.push(contractAddress)
|
||||||
}
|
}
|
||||||
@ -130,10 +131,12 @@ export default class DetectTokensController {
|
|||||||
this.tokenAddresses = currentTokens
|
this.tokenAddresses = currentTokens
|
||||||
? currentTokens.map((token) => token.address)
|
? currentTokens.map((token) => token.address)
|
||||||
: []
|
: []
|
||||||
preferences.store.subscribe(({ tokens = [] }) => {
|
this.hiddenTokens = preferences.store.getState().hiddenTokens
|
||||||
|
preferences.store.subscribe(({ tokens = [], hiddenTokens = [] }) => {
|
||||||
this.tokenAddresses = tokens.map((token) => {
|
this.tokenAddresses = tokens.map((token) => {
|
||||||
return token.address
|
return token.address
|
||||||
})
|
})
|
||||||
|
this.hiddenTokens = hiddenTokens
|
||||||
})
|
})
|
||||||
preferences.store.subscribe(({ selectedAddress }) => {
|
preferences.store.subscribe(({ selectedAddress }) => {
|
||||||
if (this.selectedAddress !== selectedAddress) {
|
if (this.selectedAddress !== selectedAddress) {
|
||||||
|
@ -34,8 +34,10 @@ export default class PreferencesController {
|
|||||||
const initState = {
|
const initState = {
|
||||||
frequentRpcListDetail: [],
|
frequentRpcListDetail: [],
|
||||||
accountTokens: {},
|
accountTokens: {},
|
||||||
|
accountHiddenTokens: {},
|
||||||
assetImages: {},
|
assetImages: {},
|
||||||
tokens: [],
|
tokens: [],
|
||||||
|
hiddenTokens: [],
|
||||||
suggestedTokens: {},
|
suggestedTokens: {},
|
||||||
useBlockie: false,
|
useBlockie: false,
|
||||||
useNonceField: false,
|
useNonceField: false,
|
||||||
@ -191,6 +193,7 @@ export default class PreferencesController {
|
|||||||
setAddresses(addresses) {
|
setAddresses(addresses) {
|
||||||
const oldIdentities = this.store.getState().identities
|
const oldIdentities = this.store.getState().identities
|
||||||
const oldAccountTokens = this.store.getState().accountTokens
|
const oldAccountTokens = this.store.getState().accountTokens
|
||||||
|
const oldAccountHiddenTokens = this.store.getState().accountHiddenTokens
|
||||||
|
|
||||||
const identities = addresses.reduce((ids, address, index) => {
|
const identities = addresses.reduce((ids, address, index) => {
|
||||||
const oldId = oldIdentities[address] || {}
|
const oldId = oldIdentities[address] || {}
|
||||||
@ -202,7 +205,12 @@ export default class PreferencesController {
|
|||||||
tokens[address] = oldTokens
|
tokens[address] = oldTokens
|
||||||
return tokens
|
return tokens
|
||||||
}, {})
|
}, {})
|
||||||
this.store.updateState({ identities, accountTokens })
|
const accountHiddenTokens = addresses.reduce((hiddenTokens, address) => {
|
||||||
|
const oldHiddenTokens = oldAccountHiddenTokens[address] || {}
|
||||||
|
hiddenTokens[address] = oldHiddenTokens
|
||||||
|
return hiddenTokens
|
||||||
|
}, {})
|
||||||
|
this.store.updateState({ identities, accountTokens, accountHiddenTokens })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -212,14 +220,19 @@ export default class PreferencesController {
|
|||||||
* @returns {string} the address that was removed
|
* @returns {string} the address that was removed
|
||||||
*/
|
*/
|
||||||
removeAddress(address) {
|
removeAddress(address) {
|
||||||
const { identities } = this.store.getState()
|
const {
|
||||||
const { accountTokens } = this.store.getState()
|
identities,
|
||||||
|
accountTokens,
|
||||||
|
accountHiddenTokens,
|
||||||
|
} = this.store.getState()
|
||||||
|
|
||||||
if (!identities[address]) {
|
if (!identities[address]) {
|
||||||
throw new Error(`${address} can't be deleted cause it was not found`)
|
throw new Error(`${address} can't be deleted cause it was not found`)
|
||||||
}
|
}
|
||||||
delete identities[address]
|
delete identities[address]
|
||||||
delete accountTokens[address]
|
delete accountTokens[address]
|
||||||
this.store.updateState({ identities, accountTokens })
|
delete accountHiddenTokens[address]
|
||||||
|
this.store.updateState({ identities, accountTokens, accountHiddenTokens })
|
||||||
|
|
||||||
// If the selected account is no longer valid,
|
// If the selected account is no longer valid,
|
||||||
// select an arbitrary other account:
|
// select an arbitrary other account:
|
||||||
@ -237,7 +250,11 @@ export default class PreferencesController {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
addAddresses(addresses) {
|
addAddresses(addresses) {
|
||||||
const { identities, accountTokens } = this.store.getState()
|
const {
|
||||||
|
identities,
|
||||||
|
accountTokens,
|
||||||
|
accountHiddenTokens,
|
||||||
|
} = this.store.getState()
|
||||||
addresses.forEach((address) => {
|
addresses.forEach((address) => {
|
||||||
// skip if already exists
|
// skip if already exists
|
||||||
if (identities[address]) {
|
if (identities[address]) {
|
||||||
@ -247,9 +264,10 @@ export default class PreferencesController {
|
|||||||
const identityCount = Object.keys(identities).length
|
const identityCount = Object.keys(identities).length
|
||||||
|
|
||||||
accountTokens[address] = {}
|
accountTokens[address] = {}
|
||||||
|
accountHiddenTokens[address] = {}
|
||||||
identities[address] = { name: `Account ${identityCount + 1}`, address }
|
identities[address] = { name: `Account ${identityCount + 1}`, address }
|
||||||
})
|
})
|
||||||
this.store.updateState({ identities, accountTokens })
|
this.store.updateState({ identities, accountTokens, accountHiddenTokens })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -346,7 +364,7 @@ export default class PreferencesController {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a new token to the token array, or updates the token if passed an address that already exists.
|
* Adds a new token to the token array and removes it from the hiddenToken array, or updates the token if passed an address that already exists.
|
||||||
* Modifies the existing tokens array from the store. All objects in the tokens array array AddedToken objects.
|
* Modifies the existing tokens array from the store. All objects in the tokens array array AddedToken objects.
|
||||||
* @see AddedToken {@link AddedToken}
|
* @see AddedToken {@link AddedToken}
|
||||||
*
|
*
|
||||||
@ -359,8 +377,11 @@ export default class PreferencesController {
|
|||||||
async addToken(rawAddress, symbol, decimals, image) {
|
async addToken(rawAddress, symbol, decimals, image) {
|
||||||
const address = normalizeAddress(rawAddress)
|
const address = normalizeAddress(rawAddress)
|
||||||
const newEntry = { address, symbol, decimals }
|
const newEntry = { address, symbol, decimals }
|
||||||
const { tokens } = this.store.getState()
|
const { tokens, hiddenTokens } = this.store.getState()
|
||||||
const assetImages = this.getAssetImages()
|
const assetImages = this.getAssetImages()
|
||||||
|
const updatedHiddenTokens = hiddenTokens.filter(
|
||||||
|
(tokenAddress) => tokenAddress !== rawAddress.toLowerCase(),
|
||||||
|
)
|
||||||
const previousEntry = tokens.find((token) => {
|
const previousEntry = tokens.find((token) => {
|
||||||
return token.address === address
|
return token.address === address
|
||||||
})
|
})
|
||||||
@ -372,23 +393,24 @@ export default class PreferencesController {
|
|||||||
tokens.push(newEntry)
|
tokens.push(newEntry)
|
||||||
}
|
}
|
||||||
assetImages[address] = image
|
assetImages[address] = image
|
||||||
this._updateAccountTokens(tokens, assetImages)
|
this._updateAccountTokens(tokens, assetImages, updatedHiddenTokens)
|
||||||
return Promise.resolve(tokens)
|
return Promise.resolve(tokens)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a specified token from the tokens array.
|
* Removes a specified token from the tokens array and adds it to hiddenTokens array
|
||||||
*
|
*
|
||||||
* @param {string} rawAddress - Hex address of the token contract to remove.
|
* @param {string} rawAddress - Hex address of the token contract to remove.
|
||||||
* @returns {Promise<array>} The new array of AddedToken objects
|
* @returns {Promise<array>} The new array of AddedToken objects
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
removeToken(rawAddress) {
|
removeToken(rawAddress) {
|
||||||
const { tokens } = this.store.getState()
|
const { tokens, hiddenTokens } = this.store.getState()
|
||||||
const assetImages = this.getAssetImages()
|
const assetImages = this.getAssetImages()
|
||||||
const updatedTokens = tokens.filter((token) => token.address !== rawAddress)
|
const updatedTokens = tokens.filter((token) => token.address !== rawAddress)
|
||||||
|
const updatedHiddenTokens = [...hiddenTokens, rawAddress.toLowerCase()]
|
||||||
delete assetImages[rawAddress]
|
delete assetImages[rawAddress]
|
||||||
this._updateAccountTokens(updatedTokens, assetImages)
|
this._updateAccountTokens(updatedTokens, assetImages, updatedHiddenTokens)
|
||||||
return Promise.resolve(updatedTokens)
|
return Promise.resolve(updatedTokens)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -643,47 +665,59 @@ export default class PreferencesController {
|
|||||||
*/
|
*/
|
||||||
_subscribeProviderType() {
|
_subscribeProviderType() {
|
||||||
this.network.providerStore.subscribe(() => {
|
this.network.providerStore.subscribe(() => {
|
||||||
const { tokens } = this._getTokenRelatedStates()
|
const { tokens, hiddenTokens } = this._getTokenRelatedStates()
|
||||||
this.store.updateState({ tokens })
|
this._updateAccountTokens(tokens, this.getAssetImages(), hiddenTokens)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates `accountTokens` and `tokens` of current account and network according to it.
|
* Updates `accountTokens`, `tokens`, `accountHiddenTokens` and `hiddenTokens` of current account and network according to it.
|
||||||
*
|
*
|
||||||
* @param {Array} tokens - Array of tokens to be updated.
|
* @param {array} tokens - Array of tokens to be updated.
|
||||||
|
* @param {array} assetImages - Array of assets objects related to assets added
|
||||||
|
* @param {array} hiddenTokens - Array of tokens hidden by user
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
_updateAccountTokens(tokens, assetImages) {
|
_updateAccountTokens(tokens, assetImages, hiddenTokens) {
|
||||||
const {
|
const {
|
||||||
accountTokens,
|
accountTokens,
|
||||||
providerType,
|
providerType,
|
||||||
selectedAddress,
|
selectedAddress,
|
||||||
|
accountHiddenTokens,
|
||||||
} = this._getTokenRelatedStates()
|
} = this._getTokenRelatedStates()
|
||||||
accountTokens[selectedAddress][providerType] = tokens
|
accountTokens[selectedAddress][providerType] = tokens
|
||||||
this.store.updateState({ accountTokens, tokens, assetImages })
|
accountHiddenTokens[selectedAddress][providerType] = hiddenTokens
|
||||||
|
this.store.updateState({
|
||||||
|
accountTokens,
|
||||||
|
tokens,
|
||||||
|
assetImages,
|
||||||
|
accountHiddenTokens,
|
||||||
|
hiddenTokens,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates `tokens` of current account and network.
|
* Updates `tokens` and `hiddenTokens` of current account and network.
|
||||||
*
|
*
|
||||||
* @param {string} selectedAddress - Account address to be updated with.
|
* @param {string} selectedAddress - Account address to be updated with.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
_updateTokens(selectedAddress) {
|
_updateTokens(selectedAddress) {
|
||||||
const { tokens } = this._getTokenRelatedStates(selectedAddress)
|
const { tokens, hiddenTokens } = this._getTokenRelatedStates(
|
||||||
this.store.updateState({ tokens })
|
selectedAddress,
|
||||||
|
)
|
||||||
|
this.store.updateState({ tokens, hiddenTokens })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A getter for `tokens` and `accountTokens` related states.
|
* A getter for `tokens`, `accountTokens`, `hiddenTokens` and `accountHiddenTokens` related states.
|
||||||
*
|
*
|
||||||
* @param {string} [selectedAddress] - A new hex address for an account
|
* @param {string} [selectedAddress] - A new hex address for an account
|
||||||
* @returns {Object.<array, object, string, string>} States to interact with tokens in `accountTokens`
|
* @returns {Object.<array, object, string, string>} States to interact with tokens in `accountTokens`
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
_getTokenRelatedStates(selectedAddress) {
|
_getTokenRelatedStates(selectedAddress) {
|
||||||
const { accountTokens } = this.store.getState()
|
const { accountTokens, accountHiddenTokens } = this.store.getState()
|
||||||
if (!selectedAddress) {
|
if (!selectedAddress) {
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
selectedAddress = this.store.getState().selectedAddress
|
selectedAddress = this.store.getState().selectedAddress
|
||||||
@ -692,11 +726,25 @@ export default class PreferencesController {
|
|||||||
if (!(selectedAddress in accountTokens)) {
|
if (!(selectedAddress in accountTokens)) {
|
||||||
accountTokens[selectedAddress] = {}
|
accountTokens[selectedAddress] = {}
|
||||||
}
|
}
|
||||||
|
if (!(selectedAddress in accountHiddenTokens)) {
|
||||||
|
accountHiddenTokens[selectedAddress] = {}
|
||||||
|
}
|
||||||
if (!(providerType in accountTokens[selectedAddress])) {
|
if (!(providerType in accountTokens[selectedAddress])) {
|
||||||
accountTokens[selectedAddress][providerType] = []
|
accountTokens[selectedAddress][providerType] = []
|
||||||
}
|
}
|
||||||
|
if (!(providerType in accountHiddenTokens[selectedAddress])) {
|
||||||
|
accountHiddenTokens[selectedAddress][providerType] = []
|
||||||
|
}
|
||||||
const tokens = accountTokens[selectedAddress][providerType]
|
const tokens = accountTokens[selectedAddress][providerType]
|
||||||
return { tokens, accountTokens, providerType, selectedAddress }
|
const hiddenTokens = accountHiddenTokens[selectedAddress][providerType]
|
||||||
|
return {
|
||||||
|
tokens,
|
||||||
|
accountTokens,
|
||||||
|
hiddenTokens,
|
||||||
|
accountHiddenTokens,
|
||||||
|
providerType,
|
||||||
|
selectedAddress,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -85,6 +85,53 @@ describe('DetectTokensController', function () {
|
|||||||
sandbox.assert.notCalled(stub)
|
sandbox.assert.notCalled(stub)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should skip adding tokens listed in hiddenTokens array', async function () {
|
||||||
|
sandbox.useFakeTimers()
|
||||||
|
network.setProviderType(MAINNET)
|
||||||
|
const controller = new DetectTokensController({
|
||||||
|
preferences,
|
||||||
|
network,
|
||||||
|
keyringMemStore,
|
||||||
|
})
|
||||||
|
controller.isOpen = true
|
||||||
|
controller.isUnlocked = true
|
||||||
|
|
||||||
|
const contractAddresses = Object.keys(contracts)
|
||||||
|
const erc20ContractAddresses = contractAddresses.filter(
|
||||||
|
(contractAddress) => contracts[contractAddress].erc20 === true,
|
||||||
|
)
|
||||||
|
|
||||||
|
const existingTokenAddress = erc20ContractAddresses[0]
|
||||||
|
const existingToken = contracts[existingTokenAddress]
|
||||||
|
await preferences.addToken(
|
||||||
|
existingTokenAddress,
|
||||||
|
existingToken.symbol,
|
||||||
|
existingToken.decimals,
|
||||||
|
)
|
||||||
|
|
||||||
|
const tokenAddressToSkip = erc20ContractAddresses[1]
|
||||||
|
|
||||||
|
sandbox
|
||||||
|
.stub(controller, '_getTokenBalances')
|
||||||
|
.callsFake((tokensToDetect) =>
|
||||||
|
tokensToDetect.map((token) =>
|
||||||
|
token === tokenAddressToSkip ? new BigNumber(10) : 0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
await preferences.removeToken(tokenAddressToSkip)
|
||||||
|
|
||||||
|
await controller.detectNewTokens()
|
||||||
|
|
||||||
|
assert.deepEqual(preferences.store.getState().tokens, [
|
||||||
|
{
|
||||||
|
address: existingTokenAddress.toLowerCase(),
|
||||||
|
decimals: existingToken.decimals,
|
||||||
|
symbol: existingToken.symbol,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
it('should check and add tokens while on main network', async function () {
|
it('should check and add tokens while on main network', async function () {
|
||||||
sandbox.useFakeTimers()
|
sandbox.useFakeTimers()
|
||||||
network.setProviderType(MAINNET)
|
network.setProviderType(MAINNET)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user