Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(wallet): Allow Removing Auto Discovered Tokens #18459

Merged
merged 1 commit into from
May 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions components/brave_wallet_ui/common/actions/wallet_actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/. */

import { WalletActions } from '../slices/wallet.slice'
import { WalletActions } from '../slices/wallet.slice'

// We must re-export actions here until we remove all imports of this file
export const {
// We must re-export actions here until we remove all imports of this file
export const {
accountsChanged,
activeOriginChanged,
addAccount,
Expand Down Expand Up @@ -98,5 +98,7 @@
updateUnapprovedTransactionSpendAllowance,
updateUserAsset,
setHidePortfolioGraph,
setHidePortfolioBalances
setHidePortfolioBalances,
setRemovedFungibleTokenIds,
setRemovedNonFungibleTokenIds
} = WalletActions
27 changes: 19 additions & 8 deletions components/brave_wallet_ui/common/async/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {
import { getTokenParam, getFlattenedAccountBalances } from '../../utils/api-utils'
import Amount from '../../utils/amount'
import { sortTransactionByDate } from '../../utils/tx-utils'
import { getBatTokensFromList, getNativeTokensFromList, getUniqueAssets } from '../../utils/asset-utils'
import { getAssetIdKey, getBatTokensFromList, getNativeTokensFromList, getUniqueAssets } from '../../utils/asset-utils'
import { loadTimeData } from '../../../common/loadTimeData'
import { getVisibleNetworksList } from '../slices/api.slice'

Expand Down Expand Up @@ -62,7 +62,7 @@ export const getERC20Allowance = (
const { braveWalletService, jsonRpcService } = getAPIProxy()
const { chainId }
= await braveWalletService.getChainIdForActiveOrigin(
BraveWallet.CoinType.ETH)
BraveWallet.CoinType.ETH)
const result = await jsonRpcService.getERC20TokenAllowance(
contractAddress,
ownerAddress,
Expand Down Expand Up @@ -488,17 +488,23 @@ export function refreshVisibleTokenInfo (targetNetwork?: BraveWallet.NetworkInfo
const visibleAssets = targetNetwork
? await inner(targetNetwork)
: await Promise.all(networkList.map(async (item) => await inner(item)))

const userVisibleTokensInfo = visibleAssets.flat(1)
const removedAssetIds =
[
...getState().wallet.removedFungibleTokenIds,
...getState().wallet.removedNonFungibleTokenIds
]
const userVisibleTokensInfo = visibleAssets
.flat(1)
.filter(token => !removedAssetIds.includes(getAssetIdKey(token)))
await dispatch(WalletActions.setVisibleTokensInfo(userVisibleTokensInfo))
const nfts = userVisibleTokensInfo.filter((asset) => asset.isErc721 || asset.isNft)
dispatch(WalletPageActions.getNftsPinningStatus(nfts))
}
}

function reportActiveWalletsToP3A (accounts: WalletAccountType[],
nativeBalances: BalancePayload[][],
blockchainTokenBalances: BalancePayload[][]) {
nativeBalances: BalancePayload[][],
blockchainTokenBalances: BalancePayload[][]) {
const { braveWalletP3A } = getAPIProxy()
const coinsActiveAddresses: {
[coin: BraveWallet.CoinType]: {
Expand Down Expand Up @@ -632,7 +638,12 @@ export function refreshBalances () {
await jsonRpcService.getERC721TokenBalance(token.contractAddress, token.tokenId ?? '', account.address, token?.chainId ?? '')
} else {
balanceInfo =
await jsonRpcService.getERC20TokenBalance(token.contractAddress, account.address, token?.chainId ?? '')
await jsonRpcService
.getERC20TokenBalance(
token.contractAddress,
account.address,
token?.chainId ?? ''
)
}
}
return {
Expand Down Expand Up @@ -1125,7 +1136,7 @@ export const areSupportedForPinning = async (urls: string[]) => {
export const extractIpfsUrl = async (url: string | undefined) => {
const { braveWalletIpfsService } = getAPIProxy()
const trimmedUrl = url ? url.trim() : ''
if (isIpfs(trimmedUrl)) {
if (isIpfs(trimmedUrl)) {
return trimmedUrl
}
return (await braveWalletIpfsService
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,9 @@ export const LOCAL_STORAGE_KEYS = {
PORTFOLIO_ASSET_FILTER_OPTION: 'PORTFOLIO_ASSET_FILTER_OPTION',
PORTFOLIO_TIME_LINE_OPTION: 'PORTFOLIO_TIME_LINE_OPTION',
IS_IPFS_BANNER_HIDDEN: 'BRAVE_WALLET_IS_IPFS_BANNER_HIDDEN',
IS_ENABLE_NFT_AUTO_DISCOVERY_MODAL_HIDDEN: 'BRAVE_WALLET_IS_ENABLE_NFT_AUTO_DISCOVERY_MODAL_HIDDEN'
IS_ENABLE_NFT_AUTO_DISCOVERY_MODAL_HIDDEN: 'BRAVE_WALLET_IS_ENABLE_NFT_AUTO_DISCOVERY_MODAL_HIDDEN',
USER_REMOVED_NON_FUNGIBLE_TOKEN_IDS:
'BRAVE_WALLET_USER_REMOVED_NON_FUNGIBLE_TOKEN_IDS',
USER_REMOVED_FUNGIBLE_TOKEN_IDS:
'BRAVE_WALLET_USER_REMOVED_FUNGIBLE_TOKEN_IDS'
} as const
131 changes: 109 additions & 22 deletions components/brave_wallet_ui/common/hooks/assets-management.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,22 @@
// you can obtain one at https://mozilla.org/MPL/2.0/.

import * as React from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { useDispatch } from 'react-redux'

// Selectors
import {
useUnsafeWalletSelector
} from './use-safe-selector'
import { WalletSelectors } from '../selectors'

// Constants
import { BraveWallet, WalletState } from '../../constants/types'
import { BraveWallet } from '../../constants/types'

// Utils
import { stripERC20TokenImageURL } from '../../utils/string-utils'
import { WalletActions } from '../actions'
import { LOCAL_STORAGE_KEYS } from '../constants/local-storage-keys'
import { getAssetIdKey } from '../../utils/asset-utils'

const onlyInLeft = (left: BraveWallet.BlockchainToken[], right: BraveWallet.BlockchainToken[]) =>
left.filter(leftValue =>
Expand All @@ -28,27 +36,101 @@ const findTokensWithMismatchedVisibility = (left: BraveWallet.BlockchainToken[],
leftValue.visible !== rightValue.visible))

export default function useAssetManagement () {
// redux
const {
userVisibleTokensInfo
} = useSelector(({ wallet }: { wallet: WalletState }) => wallet)
const dispatch = useDispatch()
// selectors
const userVisibleTokensInfo =
useUnsafeWalletSelector(WalletSelectors.userVisibleTokensInfo)
const removedFungibleTokenIds =
useUnsafeWalletSelector(WalletSelectors.removedFungibleTokenIds)
const removedNonFungibleTokenIds =
useUnsafeWalletSelector(WalletSelectors.removedNonFungibleTokenIds)

const onAddUserAsset = (token: BraveWallet.BlockchainToken) => {
dispatch(WalletActions.addUserAsset({
...token,
logo: stripERC20TokenImageURL(token.logo) || ''
}))
}

const onAddCustomAsset = (token: BraveWallet.BlockchainToken) => {
onAddUserAsset(token)
// redux
const dispatch = useDispatch()

// We handle refreshing balances for ERC721 tokens in the addUserAsset handler.
if (!(token.isErc721 || token.isNft)) {
dispatch(WalletActions.refreshBalancesAndPriceHistory())
}
}
const tokenIsSetAsRemovedInLocalStorage = React.useCallback(
(
token: BraveWallet.BlockchainToken
) => {
const assetId = getAssetIdKey(token)
return token.isNft ||
token.isErc1155 ||
token.isErc721
? removedNonFungibleTokenIds.includes(assetId)
: removedFungibleTokenIds.includes(assetId)
}, [removedNonFungibleTokenIds, removedFungibleTokenIds])

const addOrRemoveTokenInLocalStorage = React.useCallback(
(
token: BraveWallet.BlockchainToken,
addOrRemove: 'add' | 'remove'
) => {
const assetId = getAssetIdKey(token)
const isNFT = token.isNft || token.isErc1155 || token.isErc721
const removedList = isNFT
? removedNonFungibleTokenIds
: removedFungibleTokenIds
const localStorageKey = isNFT
? LOCAL_STORAGE_KEYS.USER_REMOVED_NON_FUNGIBLE_TOKEN_IDS
: LOCAL_STORAGE_KEYS.USER_REMOVED_FUNGIBLE_TOKEN_IDS

// add assetId if it is not in the array
if (addOrRemove === 'remove') {
const newList = [...removedList, assetId]
// update state
if (isNFT) {
dispatch(WalletActions.setRemovedNonFungibleTokenIds(newList))
} else {
dispatch(WalletActions.setRemovedFungibleTokenIds(newList))
}
// persist array
localStorage.setItem(localStorageKey, JSON.stringify(newList))
}

// add assetId if it is not in the array
if (addOrRemove === 'add') {
const newList = removedList.filter((id) => id !== assetId)
// update state
if (isNFT) {
dispatch(WalletActions.setRemovedNonFungibleTokenIds(newList))
} else {
dispatch(WalletActions.setRemovedFungibleTokenIds(newList))
}
// persist array
localStorage.setItem(localStorageKey, JSON.stringify(newList))
}

}, [removedNonFungibleTokenIds, removedFungibleTokenIds])

const onAddUserAsset = React.useCallback(
(
token: BraveWallet.BlockchainToken
) => {
if (tokenIsSetAsRemovedInLocalStorage(token)) {
addOrRemoveTokenInLocalStorage(token, 'add')
} else {
dispatch(WalletActions.addUserAsset({
...token,
logo: stripERC20TokenImageURL(token.logo) || ''
}))
}
}, [
addOrRemoveTokenInLocalStorage,
tokenIsSetAsRemovedInLocalStorage
])

const onAddCustomAsset = React.useCallback(
(
token: BraveWallet.BlockchainToken
) => {
onAddUserAsset(token)

// We handle refreshing balances for ERC721 tokens in the
// addUserAsset handler.
if (!(token.isErc721 || token.isNft)) {
dispatch(WalletActions.refreshBalancesAndPriceHistory())
}
}, [onAddUserAsset])

const onUpdateVisibleAssets = React.useCallback((updatedTokensList: BraveWallet.BlockchainToken[]) => {
// Gets a list of all added tokens and adds them to the userVisibleTokensInfo list
Expand All @@ -57,7 +139,12 @@ export default function useAssetManagement () {

// Gets a list of all removed tokens and removes them from the userVisibleTokensInfo list
onlyInLeft(userVisibleTokensInfo, updatedTokensList)
.forEach(token => dispatch(WalletActions.removeUserAsset(token)))
.forEach(token => {
dispatch(WalletActions.removeUserAsset(token))
if (!tokenIsSetAsRemovedInLocalStorage(token)) {
addOrRemoveTokenInLocalStorage(token, 'remove')
}
})

// Gets a list of custom tokens and native assets returned from updatedTokensList payload
// then compares against userVisibleTokensInfo list and updates the tokens visibility if it has changed.
Expand All @@ -66,7 +153,7 @@ export default function useAssetManagement () {

// Refresh Balances, Prices and Price History when done.
dispatch(WalletActions.refreshBalancesAndPriceHistory())
}, [userVisibleTokensInfo])
}, [userVisibleTokensInfo, addOrRemoveTokenInLocalStorage])

const makeTokenVisible = React.useCallback((token: BraveWallet.BlockchainToken) => {
const foundTokenIdx = userVisibleTokensInfo.findIndex(t =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,7 @@ export const transactionSpotPrices = ({ wallet }: State) => wallet.transactionSp
export const transactions = ({ wallet }: State) => wallet.transactions
export const userVisibleTokensInfo = ({ wallet }: State) => wallet.userVisibleTokensInfo
export const selectedAccountFilter = ({ wallet }: State) => wallet.selectedAccountFilter
export const removedFungibleTokenIds = ({ wallet }: State) =>
wallet.removedFungibleTokenIds
export const removedNonFungibleTokenIds = ({ wallet }: State) =>
wallet.removedNonFungibleTokenIds
36 changes: 33 additions & 3 deletions components/brave_wallet_ui/common/slices/wallet.slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,20 @@ const defaultState: WalletState = {
LOCAL_STORAGE_KEYS
.HIDE_PORTFOLIO_BALANCES
) === 'true',
removedFungibleTokenIds:
JSON.parse(
localStorage
.getItem(
LOCAL_STORAGE_KEYS
.USER_REMOVED_FUNGIBLE_TOKEN_IDS
) || '[]'),
removedNonFungibleTokenIds:
JSON.parse(
localStorage
.getItem(
LOCAL_STORAGE_KEYS
.USER_REMOVED_NON_FUNGIBLE_TOKEN_IDS
) || '[]')
}

// async actions
Expand Down Expand Up @@ -428,9 +442,9 @@ export const createWalletSlice = (initialState: WalletState = defaultState) => {
setCoinMarkets (state: WalletState, { payload }: PayloadAction<GetCoinMarketsResponse>) {
state.coinMarketData = payload.success
? payload.values.map(coin => {
coin.image = coin.image.replace('https://assets.coingecko.com', ' https://assets.cgproxy.brave.com')
return coin
})
coin.image = coin.image.replace('https://assets.coingecko.com', ' https://assets.cgproxy.brave.com')
return coin
})
: []
state.isLoadingCoinMarketData = false
},
Expand Down Expand Up @@ -477,6 +491,22 @@ export const createWalletSlice = (initialState: WalletState = defaultState) => {
state.hidePortfolioGraph = payload
},

setRemovedFungibleTokenIds
(
state: WalletState,
{ payload }: PayloadAction<string[]>
) {
state.removedFungibleTokenIds = payload
},

setRemovedNonFungibleTokenIds
(
state: WalletState,
{ payload }: PayloadAction<string[]>
) {
state.removedNonFungibleTokenIds = payload
},

setHidePortfolioBalances
(
state: WalletState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,13 @@ import {
} from './style'

export interface Props {
onSelectAsset: (key: string, selected: boolean, token: BraveWallet.BlockchainToken, isCustom: boolean) => void
onSelectAsset: (
key: string,
selected: boolean,
token: BraveWallet.BlockchainToken
) => void
onRemoveAsset: (token: BraveWallet.BlockchainToken) => void
isCustom: boolean
isRemovable: boolean
isSelected: boolean
token: BraveWallet.BlockchainToken
}
Expand All @@ -44,7 +48,7 @@ const AssetWatchlistItem = React.forwardRef<HTMLDivElement, Props>(
const {
onSelectAsset,
onRemoveAsset,
isCustom,
isRemovable,
token,
isSelected
} = props
Expand All @@ -54,12 +58,12 @@ const AssetWatchlistItem = React.forwardRef<HTMLDivElement, Props>(

// callbacks
const onCheck = React.useCallback((key: string, selected: boolean) => {
onSelectAsset(key, selected, token, isCustom)
}, [onSelectAsset, token, isCustom])
onSelectAsset(key, selected, token)
}, [onSelectAsset, token])

const onClickAsset = React.useCallback(() => {
onSelectAsset(token.contractAddress, !isSelected, token, isCustom)
}, [onSelectAsset, token, isSelected, isCustom])
onSelectAsset(token.contractAddress, !isSelected, token)
}, [onSelectAsset, token, isSelected])

const onClickRemoveAsset = React.useCallback(() => {
onRemoveAsset(token)
Expand Down Expand Up @@ -91,7 +95,7 @@ const AssetWatchlistItem = React.forwardRef<HTMLDivElement, Props>(
</NameAndSymbol>
</NameAndIcon>
<RightSide>
{isCustom &&
{isRemovable &&
<DeleteButton onClick={onClickRemoveAsset}>
<DeleteIcon />
</DeleteButton>
Expand Down
Loading