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

chore(wallet): Created Asset Management Hook #11875

Merged
merged 1 commit into from
Jan 19, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,4 @@ export const cancelTransaction = createAction<BraveWallet.TransactionInfo>('canc
export const speedupTransaction = createAction<BraveWallet.TransactionInfo>('speedupTransaction')
export const defaultCurrenciesUpdated = createAction<DefaultCurrencies>('defaultCurrenciesUpdated')
export const expandWalletNetworks = createAction('expandWalletNetworks')
export const refreshBalancesAndPriceHistory = createAction('refreshBalancesAndPriceHistory')
7 changes: 4 additions & 3 deletions components/brave_wallet_ui/common/async/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,19 +244,20 @@ handler.on(WalletActions.addUserAsset.getType(), async (store: Store, payload: A
const braveWalletService = getAPIProxy().braveWalletService
const result = await braveWalletService.addUserAsset(payload.token, payload.chainId)
store.dispatch(WalletActions.addUserAssetError(!result.success))
await refreshBalancesPricesAndHistory(store)
})

handler.on(WalletActions.removeUserAsset.getType(), async (store: Store, payload: RemoveUserAssetPayloadType) => {
const braveWalletService = getAPIProxy().braveWalletService
await braveWalletService.removeUserAsset(payload.token, payload.chainId)
await refreshBalancesPricesAndHistory(store)
})

handler.on(WalletActions.setUserAssetVisible.getType(), async (store: Store, payload: SetUserAssetVisiblePayloadType) => {
const braveWalletService = getAPIProxy().braveWalletService
await braveWalletService.setUserAssetVisible(payload.token, payload.chainId, payload.isVisible)
await refreshBalancesPricesAndHistory(store)
})

handler.on(WalletActions.refreshBalancesAndPriceHistory.getType(), async (store: Store) => {
refreshBalancesPricesAndHistory(store)
})

handler.on(WalletActions.selectPortfolioTimeline.getType(), async (store: Store, payload: BraveWallet.AssetPriceTimeframe) => {
Expand Down
3 changes: 2 additions & 1 deletion components/brave_wallet_ui/common/constants/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ export const mockERC20Token: BraveWallet.BlockchainToken = {
isErc721: false,
decimals: 18,
visible: true,
tokenId: ''
tokenId: '',
coingeckoId: ''
}

export const mockAccount: WalletAccountType = {
Expand Down
113 changes: 113 additions & 0 deletions components/brave_wallet_ui/common/hooks/assets-management.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { renderHook, act } from '@testing-library/react-hooks'
import * as WalletActions from '../actions/wallet_actions'
import {
mockNetwork
} from '../constants/mocks'
import useAssetManagement from './assets-management'
import { AccountAssetOptions } from '../../options/asset-options'

const mockUserVisibleTokensInfo = [
AccountAssetOptions[1],
AccountAssetOptions[2]
]

const mockCustomToken = {
contractAddress: 'customTokenContractAddress',
name: 'Custom Token',
symbol: 'CT',
logo: '',
isErc20: true,
isErc721: false,
decimals: 18,
visible: true,
tokenId: '',
coingeckoId: ''
}

describe('useAssetManagement hook', () => {
let addedAssetSpy: jest.SpyInstance
let removedAssetSpy: jest.SpyInstance
let refreshedBalancesPricesSpy: jest.SpyInstance
let setUserAssetVisibleSpy: jest.SpyInstance

addedAssetSpy = jest.spyOn(WalletActions, 'addUserAsset')
removedAssetSpy = jest.spyOn(WalletActions, 'removeUserAsset')
refreshedBalancesPricesSpy = jest.spyOn(WalletActions, 'refreshBalancesAndPriceHistory')
setUserAssetVisibleSpy = jest.spyOn(WalletActions, 'setUserAssetVisible')

it('Should add an asset', () => {
const { result } = renderHook(() => useAssetManagement(
WalletActions.addUserAsset,
WalletActions.setUserAssetVisible,
WalletActions.removeUserAsset,
WalletActions.refreshBalancesAndPriceHistory,
mockNetwork,
AccountAssetOptions,
mockUserVisibleTokensInfo
))
act(() => result.current.onUpdateVisibleAssets([AccountAssetOptions[1], AccountAssetOptions[2], AccountAssetOptions[3]]))
expect(addedAssetSpy).toBeCalledWith({ chainId: mockNetwork.chainId, token: { ...AccountAssetOptions[3], logo: '' } })
expect(refreshedBalancesPricesSpy).toBeCalledTimes(1)
})

it('Should remove an asset', () => {
const { result } = renderHook(() => useAssetManagement(
WalletActions.addUserAsset,
WalletActions.setUserAssetVisible,
WalletActions.removeUserAsset,
WalletActions.refreshBalancesAndPriceHistory,
mockNetwork,
AccountAssetOptions,
mockUserVisibleTokensInfo
))
act(() => result.current.onUpdateVisibleAssets([AccountAssetOptions[1]]))
expect(removedAssetSpy).toBeCalledWith({ chainId: mockNetwork.chainId, token: AccountAssetOptions[2] })
expect(refreshedBalancesPricesSpy).toBeCalledTimes(1)
})

it('Should remove and add an asset', () => {
const { result } = renderHook(() => useAssetManagement(
WalletActions.addUserAsset,
WalletActions.setUserAssetVisible,
WalletActions.removeUserAsset,
WalletActions.refreshBalancesAndPriceHistory,
mockNetwork,
AccountAssetOptions,
mockUserVisibleTokensInfo
))
act(() => result.current.onUpdateVisibleAssets([AccountAssetOptions[1], AccountAssetOptions[3]]))
expect(addedAssetSpy).toBeCalledWith({ chainId: mockNetwork.chainId, token: { ...AccountAssetOptions[3], logo: '' } })
expect(removedAssetSpy).toBeCalledWith({ chainId: mockNetwork.chainId, token: AccountAssetOptions[2] })
expect(refreshedBalancesPricesSpy).toBeCalledTimes(1)
})

it('Should set custom tokens visibility to false', () => {
const { result } = renderHook(() => useAssetManagement(
WalletActions.addUserAsset,
WalletActions.setUserAssetVisible,
WalletActions.removeUserAsset,
WalletActions.refreshBalancesAndPriceHistory,
mockNetwork,
AccountAssetOptions,
[...mockUserVisibleTokensInfo, mockCustomToken]
))
act(() => result.current.onUpdateVisibleAssets([...mockUserVisibleTokensInfo, { ...mockCustomToken, visible: false }]))
expect(setUserAssetVisibleSpy).toBeCalledWith({ chainId: mockNetwork.chainId, token: { ...mockCustomToken, visible: false }, isVisible: false })
expect(refreshedBalancesPricesSpy).toBeCalledTimes(1)
})

it('Should set custom tokens visibility to true', () => {
const { result } = renderHook(() => useAssetManagement(
WalletActions.addUserAsset,
WalletActions.setUserAssetVisible,
WalletActions.removeUserAsset,
WalletActions.refreshBalancesAndPriceHistory,
mockNetwork,
AccountAssetOptions,
[...mockUserVisibleTokensInfo, { ...mockCustomToken, visible: false }]
))
act(() => result.current.onUpdateVisibleAssets([...mockUserVisibleTokensInfo, mockCustomToken]))
expect(setUserAssetVisibleSpy).toBeCalledWith({ chainId: mockNetwork.chainId, token: mockCustomToken, isVisible: true })
expect(refreshedBalancesPricesSpy).toBeCalledTimes(1)
})
})
88 changes: 88 additions & 0 deletions components/brave_wallet_ui/common/hooks/assets-management.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright (c) 2021 The Brave Authors. All rights reserved.
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// you can obtain one at http://mozilla.org/MPL/2.0/.

import * as React from 'react'
import { SimpleActionCreator, EmptyActionCreator } from 'redux-act'
import {
BraveWallet
} from '../../constants/types'

import {
AddUserAssetPayloadType,
SetUserAssetVisiblePayloadType,
RemoveUserAssetPayloadType
} from '../constants/action_types'

import { stripERC20TokenImageURL } from '../../utils/string-utils'

export default function useAssetManagement (
addUserAsset: SimpleActionCreator<AddUserAssetPayloadType>,
setUserAssetVisible: SimpleActionCreator<SetUserAssetVisiblePayloadType>,
removeUserAsset: SimpleActionCreator<RemoveUserAssetPayloadType>,
onyb marked this conversation as resolved.
Show resolved Hide resolved
refreshBalancesPricesAndHistory: EmptyActionCreator,
selectedNetwork: BraveWallet.EthereumChain,
fullTokenList: BraveWallet.BlockchainToken[],
userVisibleTokensInfo: BraveWallet.BlockchainToken[]
) {
const onAddUserAsset = (token: BraveWallet.BlockchainToken) => {
addUserAsset({
token: {
...token,
logo: stripERC20TokenImageURL(token.logo) || ''
},
chainId: selectedNetwork.chainId
})
}

const onAddCustomAsset = (token: BraveWallet.BlockchainToken) => {
onAddUserAsset(token)
refreshBalancesPricesAndHistory()
}

const findVisibleTokenInfo = (token: BraveWallet.BlockchainToken) => {
return userVisibleTokensInfo.find((t) => t.contractAddress.toLowerCase() === token.contractAddress.toLowerCase())
}

const onUpdateVisibleAssets = React.useCallback((updatedTokensList: BraveWallet.BlockchainToken[]) => {
// Gets a list of all added tokens and adds them to the userVisibleTokensInfo list
updatedTokensList.filter((firstItem) =>
!userVisibleTokensInfo.some((secondItem) =>
firstItem.contractAddress.toLowerCase() === secondItem.contractAddress.toLowerCase()))
.forEach((token) => onAddUserAsset(token))

// Gets a list of all removed tokens and removes them from the userVisibleTokensInfo list
userVisibleTokensInfo.filter((firstItem) =>
!updatedTokensList.some((secondItem) =>
firstItem.contractAddress.toLowerCase() === secondItem.contractAddress.toLowerCase()))
.forEach((token) => removeUserAsset({ token, chainId: selectedNetwork.chainId }))

// Gets a list of custom tokens returned from updatedTokensList payload
// then compares customTokens against userVisibleTokensInfo list and updates the custom tokens visibility if it has changed
updatedTokensList.filter((firstItem) =>
!fullTokenList.some((secondItem) =>
firstItem.contractAddress.toLowerCase() === secondItem.contractAddress.toLowerCase()))
.forEach((token) => {
const foundToken = findVisibleTokenInfo(token)
// Since a networks native token (example 'ETH') can be removed from the the userVisibleTokensInfo list,
// when it is re-added we handle it as a custom token since it is not part of the token registry.
// This check here will add it only if it's value 'visible' is returned true
if (token.contractAddress.toLowerCase() === '' && !foundToken?.visible && token.visible) {
onAddUserAsset(token)
}
// Updates token visibility exluding a networks native token
if (foundToken?.visible !== token.visible && token.contractAddress.toLowerCase() !== '') {
setUserAssetVisible({ token, chainId: selectedNetwork.chainId, isVisible: token.visible })
}
})

// Refreshes Balances, Prices and Price History when done.
refreshBalancesPricesAndHistory()
}, [userVisibleTokensInfo, selectedNetwork])

return {
onUpdateVisibleAssets,
onAddCustomAsset
}
}
4 changes: 3 additions & 1 deletion components/brave_wallet_ui/common/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import usePricing from './pricing'
import usePreset from './select-preset'
import useTokenInfo from './token'
import useExplorer from './explorer'
import useAssetManagement from './assets-management'

export {
useAssets,
Expand All @@ -25,5 +26,6 @@ export {
useSend,
usePreset,
useTokenInfo,
useExplorer
useExplorer,
useAssetManagement
}
Loading