Feature #224: Activate tokens automatically (#300)

* Replace 'Manage Tokens' with 'Manage List'

* prevent 301 redirects

* Add `BLACKLISTED_TOKENS` key to persist through immortal

* Add store/action to extract _activate tokens by its balance_

- keeps already activated tokens
- discards blacklisted tokens
- adds tokens whose vales are bigger than zero and are not blacklisted

* Add `blacklistedTokens` list to safe's store

* Display activeTokensByBalance in 'Balances' screen

* Enable token's blacklisting functionality in Tokens List

* Retrieve balance from API

* Rename action to `activateTokensByBalance`

* Fix linting errors

- line too long
- required return

* Do not persist a separate list into `BLACKLISTED_TOKENS`
This commit is contained in:
Fernando 2019-12-05 06:18:07 -03:00 committed by Mikhail Mikheev
parent 85ff11796e
commit 21b7a59f20
18 changed files with 202 additions and 30 deletions

View File

@ -0,0 +1,16 @@
// @flow
import axios from 'axios'
import { getTxServiceHost } from '~/config/index'
const fetchTokenBalanceList = (safeAddress: string) => {
const apiUrl = getTxServiceHost()
const url = `${apiUrl}safes/${safeAddress}/balances/`
return axios.get(url, {
params: {
limit: 300,
},
})
}
export default fetchTokenBalanceList

View File

@ -4,7 +4,7 @@ import { getRelayUrl } from '~/config/index'
const fetchTokenList = () => { const fetchTokenList = () => {
const apiUrl = getRelayUrl() const apiUrl = getRelayUrl()
const url = `${apiUrl}/tokens` const url = `${apiUrl}tokens/`
return axios.get(url, { return axios.get(url, {
params: { params: {

View File

@ -0,0 +1,49 @@
// @flow
import type { Dispatch as ReduxDispatch } from 'redux'
import { Set } from 'immutable'
import { type GetState, type GlobalState } from '~/store'
import updateActiveTokens from '~/routes/safe/store/actions/updateActiveTokens'
import {
safeActiveTokensSelectorBySafe,
safeBlacklistedTokensSelectorBySafe,
safesMapSelector,
} from '~/routes/safe/store/selectors'
import fetchTokenBalanceList from '~/logic/tokens/api/fetchTokenBalanceList'
import updateSafe from '~/routes/safe/store/actions/updateSafe'
const activateTokensByBalance = (safeAddress: string) => async (
dispatch: ReduxDispatch<GlobalState>,
getState: GetState,
) => {
try {
const result = await fetchTokenBalanceList(safeAddress)
const safes = safesMapSelector(getState())
const alreadyActiveTokens = safeActiveTokensSelectorBySafe(safeAddress, safes)
const blacklistedTokens = safeBlacklistedTokensSelectorBySafe(safeAddress, safes)
// addresses: potentially active tokens by balance
// balances: tokens' balance returned by the backend
const { addresses, balances } = result.data.reduce((acc, { tokenAddress, balance }) => ({
addresses: [...acc.addresses, tokenAddress],
balances: [...acc.balances, balance],
}), { addresses: [], balances: [] })
// update balance list for the safe
dispatch(updateSafe({ address: safeAddress, balances: Set(balances) }))
// active tokens by balance, excluding those already blacklisted and the `null` address
const activeByBalance = addresses.filter((address) => address !== null && !blacklistedTokens.includes(address))
// need to persist those already active tokens, despite its balances
const activeTokens = alreadyActiveTokens.toSet().union(activeByBalance)
// update list of active tokens
dispatch(updateActiveTokens(safeAddress, activeTokens))
} catch (err) {
console.error('Error fetching token list', err)
}
return null
}
export default activateTokensByBalance

View File

@ -2,7 +2,7 @@
import { createAction } from 'redux-actions' import { createAction } from 'redux-actions'
import type { Dispatch as ReduxDispatch } from 'redux' import type { Dispatch as ReduxDispatch } from 'redux'
import { type Token } from '~/logic/tokens/store/model/token' import { type Token } from '~/logic/tokens/store/model/token'
import { removeTokenFromStorage, removeFromActiveTokens } from '~/logic/tokens/utils/tokensStorage' import { removeFromActiveTokens, removeTokenFromStorage } from '~/logic/tokens/utils/tokensStorage'
import { type GlobalState } from '~/store/index' import { type GlobalState } from '~/store/index'
export const REMOVE_TOKEN = 'REMOVE_TOKEN' export const REMOVE_TOKEN = 'REMOVE_TOKEN'

View File

@ -2,11 +2,13 @@
import fetchTokens from '~/logic/tokens/store/actions/fetchTokens' import fetchTokens from '~/logic/tokens/store/actions/fetchTokens'
import { addToken } from '~/logic/tokens/store/actions/addToken' import { addToken } from '~/logic/tokens/store/actions/addToken'
import updateActiveTokens from '~/routes/safe/store/actions/updateActiveTokens' import updateActiveTokens from '~/routes/safe/store/actions/updateActiveTokens'
import updateBlacklistedTokens from '~/routes/safe/store/actions/updateBlacklistedTokens'
import activateTokenForAllSafes from '~/routes/safe/store/actions/activateTokenForAllSafes' import activateTokenForAllSafes from '~/routes/safe/store/actions/activateTokenForAllSafes'
export type Actions = { export type Actions = {
fetchTokens: Function, fetchTokens: Function,
updateActiveTokens: Function, updateActiveTokens: Function,
updateBlacklistedTokens: typeof updateBlacklistedTokens,
addToken: Function, addToken: Function,
activateTokenForAllSafes: Function, activateTokenForAllSafes: Function,
} }
@ -15,5 +17,6 @@ export default {
fetchTokens, fetchTokens,
addToken, addToken,
updateActiveTokens, updateActiveTokens,
updateBlacklistedTokens,
activateTokenForAllSafes, activateTokenForAllSafes,
} }

View File

@ -22,6 +22,7 @@ type Props = Actions & {
tokens: List<Token>, tokens: List<Token>,
safeAddress: string, safeAddress: string,
activeTokens: List<Token>, activeTokens: List<Token>,
blacklistedTokens: List<Token>,
} }
type ActiveScreen = 'tokenList' | 'addCustomToken' type ActiveScreen = 'tokenList' | 'addCustomToken'
@ -32,8 +33,10 @@ const Tokens = (props: Props) => {
classes, classes,
tokens, tokens,
activeTokens, activeTokens,
blacklistedTokens,
fetchTokens, fetchTokens,
updateActiveTokens, updateActiveTokens,
updateBlacklistedTokens,
safeAddress, safeAddress,
addToken, addToken,
activateTokenForAllSafes, activateTokenForAllSafes,
@ -43,7 +46,7 @@ const Tokens = (props: Props) => {
<> <>
<Row align="center" grow className={classes.heading}> <Row align="center" grow className={classes.heading}>
<Paragraph size="xl" noMargin weight="bolder"> <Paragraph size="xl" noMargin weight="bolder">
Manage Tokens Manage List
</Paragraph> </Paragraph>
<IconButton onClick={onClose} disableRipple data-testid={MANAGE_TOKENS_MODAL_CLOSE_BUTTON_TEST_ID}> <IconButton onClick={onClose} disableRipple data-testid={MANAGE_TOKENS_MODAL_CLOSE_BUTTON_TEST_ID}>
<Close className={classes.close} /> <Close className={classes.close} />
@ -54,8 +57,10 @@ const Tokens = (props: Props) => {
<TokenList <TokenList
tokens={tokens} tokens={tokens}
activeTokens={activeTokens} activeTokens={activeTokens}
blacklistedTokens={blacklistedTokens}
fetchTokens={fetchTokens} fetchTokens={fetchTokens}
updateActiveTokens={updateActiveTokens} updateActiveTokens={updateActiveTokens}
updateBlacklistedTokens={updateBlacklistedTokens}
safeAddress={safeAddress} safeAddress={safeAddress}
setActiveScreen={setActiveScreen} setActiveScreen={setActiveScreen}
/> />

View File

@ -25,14 +25,17 @@ type Props = {
tokens: List<Token>, tokens: List<Token>,
safeAddress: string, safeAddress: string,
activeTokens: List<Token>, activeTokens: List<Token>,
fetchTokens: Function, blacklistedTokens: List<Token>,
updateActiveTokens: Function, updateActiveTokens: Function,
updateBlacklistedTokens: Function,
setActiveScreen: Function, setActiveScreen: Function,
} }
type State = { type State = {
filter: string, filter: string,
activeTokensAddresses: Set<string>, activeTokensAddresses: Set<string>,
initialActiveTokensAddresses: Set<string>,
blacklistedTokensAddresses: Set<string>,
} }
const filterBy = (filter: string, tokens: List<Token>): List<Token> => tokens.filter( const filterBy = (filter: string, tokens: List<Token>): List<Token> => tokens.filter(
@ -52,13 +55,10 @@ class Tokens extends React.Component<Props, State> {
state = { state = {
filter: '', filter: '',
activeTokensAddresses: Set([]), activeTokensAddresses: Set([]),
initialActiveTokensAddresses: Set([]),
blacklistedTokensAddresses: Set([]),
activeTokensCalculated: false, activeTokensCalculated: false,
} blacklistedTokensCalculated: false,
componentDidMount() {
const { fetchTokens } = this.props
fetchTokens()
} }
static getDerivedStateFromProps(nextProps, prevState) { static getDerivedStateFromProps(nextProps, prevState) {
@ -70,17 +70,29 @@ class Tokens extends React.Component<Props, State> {
return { return {
activeTokensAddresses: Set(activeTokens.map(({ address }) => address)), activeTokensAddresses: Set(activeTokens.map(({ address }) => address)),
initialActiveTokensAddresses: Set(activeTokens.map(({ address }) => address)),
activeTokensCalculated: true, activeTokensCalculated: true,
} }
} }
if (!prevState.blacklistedTokensCalculated) {
const { blacklistedTokens } = nextProps
return {
blacklistedTokensAddresses: blacklistedTokens,
blacklistedTokensCalculated: true,
}
}
return null return null
} }
componentWillUnmount() { componentWillUnmount() {
const { activeTokensAddresses } = this.state const { activeTokensAddresses, blacklistedTokensAddresses } = this.state
const { updateActiveTokens, safeAddress } = this.props const { updateActiveTokens, updateBlacklistedTokens, safeAddress } = this.props
updateActiveTokens(safeAddress, activeTokensAddresses) updateActiveTokens(safeAddress, activeTokensAddresses)
updateBlacklistedTokens(safeAddress, blacklistedTokensAddresses)
} }
onCancelSearch = () => { onCancelSearch = () => {
@ -92,17 +104,20 @@ class Tokens extends React.Component<Props, State> {
} }
onSwitch = (token: Token) => () => { onSwitch = (token: Token) => () => {
const { activeTokensAddresses } = this.state this.setState((prevState) => {
const activeTokensAddresses = prevState.activeTokensAddresses.has(token.address)
? prevState.activeTokensAddresses.remove(token.address)
: prevState.activeTokensAddresses.add(token.address)
if (activeTokensAddresses.has(token.address)) { let { blacklistedTokensAddresses } = prevState
this.setState({ if (activeTokensAddresses.has(token.address)) {
activeTokensAddresses: activeTokensAddresses.remove(token.address), blacklistedTokensAddresses = prevState.blacklistedTokensAddresses.remove(token.address)
}) } else if (prevState.initialActiveTokensAddresses.has(token.address)) {
} else { blacklistedTokensAddresses = prevState.blacklistedTokensAddresses.add(token.address)
this.setState({ }
activeTokensAddresses: activeTokensAddresses.add(token.address),
}) return ({ ...prevState, activeTokensAddresses, blacklistedTokensAddresses })
} })
} }
createItemData = (tokens, activeTokensAddresses) => ({ createItemData = (tokens, activeTokensAddresses) => ({

View File

@ -38,6 +38,9 @@ type Props = {
granted: boolean, granted: boolean,
tokens: List<Token>, tokens: List<Token>,
activeTokens: List<Token>, activeTokens: List<Token>,
blacklistedTokens: List<Token>,
activateTokensByBalance: Function,
fetchTokens: Function,
safeAddress: string, safeAddress: string,
safeName: string, safeName: string,
ethBalance: string, ethBalance: string,
@ -57,6 +60,7 @@ class Balances extends React.Component<Props, State> {
}, },
showReceive: false, showReceive: false,
} }
props.fetchTokens()
} }
onShow = (action: Action) => () => { onShow = (action: Action) => () => {
@ -85,6 +89,17 @@ class Balances extends React.Component<Props, State> {
}) })
} }
handleChange = (e: SyntheticInputEvent<HTMLInputElement>) => {
const { checked } = e.target
this.setState(() => ({ hideZero: checked }))
}
componentDidMount(): void {
const { activateTokensByBalance, safeAddress } = this.props
activateTokensByBalance(safeAddress)
}
render() { render() {
const { const {
showToken, showReceive, sendFunds, showToken, showReceive, sendFunds,
@ -95,6 +110,7 @@ class Balances extends React.Component<Props, State> {
tokens, tokens,
safeAddress, safeAddress,
activeTokens, activeTokens,
blacklistedTokens,
safeName, safeName,
ethBalance, ethBalance,
createTransaction, createTransaction,
@ -110,10 +126,10 @@ class Balances extends React.Component<Props, State> {
<Row align="center" className={classes.message}> <Row align="center" className={classes.message}>
<Col xs={12} end="sm"> <Col xs={12} end="sm">
<ButtonLink size="lg" onClick={this.onShow('Token')} testId="manage-tokens-btn"> <ButtonLink size="lg" onClick={this.onShow('Token')} testId="manage-tokens-btn">
Manage Tokens Manage List
</ButtonLink> </ButtonLink>
<Modal <Modal
title="Manage Tokens" title="Manage List"
description="Enable and disable tokens to be listed" description="Enable and disable tokens to be listed"
handleClose={this.onHide('Token')} handleClose={this.onHide('Token')}
open={showToken} open={showToken}
@ -123,6 +139,7 @@ class Balances extends React.Component<Props, State> {
onClose={this.onHide('Token')} onClose={this.onHide('Token')}
safeAddress={safeAddress} safeAddress={safeAddress}
activeTokens={activeTokens} activeTokens={activeTokens}
blacklistedTokens={blacklistedTokens}
/> />
</Modal> </Modal>
</Col> </Col>

View File

@ -60,9 +60,12 @@ const Layout = (props: Props) => {
granted, granted,
tokens, tokens,
activeTokens, activeTokens,
blacklistedTokens,
createTransaction, createTransaction,
processTransaction, processTransaction,
fetchTransactions, fetchTransactions,
activateTokensByBalance,
fetchTokens,
updateSafe, updateSafe,
transactions, transactions,
userAddress, userAddress,
@ -156,8 +159,11 @@ const Layout = (props: Props) => {
ethBalance={ethBalance} ethBalance={ethBalance}
tokens={tokens} tokens={tokens}
activeTokens={activeTokens} activeTokens={activeTokens}
blacklistedTokens={blacklistedTokens}
granted={granted} granted={granted}
safeAddress={address} safeAddress={address}
activateTokensByBalance={activateTokensByBalance}
fetchTokens={fetchTokens}
safeName={name} safeName={name}
createTransaction={createTransaction} createTransaction={createTransaction}
/> />

View File

@ -7,6 +7,7 @@ import processTransaction from '~/routes/safe/store/actions/processTransaction'
import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions' import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions'
import updateSafe from '~/routes/safe/store/actions/updateSafe' import updateSafe from '~/routes/safe/store/actions/updateSafe'
import fetchTokens from '~/logic/tokens/store/actions/fetchTokens' import fetchTokens from '~/logic/tokens/store/actions/fetchTokens'
import activateTokensByBalance from '~/logic/tokens/store/actions/activateTokensByBalance'
export type Actions = { export type Actions = {
fetchSafe: typeof fetchSafe, fetchSafe: typeof fetchSafe,
@ -17,6 +18,7 @@ export type Actions = {
fetchTokens: typeof fetchTokens, fetchTokens: typeof fetchTokens,
processTransaction: typeof processTransaction, processTransaction: typeof processTransaction,
fetchEtherBalance: typeof fetchEtherBalance, fetchEtherBalance: typeof fetchEtherBalance,
activateTokensByBalance: typeof activateTokensByBalance
} }
export default { export default {
@ -26,6 +28,7 @@ export default {
processTransaction, processTransaction,
fetchTokens, fetchTokens,
fetchTransactions, fetchTransactions,
activateTokensByBalance,
updateSafe, updateSafe,
fetchEtherBalance, fetchEtherBalance,
checkAndUpdateSafeOwners, checkAndUpdateSafeOwners,

View File

@ -102,6 +102,7 @@ class SafeView extends React.Component<Props, State> {
safe, safe,
provider, provider,
activeTokens, activeTokens,
blacklistedTokens,
granted, granted,
userAddress, userAddress,
network, network,
@ -109,6 +110,8 @@ class SafeView extends React.Component<Props, State> {
createTransaction, createTransaction,
processTransaction, processTransaction,
fetchTransactions, fetchTransactions,
activateTokensByBalance,
fetchTokens,
updateSafe, updateSafe,
transactions, transactions,
} = this.props } = this.props
@ -117,6 +120,7 @@ class SafeView extends React.Component<Props, State> {
<Page> <Page>
<Layout <Layout
activeTokens={activeTokens} activeTokens={activeTokens}
blacklistedTokens={blacklistedTokens}
tokens={tokens} tokens={tokens}
provider={provider} provider={provider}
safe={safe} safe={safe}
@ -126,6 +130,8 @@ class SafeView extends React.Component<Props, State> {
createTransaction={createTransaction} createTransaction={createTransaction}
processTransaction={processTransaction} processTransaction={processTransaction}
fetchTransactions={fetchTransactions} fetchTransactions={fetchTransactions}
activateTokensByBalance={activateTokensByBalance}
fetchTokens={fetchTokens}
updateSafe={updateSafe} updateSafe={updateSafe}
transactions={transactions} transactions={transactions}
sendFunds={sendFunds} sendFunds={sendFunds}

View File

@ -5,6 +5,7 @@ import {
safeSelector, safeSelector,
safeActiveTokensSelector, safeActiveTokensSelector,
safeBalancesSelector, safeBalancesSelector,
safeBlacklistedTokensSelector,
type RouterProps, type RouterProps,
type SafeSelectorProps, type SafeSelectorProps,
} from '~/routes/safe/store/selectors' } from '~/routes/safe/store/selectors'
@ -25,6 +26,7 @@ export type SelectorProps = {
provider: string, provider: string,
tokens: List<Token>, tokens: List<Token>,
activeTokens: List<Token>, activeTokens: List<Token>,
blacklistedTokens: List<Token>,
userAddress: string, userAddress: string,
network: string, network: string,
safeUrl: string, safeUrl: string,
@ -135,6 +137,7 @@ export default createStructuredSelector<Object, *>({
provider: providerNameSelector, provider: providerNameSelector,
tokens: orderedTokenListSelector, tokens: orderedTokenListSelector,
activeTokens: extendedSafeTokensSelector, activeTokens: extendedSafeTokensSelector,
blacklistedTokens: safeBlacklistedTokensSelector,
granted: grantedSelector, granted: grantedSelector,
userAddress: userAccountSelector, userAddress: userAccountSelector,
network: networkSelector, network: networkSelector,

View File

@ -19,7 +19,7 @@ export const calculateBalanceOf = async (tokenAddress: string, safeAddress: stri
const token = await erc20Token.at(tokenAddress) const token = await erc20Token.at(tokenAddress)
balance = await token.balanceOf(safeAddress) balance = await token.balanceOf(safeAddress)
} catch (err) { } catch (err) {
console.error('Failed to fetch token balances: ', err) console.error('Failed to fetch token balances: ', tokenAddress, err)
} }
return new BigNumber(balance).div(10 ** decimals).toString() return new BigNumber(balance).div(10 ** decimals).toString()
@ -50,7 +50,6 @@ const fetchTokenBalances = (safeAddress: string, tokens: List<Token>) => async (
dispatch(updateSafe({ address: safeAddress, balances })) dispatch(updateSafe({ address: safeAddress, balances }))
} catch (err) { } catch (err) {
// eslint-disable-next-line
console.error('Error when fetching token balances:', err) console.error('Error when fetching token balances:', err)
} }
} }

View File

@ -0,0 +1,13 @@
// @flow
import { Set } from 'immutable'
import type { Dispatch as ReduxDispatch } from 'redux'
import { type GlobalState } from '~/store'
import updateSafe from './updateSafe'
const updateBlacklistedTokens = (safeAddress: string, blacklistedTokens: Set<string>) => async (
dispatch: ReduxDispatch<GlobalState>,
) => {
dispatch(updateSafe({ address: safeAddress, blacklistedTokens }))
}
export default updateBlacklistedTokens

View File

@ -1,5 +1,5 @@
// @flow // @flow
import type { Store, AnyAction } from 'redux' import type { AnyAction, Store } from 'redux'
import { List } from 'immutable' import { List } from 'immutable'
import { ADD_SAFE } from '~/routes/safe/store/actions/addSafe' import { ADD_SAFE } from '~/routes/safe/store/actions/addSafe'
import { UPDATE_SAFE } from '~/routes/safe/store/actions/updateSafe' import { UPDATE_SAFE } from '~/routes/safe/store/actions/updateSafe'
@ -10,10 +10,12 @@ import { REPLACE_SAFE_OWNER } from '~/routes/safe/store/actions/replaceSafeOwner
import { EDIT_SAFE_OWNER } from '~/routes/safe/store/actions/editSafeOwner' import { EDIT_SAFE_OWNER } from '~/routes/safe/store/actions/editSafeOwner'
import { type GlobalState } from '~/store/' import { type GlobalState } from '~/store/'
import { import {
saveSafes, setOwners, removeOwners, saveDefaultSafe, removeOwners,
saveDefaultSafe,
saveSafes,
setOwners,
} from '~/logic/safe/utils' } from '~/logic/safe/utils'
import { safesMapSelector, getActiveTokensAddressesForAllSafes } from '~/routes/safe/store/selectors' import { getActiveTokensAddressesForAllSafes, safesMapSelector } from '~/routes/safe/store/selectors'
import { tokensSelector } from '~/logic/tokens/store/selectors' import { tokensSelector } from '~/logic/tokens/store/selectors'
import type { Token } from '~/logic/tokens/store/model/token' import type { Token } from '~/logic/tokens/store/model/token'
import { makeOwner } from '~/routes/safe/store/models/owner' import { makeOwner } from '~/routes/safe/store/models/owner'

View File

@ -12,6 +12,7 @@ export type SafeProps = {
owners: List<Owner>, owners: List<Owner>,
balances?: Map<string, string>, balances?: Map<string, string>,
activeTokens: Set<string>, activeTokens: Set<string>,
blacklistedTokens: Set<string>,
ethBalance?: string, ethBalance?: string,
} }
@ -22,6 +23,7 @@ const SafeRecord: RecordFactory<SafeProps> = Record({
ethBalance: 0, ethBalance: 0,
owners: List([]), owners: List([]),
activeTokens: new Set(), activeTokens: new Set(),
blacklistedTokens: new Set(),
balances: Map({}), balances: Map({}),
}) })

View File

@ -22,6 +22,7 @@ export const buildSafe = (storedSafe: SafeProps) => {
const addresses = storedSafe.owners.map((owner: OwnerProps) => owner.address) const addresses = storedSafe.owners.map((owner: OwnerProps) => owner.address)
const owners = buildOwnersFrom(Array.from(names), Array.from(addresses)) const owners = buildOwnersFrom(Array.from(names), Array.from(addresses))
const activeTokens = Set(storedSafe.activeTokens) const activeTokens = Set(storedSafe.activeTokens)
const blacklistedTokens = Set(storedSafe.blacklistedTokens)
const balances = Map(storedSafe.balances) const balances = Map(storedSafe.balances)
const safe: SafeProps = { const safe: SafeProps = {
@ -29,6 +30,7 @@ export const buildSafe = (storedSafe: SafeProps) => {
owners, owners,
balances, balances,
activeTokens, activeTokens,
blacklistedTokens,
} }
return safe return safe

View File

@ -106,6 +106,21 @@ export const safeActiveTokensSelector: Selector<GlobalState, RouterProps, List<s
}, },
) )
export const safeBlacklistedTokensSelector: Selector<GlobalState, RouterProps, List<string>> = createSelector(
safeSelector,
(safe: Safe) => {
if (!safe) {
return List()
}
return safe.blacklistedTokens
},
)
export const safeActiveTokensSelectorBySafe = (safeAddress: string, safes: Map<string, Safe>): List<string> => safes.get(safeAddress).get('activeTokens')
export const safeBlacklistedTokensSelectorBySafe = (safeAddress: string, safes: Map<string, Safe>): List<string> => safes.get(safeAddress).get('blacklistedTokens')
export const safeBalancesSelector: Selector<GlobalState, RouterProps, Map<string, string>> = createSelector( export const safeBalancesSelector: Selector<GlobalState, RouterProps, Map<string, string>> = createSelector(
safeSelector, safeSelector,
(safe: Safe) => { (safe: Safe) => {
@ -132,7 +147,23 @@ export const getActiveTokensAddressesForAllSafes: Selector<GlobalState, any, Set
}, },
) )
export const getBlacklistedTokensAddressesForAllSafes: Selector<GlobalState, any, Set<string>> = createSelector(
safesListSelector,
(safes: List<Safe>) => {
const addresses = Set().withMutations((set) => {
safes.forEach((safe: Safe) => {
safe.blacklistedTokens.forEach((tokenAddress) => {
set.add(tokenAddress)
})
})
})
return addresses
},
)
export default createStructuredSelector<Object, *>({ export default createStructuredSelector<Object, *>({
safe: safeSelector, safe: safeSelector,
tokens: safeActiveTokensSelector, tokens: safeActiveTokensSelector,
blacklistedTokens: safeBlacklistedTokensSelector,
}) })