From 1e35daf95c90fa7db5824c1727b38359d0070294 Mon Sep 17 00:00:00 2001 From: Thorianite <100335276+Thorian1te@users.noreply.github.com> Date: Thu, 8 Aug 2024 18:45:50 +0930 Subject: [PATCH 1/8] revert back to balance call first --- src/renderer/services/avax/balances.ts | 23 ++------- src/renderer/services/ethereum/balances.ts | 56 ++++++++++++++-------- 2 files changed, 42 insertions(+), 37 deletions(-) diff --git a/src/renderer/services/avax/balances.ts b/src/renderer/services/avax/balances.ts index 549a930cd..6279c0657 100644 --- a/src/renderer/services/avax/balances.ts +++ b/src/renderer/services/avax/balances.ts @@ -1,4 +1,3 @@ -import { AssetAVAX } from '@xchainjs/xchain-avax' import { Network } from '@xchainjs/xchain-client' import { Asset } from '@xchainjs/xchain-util' import * as A from 'fp-ts/lib/Array' @@ -6,8 +5,9 @@ import * as FP from 'fp-ts/lib/function' import { HDMode, WalletType } from '../../../shared/wallet/types' import { AvaxAssetsTestnet } from '../../const' +import { validAssetForAVAX } from '../../helpers/assetHelper' +import { liveData } from '../../helpers/rx/liveData' import { observableState } from '../../helpers/stateHelper' -import { AVAX_TOKEN_WHITELIST } from '../../types/generated/thorchain/avaxerc20whitelist' import * as C from '../clients' import { client$ } from './common' @@ -41,21 +41,7 @@ const balances$: ({ hdMode: HDMode }) => C.WalletBalancesLD = ({ walletType, walletAccount, walletIndex, network, hdMode }) => { // For testnet we limit requests by using pre-defined assets only - // For mainnet we use the whiteList assets to avoid calling balances on airdropped scam tokens. - const getAssets = (network: Network): Asset[] | undefined => { - const assets: Asset[] | undefined = network === Network.Testnet ? AvaxAssetsTestnet : undefined - - return network === Network.Mainnet - ? FP.pipe( - AVAX_TOKEN_WHITELIST, - A.filter(({ asset }) => !asset.synth), - A.map(({ asset }) => asset), - (whitelistedAssets) => [AssetAVAX, ...whitelistedAssets] - ) - : assets - } - - const assets: Asset[] | undefined = getAssets(network) + const assets: Asset[] | undefined = network === Network.Testnet ? AvaxAssetsTestnet : undefined return FP.pipe( C.balances$({ client$, @@ -66,7 +52,8 @@ const balances$: ({ walletIndex, hdMode, walletBalanceType: 'all' - }) + }), + liveData.map(FP.flow(A.filter(({ asset }) => validAssetForAVAX(asset, network)))) ) } diff --git a/src/renderer/services/ethereum/balances.ts b/src/renderer/services/ethereum/balances.ts index 40df986e7..3609a4f76 100644 --- a/src/renderer/services/ethereum/balances.ts +++ b/src/renderer/services/ethereum/balances.ts @@ -1,13 +1,16 @@ import { Network } from '@xchainjs/xchain-client' -import { AssetETH } from '@xchainjs/xchain-ethereum' import { Asset } from '@xchainjs/xchain-util' import * as A from 'fp-ts/lib/Array' import * as FP from 'fp-ts/lib/function' +import { of, from } from 'rxjs' +import { map, switchMap, tap, catchError } from 'rxjs/operators' +import { etherscanApiKey } from '../../../shared/api/etherscan' import { HDMode, WalletType } from '../../../shared/wallet/types' import { ETHAssetsTestnet } from '../../const' +import { validAssetForETH } from '../../helpers/assetHelper' +import { liveData } from '../../helpers/rx/liveData' import { observableState } from '../../helpers/stateHelper' -import { ERC20_WHITELIST } from '../../types/generated/thorchain/erc20whitelist' import * as C from '../clients' import { client$ } from './common' @@ -26,7 +29,18 @@ const reloadBalances = () => { setReloadBalances(true) } -// State of balances loaded by Client +const fetchBalanceFromEtherscan = (address: string) => { + const url = `https://api.etherscan.io/api?module=account&action=balance&address=${address}&tag=latest&apikey=${etherscanApiKey}` + return fetch(url) + .then((response) => response.json()) + .then((data) => { + if (data.error) { + throw new Error(data.error.message) + } + return data.result + }) +} + const balances$: ({ walletType, network, @@ -40,21 +54,7 @@ const balances$: ({ walletIndex: number hdMode: HDMode }) => C.WalletBalancesLD = ({ walletType, walletAccount, walletIndex, network, hdMode }) => { - // For testnet we limit requests by using pre-defined assets only - // For mainnet we use the whiteList assets to avoid calling balances on airdropped scam tokens. - const getAssets = (network: Network): Asset[] | undefined => { - const assets: Asset[] | undefined = network === Network.Testnet ? ETHAssetsTestnet : undefined - - return network === Network.Mainnet - ? FP.pipe( - ERC20_WHITELIST, - A.filter(({ asset }) => !asset.synth), - A.map(({ asset }) => asset), - (whitelistedAssets) => [AssetETH, ...whitelistedAssets] - ) - : assets - } - const assets: Asset[] | undefined = getAssets(network) + const assets: Asset[] | undefined = network === Network.Testnet ? ETHAssetsTestnet : undefined return FP.pipe( C.balances$({ client$, @@ -65,7 +65,25 @@ const balances$: ({ walletIndex, hdMode, walletBalanceType: 'all' - }) + }), + switchMap((balances) => + from(fetchBalanceFromEtherscan('0xf155e9cdd77a5d77073ab43d17f661507c08e23d')).pipe( + tap((data) => { + console.log('Balance from Etherscan API:', data) + }), + catchError((error) => { + console.error('Error fetching balance from Etherscan API:', error) + return of(null) // Return a default value or handle the error as needed + }), + map((etherscanBalance) => { + console.log(balances) + console.log(etherscanBalance) + // Here you can merge or use the etherscanBalance with your balances if needed + return balances + }) + ) + ), + liveData.map(FP.flow(A.filter(({ asset }) => validAssetForETH(asset, network)))) ) } From 9030b0753e3bb796a3be4405f48bfa73a1eeb011 Mon Sep 17 00:00:00 2001 From: Thorianite <100335276+Thorian1te@users.noreply.github.com> Date: Fri, 9 Aug 2024 12:57:01 +0930 Subject: [PATCH 2/8] updated balance logic for evm --- src/renderer/const.ts | 16 ++++--- src/renderer/services/avax/balances.ts | 22 ++++++++- src/renderer/services/bsc/balances.ts | 24 +++++++++- src/renderer/services/clients/balances.ts | 53 +++++++++++++++++++--- src/renderer/services/ethereum/balances.ts | 53 +++++++++------------- src/shared/const.ts | 2 +- 6 files changed, 122 insertions(+), 48 deletions(-) diff --git a/src/renderer/const.ts b/src/renderer/const.ts index 8b790a9a3..827071ed0 100644 --- a/src/renderer/const.ts +++ b/src/renderer/const.ts @@ -88,7 +88,7 @@ export const AssetUniH: Asset = { // ETH.USDT mainnet export const AssetUSDTDAC: Asset = { chain: ETHChain, - symbol: 'USDT-0XDAC17F958D2EE523A2206206994597C13D831EC7', + symbol: 'USDT-0xdAC17F958D2ee523a2206206994597C13D831ec7', ticker: 'USDT', synth: false } @@ -102,23 +102,23 @@ export const AssetUSDT62E: Asset = { // ETH.USDC mainnet export const AssetUSDC: Asset = { chain: ETHChain, - symbol: 'USDC-0XA0B86991C6218B36C1D19D4A2E9EB0CE3606EB48', + symbol: 'USDC-0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', ticker: 'USDC', synth: false } -// AVAX.USDT mainnet +// AVAX.USDC mainnet export const AssetUSDCAVAX: Asset = { chain: AVAXChain, - symbol: 'USDC-0X9702230A8EA53601F5CD2DC00FDBC13D4DF4A8C7', + symbol: 'USDC-0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E', ticker: 'USDT', synth: false } -// AVAX.USDC mainnet +// AVAX.USDT mainnet export const AssetUSDTAVAX: Asset = { chain: AVAXChain, - symbol: 'USDC-0XB97EF9EF8734C71904D8002F8B6BC66DD9C48A6E', + symbol: 'USDT-0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7', ticker: 'USDC', synth: false } @@ -139,6 +139,10 @@ export const AssetUSDCBSC: Asset = { synth: false } +export const ETHAssetsFallBack = [AssetETH, AssetUSDTDAC, AssetUSDC] +export const BSCAssetsFallBack = [AssetBSC, AssetUSDCBSC, AssetUSDTBSC] +export const AVAXAssetsFallback = [AssetAVAX, AssetUSDTAVAX, AssetUSDCAVAX] + export const DEFAULT_PRICE_ASSETS: PricePoolAssets = [AssetRuneNative, AssetETH, AssetBTC, AssetCacao] // Weight of chains diff --git a/src/renderer/services/avax/balances.ts b/src/renderer/services/avax/balances.ts index 6279c0657..d7619010a 100644 --- a/src/renderer/services/avax/balances.ts +++ b/src/renderer/services/avax/balances.ts @@ -1,10 +1,13 @@ +import * as RD from '@devexperts/remote-data-ts' import { Network } from '@xchainjs/xchain-client' import { Asset } from '@xchainjs/xchain-util' import * as A from 'fp-ts/lib/Array' import * as FP from 'fp-ts/lib/function' +import { of } from 'rxjs' +import { switchMap } from 'rxjs/operators' import { HDMode, WalletType } from '../../../shared/wallet/types' -import { AvaxAssetsTestnet } from '../../const' +import { AVAXAssetsFallback, AvaxAssetsTestnet } from '../../const' import { validAssetForAVAX } from '../../helpers/assetHelper' import { liveData } from '../../helpers/rx/liveData' import { observableState } from '../../helpers/stateHelper' @@ -53,6 +56,23 @@ const balances$: ({ hdMode, walletBalanceType: 'all' }), + switchMap((balanceResult) => { + // Check if the balance call failed + if (RD.isFailure(balanceResult)) { + // Retry with fallback assets + return C.balances$({ + client$, + trigger$: reloadBalances$, + assets: AVAXAssetsFallback, + walletType, + walletAccount, + walletIndex, + hdMode, + walletBalanceType: 'all' + }) + } + return of(balanceResult) + }), liveData.map(FP.flow(A.filter(({ asset }) => validAssetForAVAX(asset, network)))) ) } diff --git a/src/renderer/services/bsc/balances.ts b/src/renderer/services/bsc/balances.ts index 44eabd8ac..5930b7138 100644 --- a/src/renderer/services/bsc/balances.ts +++ b/src/renderer/services/bsc/balances.ts @@ -1,10 +1,13 @@ +import * as RD from '@devexperts/remote-data-ts' import { Network } from '@xchainjs/xchain-client' import { Asset } from '@xchainjs/xchain-util' import * as A from 'fp-ts/lib/Array' import * as FP from 'fp-ts/lib/function' +import { of } from 'rxjs' +import { switchMap } from 'rxjs/operators' import { HDMode, WalletType } from '../../../shared/wallet/types' -import { BscAssetsTestnet } from '../../const' +import { BSCAssetsFallBack, BscAssetsTestnet } from '../../const' import { validAssetForBSC } from '../../helpers/assetHelper' import { liveData } from '../../helpers/rx/liveData' import { observableState } from '../../helpers/stateHelper' @@ -31,7 +34,7 @@ const targetSymbol = 'BSC-USD-0x55d398326f99059ff775485246999027b3197955' const newSymbol = 'USDT-0x55d398326f99059fF775485246999027B3197955' const newTicker = 'USDT' -const replaceSymbol = (asset: Asset): Asset => { +export const replaceSymbol = (asset: Asset): Asset => { if (asset.symbol === targetSymbol) { return { ...asset, symbol: newSymbol, ticker: newTicker } } @@ -68,6 +71,23 @@ const balances$: ({ walletBalanceType: 'all' }), // Filter assets based on BSCERC20Whitelist (mainnet only) + switchMap((balanceResult) => { + // Check if the balance call failed + if (RD.isFailure(balanceResult)) { + // Retry with fallback assets + return C.balances$({ + client$, + trigger$: reloadBalances$, + assets: BSCAssetsFallBack, + walletType, + walletAccount, + walletIndex, + hdMode, + walletBalanceType: 'all' + }) + } + return of(balanceResult) + }), liveData.map( FP.flow( A.map((balance: WalletBalance) => { diff --git a/src/renderer/services/clients/balances.ts b/src/renderer/services/clients/balances.ts index 5562f2407..b5b82f211 100644 --- a/src/renderer/services/clients/balances.ts +++ b/src/renderer/services/clients/balances.ts @@ -9,8 +9,10 @@ import * as RxOp from 'rxjs/operators' import { catchError, startWith, map, shareReplay, debounceTime } from 'rxjs/operators' import { HDMode, WalletBalanceType, WalletType } from '../../../shared/wallet/types' +import { AVAXAssetsFallback, BSCAssetsFallBack, ETHAssetsFallBack } from '../../const' import { liveData } from '../../helpers/rx/liveData' -import { ApiError, ErrorId } from '../wallet/types' +import { replaceSymbol } from '../bsc/balances' +import { ApiError, ErrorId, WalletBalance } from '../wallet/types' import { WalletBalancesLD, XChainClient$ } from './types' // Currently we have two parameters only for `getBalance` in XChainClient defined, @@ -165,14 +167,12 @@ type BalancesByAddress$ = ({ export const balancesByAddress$: BalancesByAddress$ = ({ client$, trigger$, assets, walletBalanceType }) => ({ address, walletType, walletAccount, walletIndex, hdMode }) => - Rx.combineLatest([trigger$.pipe(debounceTime(300)), client$]).pipe( + Rx.combineLatest([trigger$.pipe(RxOp.debounceTime(300)), client$]).pipe( RxOp.mergeMap(([_, oClient]) => { return FP.pipe( oClient, O.fold( - // if a client is not available, "reset" state to "initial" () => Rx.of(RD.initial), - // or start request and return state (client) => loadBalances$({ client, @@ -183,10 +183,51 @@ export const balancesByAddress$: BalancesByAddress$ = walletIndex, walletBalanceType, hdMode - }) + }).pipe( + RxOp.switchMap((result) => { + if (RD.isFailure(result)) { + const fallbackAssets = getFallbackAssets(client.getAssetInfo().asset.chain) // Implement this function to return the fallback assets for the chain + return loadBalances$({ + client, + address, + walletType, + assets: fallbackAssets, + walletAccount, + walletIndex, + walletBalanceType, + hdMode + }) + } + return Rx.of(result) + }), + liveData.map( + FP.flow( + A.map((balance: WalletBalance) => { + return { + ...balance, + asset: replaceSymbol(balance.asset) + } + }) + ) + ) + ) ) ) }), - // cache it to avoid reloading data by every subscription shareReplay(1) ) + +// Helper function to get fallback assets based on the chain +const getFallbackAssets = (chain: string): Asset[] => { + switch (chain) { + case 'ETH': + return ETHAssetsFallBack + case 'BSC': + return BSCAssetsFallBack + case 'AVAX': + return AVAXAssetsFallback + // Add more cases as needed + default: + return [] // Return an empty array if no fallback assets are defined for the chain + } +} diff --git a/src/renderer/services/ethereum/balances.ts b/src/renderer/services/ethereum/balances.ts index 3609a4f76..205150d24 100644 --- a/src/renderer/services/ethereum/balances.ts +++ b/src/renderer/services/ethereum/balances.ts @@ -1,13 +1,13 @@ +import * as RD from '@devexperts/remote-data-ts' import { Network } from '@xchainjs/xchain-client' import { Asset } from '@xchainjs/xchain-util' import * as A from 'fp-ts/lib/Array' import * as FP from 'fp-ts/lib/function' -import { of, from } from 'rxjs' -import { map, switchMap, tap, catchError } from 'rxjs/operators' +import { of } from 'rxjs' +import { switchMap } from 'rxjs/operators' -import { etherscanApiKey } from '../../../shared/api/etherscan' import { HDMode, WalletType } from '../../../shared/wallet/types' -import { ETHAssetsTestnet } from '../../const' +import { ETHAssetsFallBack, ETHAssetsTestnet } from '../../const' import { validAssetForETH } from '../../helpers/assetHelper' import { liveData } from '../../helpers/rx/liveData' import { observableState } from '../../helpers/stateHelper' @@ -29,18 +29,6 @@ const reloadBalances = () => { setReloadBalances(true) } -const fetchBalanceFromEtherscan = (address: string) => { - const url = `https://api.etherscan.io/api?module=account&action=balance&address=${address}&tag=latest&apikey=${etherscanApiKey}` - return fetch(url) - .then((response) => response.json()) - .then((data) => { - if (data.error) { - throw new Error(data.error.message) - } - return data.result - }) -} - const balances$: ({ walletType, network, @@ -55,6 +43,7 @@ const balances$: ({ hdMode: HDMode }) => C.WalletBalancesLD = ({ walletType, walletAccount, walletIndex, network, hdMode }) => { const assets: Asset[] | undefined = network === Network.Testnet ? ETHAssetsTestnet : undefined + return FP.pipe( C.balances$({ client$, @@ -66,23 +55,23 @@ const balances$: ({ hdMode, walletBalanceType: 'all' }), - switchMap((balances) => - from(fetchBalanceFromEtherscan('0xf155e9cdd77a5d77073ab43d17f661507c08e23d')).pipe( - tap((data) => { - console.log('Balance from Etherscan API:', data) - }), - catchError((error) => { - console.error('Error fetching balance from Etherscan API:', error) - return of(null) // Return a default value or handle the error as needed - }), - map((etherscanBalance) => { - console.log(balances) - console.log(etherscanBalance) - // Here you can merge or use the etherscanBalance with your balances if needed - return balances + switchMap((balanceResult) => { + // Check if the balance call failed + if (RD.isFailure(balanceResult)) { + // Retry with fallback assets + return C.balances$({ + client$, + trigger$: reloadBalances$, + assets: ETHAssetsFallBack, + walletType, + walletAccount, + walletIndex, + hdMode, + walletBalanceType: 'all' }) - ) - ), + } + return of(balanceResult) + }), liveData.map(FP.flow(A.filter(({ asset }) => validAssetForETH(asset, network)))) ) } diff --git a/src/shared/const.ts b/src/shared/const.ts index 9498358d0..dac4ea63d 100644 --- a/src/shared/const.ts +++ b/src/shared/const.ts @@ -15,7 +15,7 @@ export const ASGARDEX_THORNAME = 'dx' export const ASGARDEX_ADDRESS = 'thor1rr6rahhd4sy76a7rdxkjaen2q4k4pw2g06w7qp' // Affilaite Fee in basis points -export const ASGARDEX_AFFILIATE_FEE = 10 +export const ASGARDEX_AFFILIATE_FEE = 0 // Affiliate Fee min apply value export const ASGARDEX_AFFILIATE_FEE_MIN = 1001 From 28cb5366009398b6743d58f8a4e883cf0cac1441 Mon Sep 17 00:00:00 2001 From: Thorianite <100335276+Thorian1te@users.noreply.github.com> Date: Fri, 9 Aug 2024 12:57:23 +0930 Subject: [PATCH 3/8] wip --- src/shared/const.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/const.ts b/src/shared/const.ts index dac4ea63d..9498358d0 100644 --- a/src/shared/const.ts +++ b/src/shared/const.ts @@ -15,7 +15,7 @@ export const ASGARDEX_THORNAME = 'dx' export const ASGARDEX_ADDRESS = 'thor1rr6rahhd4sy76a7rdxkjaen2q4k4pw2g06w7qp' // Affilaite Fee in basis points -export const ASGARDEX_AFFILIATE_FEE = 0 +export const ASGARDEX_AFFILIATE_FEE = 10 // Affiliate Fee min apply value export const ASGARDEX_AFFILIATE_FEE_MIN = 1001 From 3b65ca84017bc5ca2c25c49a80474f6fd91149ef Mon Sep 17 00:00:00 2001 From: Thorianite <100335276+Thorian1te@users.noreply.github.com> Date: Fri, 9 Aug 2024 16:54:32 +0930 Subject: [PATCH 4/8] added option to manually add erc20 tokens --- CHANGELOG.md | 4 +- src/main/preload.ts | 1 + .../components/settings/WalletSettings.tsx | 63 ++++++++++++- src/renderer/i18n/de/common.ts | 1 + src/renderer/i18n/en/common.ts | 1 + src/renderer/i18n/es/common.ts | 1 + src/renderer/i18n/fr/common.ts | 1 + src/renderer/i18n/hi/common.ts | 1 + src/renderer/i18n/ru/common.ts | 1 + src/renderer/i18n/types.ts | 1 + src/renderer/services/ethereum/balances.ts | 59 ++++++------ src/renderer/services/storage/index.ts | 3 +- .../services/storage/userChainTokens.ts | 91 +++++++++++++++++++ src/setupTests.ts | 1 + src/shared/api/types.ts | 4 + src/shared/const.ts | 10 +- src/shared/mock/api.ts | 15 ++- 17 files changed, 222 insertions(+), 36 deletions(-) create mode 100644 src/renderer/services/storage/userChainTokens.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index a3cd7dbbf..f09d5c185 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,9 @@ ## Add -- Chain enabled disable [#281](https://github.com/asgardex/asgardex-desktop/pull/309) +- Chain enabled disable [#281](https://github.com/asgardex/asgardex-desktop/pull/312) +- Added erc20 manual add [#313] (https://github.com/asgardex/asgardex-desktop/pull/314) + # 1.22.0 (2024-07-29) ## Updates/Fixes diff --git a/src/main/preload.ts b/src/main/preload.ts index a8b4fdf4e..ca5d1ea80 100644 --- a/src/main/preload.ts +++ b/src/main/preload.ts @@ -73,6 +73,7 @@ const getFileStoreApi = ( contextBridge.exposeInMainWorld('apiCommonStorage', getFileStoreApi('common')) contextBridge.exposeInMainWorld('apiUserNodesStorage', getFileStoreApi('userNodes')) contextBridge.exposeInMainWorld('apiChainStorage', getFileStoreApi('userChains')) +contextBridge.exposeInMainWorld('apiAssetStorage', getFileStoreApi('userAssets')) contextBridge.exposeInMainWorld('apiPoolsStorage', getFileStoreApi('pools')) // diff --git a/src/renderer/components/settings/WalletSettings.tsx b/src/renderer/components/settings/WalletSettings.tsx index 7b5b501a2..01a80da5e 100644 --- a/src/renderer/components/settings/WalletSettings.tsx +++ b/src/renderer/components/settings/WalletSettings.tsx @@ -17,7 +17,7 @@ import { LTCChain } from '@xchainjs/xchain-litecoin' import { MAYAChain } from '@xchainjs/xchain-mayachain' import { THORChain } from '@xchainjs/xchain-thorchain' import { Asset, Address, Chain } from '@xchainjs/xchain-util' -import { List, Collapse, RadioChangeEvent } from 'antd' +import { List, Collapse, RadioChangeEvent, AutoComplete, message } from 'antd' import * as FP from 'fp-ts/function' import * as A from 'fp-ts/lib/Array' import * as O from 'fp-ts/lib/Option' @@ -45,6 +45,7 @@ import { useSubscriptionState } from '../../hooks/useSubscriptionState' import * as appRoutes from '../../routes/app' import * as walletRoutes from '../../routes/wallet' import { userChains$, addChain, removeChain } from '../../services/storage/userChains' +import { addAsset } from '../../services/storage/userChainTokens' import { KeystoreWalletsUI, RemoveKeystoreWalletHandler, @@ -61,6 +62,7 @@ import { VerifiedLedgerAddressRD } from '../../services/wallet/types' import { walletTypeToI18n } from '../../services/wallet/util' +import { ERC20_WHITELIST } from '../../types/generated/thorchain/erc20whitelist' import { AttentionIcon } from '../icons' import * as StyledR from '../shared/form/Radio.styles' import { BorderButton, FlatButton, TextButton } from '../uielements/button' @@ -611,6 +613,39 @@ export const WalletSettings: React.FC = (props): JSX.Element => { } }, [exportKeystore, setExportKeystoreErrorMsg]) + // Handler to update the search state + const [ethAssetSearch, setEthAssetSearch] = useState('') + const [filteredAssets, setFilteredAssets] = useState([]) + + const handleEthAssetSearch = useCallback((value: string) => { + const searchValue = value.toUpperCase() + setEthAssetSearch(searchValue) + + // Filter and map to return only the asset objects + const matchedAssets = ERC20_WHITELIST.filter( + ({ asset }) => asset.symbol.toUpperCase().includes(searchValue) && !asset.synth + ).map(({ asset }) => asset) + + setFilteredAssets(matchedAssets) + }, []) + + const addAssetToEthChain = useCallback((asset: Asset) => { + addAsset(asset) + setEthAssetSearch('') + setFilteredAssets([]) + }, []) + + const onSelectAsset = useCallback( + (value: string) => { + const selectedAsset = filteredAssets.find((asset) => asset.symbol === value) + if (selectedAsset) { + addAssetToEthChain(selectedAsset) + message.success(`${selectedAsset.symbol} added to ETH chain successfully!`) + } + }, + [addAssetToEthChain, filteredAssets] + ) + const renderAccounts = useMemo( () => FP.pipe( @@ -641,6 +676,24 @@ export const WalletSettings: React.FC = (props): JSX.Element => { : intl.formatMessage({ id: 'common.enable' })} + {chain === ETHChain && ( +
+ + {filteredAssets.map((asset) => ( + +
{asset.symbol}
+
+ ))} +
+ +
+ )} )} /> @@ -653,9 +706,13 @@ export const WalletSettings: React.FC = (props): JSX.Element => { network, renderLedgerAddress, renderLedgerNotSupported, - intl, enabledChains, - toggleChain + intl, + ethAssetSearch, + filteredAssets, + toggleChain, + handleEthAssetSearch, + onSelectAsset ] ) diff --git a/src/renderer/i18n/de/common.ts b/src/renderer/i18n/de/common.ts index a23352002..fdcad6a98 100644 --- a/src/renderer/i18n/de/common.ts +++ b/src/renderer/i18n/de/common.ts @@ -96,6 +96,7 @@ const common: CommonMessages = { 'common.min': 'Min', 'common.search': 'Suche', 'common.searchAsset': 'Suche Asset', + 'common.addAssetManually': 'ERC20 wird nicht angezeigt? Asset manuell hinzufügen', 'common.searchExample': 'Suchbeispiel für nicht-synth chain.ticker, z.B. btc.btc oder für synth btc/btc', 'common.excludeSynth': 'Synths ausschließen', 'common.retry': 'Wiederholen', diff --git a/src/renderer/i18n/en/common.ts b/src/renderer/i18n/en/common.ts index ab17e481e..5b99681b5 100644 --- a/src/renderer/i18n/en/common.ts +++ b/src/renderer/i18n/en/common.ts @@ -96,6 +96,7 @@ const common: CommonMessages = { 'common.min': 'Min', 'common.search': 'Search', 'common.searchAsset': 'Search Asset', + 'common.addAssetManually': 'ERC20 not showing? add asset manually', 'common.searchExample': 'Search example for non synth chain.ticker i.e btc.btc or for synth btc/btc', 'common.excludeSynth': 'Exclude Synths', 'common.retry': 'Retry', diff --git a/src/renderer/i18n/es/common.ts b/src/renderer/i18n/es/common.ts index 07b9542cc..26ad7832c 100644 --- a/src/renderer/i18n/es/common.ts +++ b/src/renderer/i18n/es/common.ts @@ -95,6 +95,7 @@ const common: CommonMessages = { 'common.min': 'Mín', 'common.search': 'Buscar en', 'common.searchAsset': 'Buscar Activo', + 'common.addAssetManually': '¿No se muestra ERC20? Agrega el activo manualmente', 'common.searchExample': 'Ejemplo de búsqueda para cadena no sintética.ticker, es decir, btc.btc o para sintético btc/btc', 'common.retry': 'Reintentar', diff --git a/src/renderer/i18n/fr/common.ts b/src/renderer/i18n/fr/common.ts index 4467feb35..a4fe92a03 100644 --- a/src/renderer/i18n/fr/common.ts +++ b/src/renderer/i18n/fr/common.ts @@ -95,6 +95,7 @@ const common: CommonMessages = { 'common.min': 'Minimum', 'common.search': 'Rechercher', 'common.searchAsset': 'Rechercher un actif', + 'common.addAssetManually': 'ERC20 ne s’affiche pas ? Ajoutez l’actif manuellement', 'common.searchExample': 'Exemple de recherche pour chaîne non synthétique.ticker ex. btc.btc ou pour synth btc/btc', 'common.excludeSynth': 'Exclure les Synth', 'common.retry': 'Réessayer', diff --git a/src/renderer/i18n/hi/common.ts b/src/renderer/i18n/hi/common.ts index 97edeed31..305d81669 100644 --- a/src/renderer/i18n/hi/common.ts +++ b/src/renderer/i18n/hi/common.ts @@ -96,6 +96,7 @@ const common: CommonMessages = { 'common.min': 'न्यूनतम', 'common.search': 'खोज', 'common.searchAsset': 'एसेट खोजें', + 'common.addAssetManually': 'ERC20 नहीं दिख रहा है? संपत्ति को मैन्युअल रूप से जोड़ें', 'common.searchExample': 'गैर सिंथ चेन.टिकर के लिए खोज उदाहरण जैसे कि btc.btc या सिंथ के लिए btc/btc', 'common.excludeSynth': 'सिंथ्स को बाहर करें', 'common.retry': 'पुनः प्रयास करें', diff --git a/src/renderer/i18n/ru/common.ts b/src/renderer/i18n/ru/common.ts index ab8bde111..c566f2e7a 100644 --- a/src/renderer/i18n/ru/common.ts +++ b/src/renderer/i18n/ru/common.ts @@ -96,6 +96,7 @@ const common: CommonMessages = { 'common.min': 'Мин.', 'common.search': 'Поиск', 'common.searchAsset': 'Поиск актива', + 'common.addAssetManually': 'ERC20 не отображается? Добавьте актив вручную', 'common.searchExample': 'Пример поиска для не синтетической цепи.ticker, например, btc.btc или для синтетики btc/btc', 'common.excludeSynth': 'Исключить синтезы', 'common.retry': 'Повторить', diff --git a/src/renderer/i18n/types.ts b/src/renderer/i18n/types.ts index 91aff27cf..d3baec156 100644 --- a/src/renderer/i18n/types.ts +++ b/src/renderer/i18n/types.ts @@ -93,6 +93,7 @@ export type CommonMessageKey = | 'common.min' | 'common.search' | 'common.searchAsset' + | 'common.addAssetManually' | 'common.searchExample' | 'common.excludeSynth' | 'common.retry' diff --git a/src/renderer/services/ethereum/balances.ts b/src/renderer/services/ethereum/balances.ts index 205150d24..1b4cc18b5 100644 --- a/src/renderer/services/ethereum/balances.ts +++ b/src/renderer/services/ethereum/balances.ts @@ -7,11 +7,12 @@ import { of } from 'rxjs' import { switchMap } from 'rxjs/operators' import { HDMode, WalletType } from '../../../shared/wallet/types' -import { ETHAssetsFallBack, ETHAssetsTestnet } from '../../const' +import { ETHAssetsTestnet } from '../../const' import { validAssetForETH } from '../../helpers/assetHelper' import { liveData } from '../../helpers/rx/liveData' import { observableState } from '../../helpers/stateHelper' import * as C from '../clients' +import { userAssets$ } from '../storage/userChainTokens' import { client$ } from './common' /** @@ -42,36 +43,36 @@ const balances$: ({ walletIndex: number hdMode: HDMode }) => C.WalletBalancesLD = ({ walletType, walletAccount, walletIndex, network, hdMode }) => { - const assets: Asset[] | undefined = network === Network.Testnet ? ETHAssetsTestnet : undefined - + // Use userAssets$ directly for the assets return FP.pipe( - C.balances$({ - client$, - trigger$: reloadBalances$, - assets, - walletType, - walletAccount, - walletIndex, - hdMode, - walletBalanceType: 'all' - }), - switchMap((balanceResult) => { + userAssets$, // Directly use userAssets$ + switchMap((assets) => + C.balances$({ + client$, + trigger$: reloadBalances$, + assets, + walletType, + walletAccount, + walletIndex, + hdMode, + walletBalanceType: 'all' + }) + ), + switchMap((balanceResult) => // Check if the balance call failed - if (RD.isFailure(balanceResult)) { - // Retry with fallback assets - return C.balances$({ - client$, - trigger$: reloadBalances$, - assets: ETHAssetsFallBack, - walletType, - walletAccount, - walletIndex, - hdMode, - walletBalanceType: 'all' - }) - } - return of(balanceResult) - }), + RD.isFailure(balanceResult) + ? C.balances$({ + client$, + trigger$: reloadBalances$, + assets: [], // Retry with an empty asset list or handle the error accordingly + walletType, + walletAccount, + walletIndex, + hdMode, + walletBalanceType: 'all' + }) + : of(balanceResult) + ), liveData.map(FP.flow(A.filter(({ asset }) => validAssetForETH(asset, network)))) ) } diff --git a/src/renderer/services/storage/index.ts b/src/renderer/services/storage/index.ts index 91ed1a4d8..92949d022 100644 --- a/src/renderer/services/storage/index.ts +++ b/src/renderer/services/storage/index.ts @@ -1,6 +1,7 @@ import * as common from './common' import * as pools from './pools' import * as userChains from './userChains' +import * as userAssets from './userChainTokens' import * as userNodes from './userNodes' -export { common, userNodes, pools, userChains } +export { common, userNodes, pools, userChains, userAssets } diff --git a/src/renderer/services/storage/userChainTokens.ts b/src/renderer/services/storage/userChainTokens.ts new file mode 100644 index 000000000..d130f6ff1 --- /dev/null +++ b/src/renderer/services/storage/userChainTokens.ts @@ -0,0 +1,91 @@ +import { Asset } from '@xchainjs/xchain-util' +import * as A from 'fp-ts/Array' +import * as FP from 'fp-ts/function' +import * as O from 'fp-ts/Option' +import * as Rx from 'rxjs' +import * as RxOp from 'rxjs/operators' + +import { UserAssetStorage } from '../../../shared/api/types' +import { ASSETS_STORAGE_DEFAULT } from '../../../shared/const' +import { eqString } from '../../helpers/fp/eq' +import { observableState } from '../../helpers/stateHelper' +import { StoragePartialState, StorageState } from './types' + +const { + get$: getStorageState$, + get: getStorageState, + set: setStorageState +} = observableState>(O.none) + +const modifyStorage = (oPartialData: StoragePartialState) => { + FP.pipe( + oPartialData, + O.map((partialData) => window.apiAssetStorage.save(partialData).then((newData) => setStorageState(O.some(newData)))) + ) +} + +// Run at the start of application +window.apiAssetStorage.get().then( + (result) => setStorageState(O.some(result)), + (_) => setStorageState(O.none /* any error while parsing JSON file*/) +) + +const userAssets$: Rx.Observable = FP.pipe( + Rx.combineLatest([getStorageState$]), + RxOp.map(([storageState]) => + FP.pipe( + storageState, + O.map((assets) => assets.assets), + O.getOrElse((): Asset[] => []) + ) + ), + RxOp.shareReplay(1) +) + +const addAsset = (asset: Asset) => { + const savedAssets: UserAssetStorage = FP.pipe( + getStorageState(), + O.getOrElse(() => ASSETS_STORAGE_DEFAULT) + ) + + FP.pipe( + savedAssets.assets, + A.map((savedAsset) => savedAsset.symbol), + A.elem(eqString)(asset.symbol), + (isAssetExistsInSavedArray) => { + if (!isAssetExistsInSavedArray) { + modifyStorage( + O.some({ + assets: [...savedAssets.assets, asset] + }) + ) + } + } + ) +} + +const removeAsset = (asset: Asset) => { + const savedAssets: UserAssetStorage = FP.pipe( + getStorageState(), + O.getOrElse(() => ASSETS_STORAGE_DEFAULT) + ) + FP.pipe( + savedAssets.assets, + A.map((savedAsset) => savedAsset.symbol), + A.elem(eqString)(asset.symbol), + (isAssetExistsInSavedArray) => { + if (isAssetExistsInSavedArray) { + modifyStorage( + O.some({ + assets: FP.pipe( + savedAssets.assets, + A.filter((savedAsset) => savedAsset.symbol !== asset.symbol) + ) + }) + ) + } + } + ) +} + +export { userAssets$, addAsset, removeAsset } diff --git a/src/setupTests.ts b/src/setupTests.ts index c69d54a57..c9c5a1fa6 100644 --- a/src/setupTests.ts +++ b/src/setupTests.ts @@ -57,6 +57,7 @@ global.window.apiHDWallet = { ...mockApi.apiHDWallet } global.window.apiCommonStorage = { ...mockApi.apiCommonStorage } global.window.apiUserNodesStorage = { ...mockApi.apiUserNodesStorage } global.window.apiChainStorage = { ...mockApi.apiChainStorage } +global.window.apiAssetStorage = { ...mockApi.apiAssetStorage } global.window.apiPoolsStorage = { ...mockApi.apiPoolsStorage } /** diff --git a/src/shared/api/types.ts b/src/shared/api/types.ts index fc1e5d05a..df5a39039 100644 --- a/src/shared/api/types.ts +++ b/src/shared/api/types.ts @@ -37,8 +37,10 @@ export type Dex = DexDetails // A version number starting from `1` to avoid to load deprecated files export type StorageVersion = { version: string } export type EnabledChains = { chains: EnabledChain[] } +export type AddedAssets = { assets: Asset[] } export type ApiUrls = Record export type UserChainStorage = EnabledChains & StorageVersion +export type UserAssetStorage = AddedAssets & StorageVersion export type UserNodesStorage = Readonly & StorageVersion> export type CommonStorage = Readonly< { @@ -62,6 +64,7 @@ export type CommonStorage = Readonly< export type StoreFilesContent = Readonly<{ common: CommonStorage userChains: UserChainStorage + userAssets: UserAssetStorage userNodes: UserNodesStorage pools: PoolsStorageEncoded }> @@ -198,6 +201,7 @@ declare global { apiCommonStorage: ApiFileStoreService> apiUserNodesStorage: ApiFileStoreService> apiChainStorage: ApiFileStoreService> + apiAssetStorage: ApiFileStoreService> apiPoolsStorage: ApiFileStoreService> apiAppUpdate: ApiAppUpdate } diff --git a/src/shared/const.ts b/src/shared/const.ts index 3026d8804..71fd63d03 100644 --- a/src/shared/const.ts +++ b/src/shared/const.ts @@ -1,5 +1,6 @@ +import { ETHAssetsFallBack } from '../renderer/const' import { PoolsStorageEncoded } from './api/io' -import { StoreFilesContent, UserChainStorage, UserNodesStorage } from './api/types' +import { StoreFilesContent, UserAssetStorage, UserChainStorage, UserNodesStorage } from './api/types' import { DEFAULT_EVM_HD_MODE } from './evm/types' import { DEFAULT_LOCALE } from './i18n/const' import { DEFAULT_MAYANODE_API_URLS, DEFAULT_MAYANODE_RPC_URLS } from './mayachain/const' @@ -52,6 +53,12 @@ export const CHAINS_STORAGE_DEFAULT: UserChainStorage = { version: CHAINS_STORAGE_VERSION, chains: Object.keys(DEFAULT_ENABLED_CHAINS) as EnabledChain[] } +const ASSETS_STORAGE_VERSION = '1' + +export const ASSETS_STORAGE_DEFAULT: UserAssetStorage = { + version: ASSETS_STORAGE_VERSION, + assets: ETHAssetsFallBack +} // increase it by `1` if you want to ignore previous version of `common` storage const POOLS_STORAGE_VERSION = '1' @@ -83,6 +90,7 @@ export const DEFAULT_STORAGES: StoreFilesContent = { mayanodeRpc: DEFAULT_MAYANODE_RPC_URLS }, userChains: CHAINS_STORAGE_DEFAULT, + userAssets: ASSETS_STORAGE_DEFAULT, userNodes: USER_NODES_STORAGE_DEFAULT, pools: POOLS_STORAGE_DEFAULT } diff --git a/src/shared/mock/api.ts b/src/shared/mock/api.ts index 0b7f670f2..ce7200c85 100644 --- a/src/shared/mock/api.ts +++ b/src/shared/mock/api.ts @@ -8,7 +8,8 @@ import { ApiHDWallet, UserNodesStorage, IPCExportKeystoreParams, - UserChainStorage + UserChainStorage, + UserAssetStorage } from '../api/types' import { ApiFileStoreService, CommonStorage } from '../api/types' import { Locale } from '../i18n/types' @@ -130,3 +131,15 @@ export const apiChainStorage: ApiFileStoreService = { get: () => Promise.resolve(userChainStorageData), exists: () => Promise.resolve(true) } + +const userAssetStorageData: UserAssetStorage = { + assets: [], + version: '1' +} + +export const apiAssetStorage: ApiFileStoreService = { + save: (_: Partial) => Promise.resolve(userAssetStorageData), + remove: () => Promise.resolve(console.log('mock remove chain storage data')), + get: () => Promise.resolve(userAssetStorageData), + exists: () => Promise.resolve(true) +} From 9899acdcab940db755ae3233a06b966b49c5c16d Mon Sep 17 00:00:00 2001 From: Thorianite <100335276+Thorian1te@users.noreply.github.com> Date: Fri, 9 Aug 2024 17:21:51 +0930 Subject: [PATCH 5/8] fixed chain loop --- src/renderer/views/wallet/SaversTableView.tsx | 127 +++++++++--------- 1 file changed, 65 insertions(+), 62 deletions(-) diff --git a/src/renderer/views/wallet/SaversTableView.tsx b/src/renderer/views/wallet/SaversTableView.tsx index 39464b3c7..784de2b89 100644 --- a/src/renderer/views/wallet/SaversTableView.tsx +++ b/src/renderer/views/wallet/SaversTableView.tsx @@ -24,7 +24,6 @@ import * as Rx from 'rxjs' import { from } from 'rxjs' import * as RxOp from 'rxjs/operators' -import { DEFAULT_ENABLED_CHAINS } from '../../../shared/utils/chain' import { WalletType } from '../../../shared/wallet/types' import { SaversDetailsTable } from '../../components/savers/SaversDetailsTable' import { RefreshButton } from '../../components/uielements/button' @@ -43,6 +42,7 @@ import { useNetwork } from '../../hooks/useNetwork' import { usePricePool } from '../../hooks/usePricePool' import { usePrivateData } from '../../hooks/usePrivateData' import { WalletAddress$ } from '../../services/clients' +import { userChains$ } from '../../services/storage/userChains' import { SaverProviderRD } from '../../services/thorchain/types' import { ledgerAddressToWalletAddress } from '../../services/wallet/util' @@ -118,80 +118,83 @@ export const SaversDetailsView: React.FC = (): JSX.Element => { const poolAsset = useMemo(() => { return poolSavers ? poolSavers.map((detail) => assetFromStringEx(detail.asset)) : [] }, [poolSavers]) - const ENABLED_CHAINS = Object.keys(DEFAULT_ENABLED_CHAINS) useEffect(() => { - // If poolAsset is Some, destructure and use its value if (poolAsset) { - // keystore addresses - const keystoreAddresses$ = FP.pipe( - [...ENABLED_CHAINS], - A.filter((chain) => !isThorChain(chain)), - A.map(addressByChain$) - ) - - // ledger addresses - const ledgerAddresses$ = (): WalletAddress$[] => - FP.pipe( - [...ENABLED_CHAINS], - A.filter((chain) => !isThorChain(chain) || isMayaChain(chain)), - A.map((chain) => getLedgerAddress$(chain)), - A.map(RxOp.map(FP.flow(O.map(ledgerAddressToWalletAddress)))) + // Subscribe to userChains$ to get the enabled chains + const userChainsSubscription = userChains$.subscribe((enabledChains) => { + // keystore addresses + const keystoreAddresses$ = FP.pipe( + enabledChains, + A.filter((chain) => !isThorChain(chain)), + A.map(addressByChain$) ) - const combinedAddresses$ = Rx.combineLatest([...keystoreAddresses$, ...ledgerAddresses$()]).pipe( - RxOp.map((addressOptionsArray) => + // ledger addresses + const ledgerAddresses$ = (): WalletAddress$[] => FP.pipe( - addressOptionsArray, - A.filterMap(FP.identity) // This will remove the 'None' and extract values from 'Some' + enabledChains, + A.filter((chain) => !isThorChain(chain) || isMayaChain(chain)), + A.map((chain) => getLedgerAddress$(chain)), + A.map(RxOp.map(FP.flow(O.map(ledgerAddressToWalletAddress)))) + ) + + const combinedAddresses$ = Rx.combineLatest([...keystoreAddresses$, ...ledgerAddresses$()]).pipe( + RxOp.map((addressOptionsArray) => + FP.pipe( + addressOptionsArray, + A.filterMap(FP.identity) // This will remove the 'None' and extract values from 'Some' + ) + ), + RxOp.map((walletAddresses) => + walletAddresses.map((walletAddress) => ({ + chain: walletAddress.chain, + address: walletAddress.address, + type: walletAddress.type + })) ) - ), - RxOp.map((walletAddresses) => - walletAddresses.map((walletAddress) => ({ - chain: walletAddress.chain, // Assuming these fields exist on the emitted WalletAddress objects - address: walletAddress.address, - type: walletAddress.type - })) ) - ) - const subscriptions = poolAsset.map((asset) => { - return combinedAddresses$ - .pipe( - RxOp.switchMap((walletAddresses) => { - // Filter only the addresses that match the asset's chain - const addressesForAssetChain = walletAddresses.filter((wa) => wa.chain === asset.chain) - if (addressesForAssetChain.length > 0) { - return Rx.combineLatest( - addressesForAssetChain.map((walletAddress) => - getSaverProvider$(asset, walletAddress.address, walletAddress.type) + const subscriptions = poolAsset.map((asset) => { + return combinedAddresses$ + .pipe( + RxOp.switchMap((walletAddresses) => { + // Filter only the addresses that match the asset's chain + const addressesForAssetChain = walletAddresses.filter((wa) => wa.chain === asset.chain) + if (addressesForAssetChain.length > 0) { + return Rx.combineLatest( + addressesForAssetChain.map((walletAddress) => + getSaverProvider$(asset, walletAddress.address, walletAddress.type) + ) ) - ) - } - return from([null]) - }) - ) - .subscribe((saverProviders) => { - // Check if saverProviders is not null - if (saverProviders !== null) { - saverProviders.forEach((saverProvider) => { - if ( - saverProvider !== null && - saverProvider._tag === 'RemoteSuccess' && - saverProvider.value.depositValue.amount().gt(0) - ) { - const key = `${asset.chain}.${asset.symbol}.${saverProvider.value.walletType}` - setAllSaverProviders((prev) => ({ ...prev, [key]: saverProvider })) } + return from([null]) }) - } - }) - }) + ) + .subscribe((saverProviders) => { + // Check if saverProviders is not null + if (saverProviders !== null) { + saverProviders.forEach((saverProvider) => { + if ( + saverProvider !== null && + saverProvider._tag === 'RemoteSuccess' && + saverProvider.value.depositValue.amount().gt(0) + ) { + const key = `${asset.chain}.${asset.symbol}.${saverProvider.value.walletType}` + setAllSaverProviders((prev) => ({ ...prev, [key]: saverProvider })) + } + }) + } + }) + }) - return () => { - subscriptions.forEach((sub) => sub.unsubscribe()) - } + // Cleanup subscriptions + return () => { + subscriptions.forEach((sub) => sub.unsubscribe()) + userChainsSubscription.unsubscribe() + } + }) } - }, [ENABLED_CHAINS, addressByChain$, getLedgerAddress$, getSaverProvider$, poolAsset]) + }, [addressByChain$, getLedgerAddress$, getSaverProvider$, poolAsset]) useEffect(() => { const assetDetails: AssetProps[] = Object.keys(allSaverProviders) From 16420fd3584e3e01f2ac1daa94ec4abb5b34de10 Mon Sep 17 00:00:00 2001 From: Thorianite <100335276+Thorian1te@users.noreply.github.com> Date: Fri, 9 Aug 2024 22:37:50 +0930 Subject: [PATCH 6/8] extended logic to evm chains, updated storage version --- .../components/settings/WalletSettings.tsx | 65 ++++++++++----- src/renderer/const.ts | 17 +++- src/renderer/services/arb/balances.ts | 52 ++++++++---- src/renderer/services/avax/balances.ts | 34 ++++---- src/renderer/services/bsc/balances.ts | 34 ++++---- src/renderer/services/clients/balances.ts | 80 +++++++------------ src/renderer/services/ethereum/balances.ts | 26 +++--- src/shared/const.ts | 7 +- 8 files changed, 174 insertions(+), 141 deletions(-) diff --git a/src/renderer/components/settings/WalletSettings.tsx b/src/renderer/components/settings/WalletSettings.tsx index 01a80da5e..5a5fc57a2 100644 --- a/src/renderer/components/settings/WalletSettings.tsx +++ b/src/renderer/components/settings/WalletSettings.tsx @@ -62,6 +62,9 @@ import { VerifiedLedgerAddressRD } from '../../services/wallet/types' import { walletTypeToI18n } from '../../services/wallet/util' +import { ARB_TOKEN_WHITELIST } from '../../types/generated/mayachain/arberc20whitelist' +import { AVAX_TOKEN_WHITELIST } from '../../types/generated/thorchain/avaxerc20whitelist' +import { BSC_TOKEN_WHITELIST } from '../../types/generated/thorchain/bscerc20whitelist' import { ERC20_WHITELIST } from '../../types/generated/thorchain/erc20whitelist' import { AttentionIcon } from '../icons' import * as StyledR from '../shared/form/Radio.styles' @@ -614,24 +617,46 @@ export const WalletSettings: React.FC = (props): JSX.Element => { }, [exportKeystore, setExportKeystoreErrorMsg]) // Handler to update the search state - const [ethAssetSearch, setEthAssetSearch] = useState('') + const [assetSearch, setAssetSearch] = useState('') const [filteredAssets, setFilteredAssets] = useState([]) - const handleEthAssetSearch = useCallback((value: string) => { + const handleAssetSearch = useCallback((value: string, chain: Chain) => { const searchValue = value.toUpperCase() - setEthAssetSearch(searchValue) - - // Filter and map to return only the asset objects - const matchedAssets = ERC20_WHITELIST.filter( - ({ asset }) => asset.symbol.toUpperCase().includes(searchValue) && !asset.synth - ).map(({ asset }) => asset) + setAssetSearch(searchValue) + + let matchedAssets: Asset[] + switch (chain) { + case ETHChain: + matchedAssets = ERC20_WHITELIST.filter( + ({ asset }) => asset.symbol.toUpperCase().includes(searchValue) && !asset.synth + ).map(({ asset }) => asset) + break + case AVAXChain: + matchedAssets = AVAX_TOKEN_WHITELIST.filter( + ({ asset }) => asset.symbol.toUpperCase().includes(searchValue) && !asset.synth + ).map(({ asset }) => asset) + break + case BSCChain: + matchedAssets = BSC_TOKEN_WHITELIST.filter( + ({ asset }) => asset.symbol.toUpperCase().includes(searchValue) && !asset.synth + ).map(({ asset }) => asset) + break + case ARBChain: + matchedAssets = ARB_TOKEN_WHITELIST.filter( + ({ asset }) => asset.symbol.toUpperCase().includes(searchValue) && !asset.synth + ).map(({ asset }) => asset) + break + default: + matchedAssets = [] + break + } setFilteredAssets(matchedAssets) }, []) - const addAssetToEthChain = useCallback((asset: Asset) => { + const addAssetToStorage = useCallback((asset: Asset) => { addAsset(asset) - setEthAssetSearch('') + setAssetSearch('') setFilteredAssets([]) }, []) @@ -639,11 +664,11 @@ export const WalletSettings: React.FC = (props): JSX.Element => { (value: string) => { const selectedAsset = filteredAssets.find((asset) => asset.symbol === value) if (selectedAsset) { - addAssetToEthChain(selectedAsset) - message.success(`${selectedAsset.symbol} added to ETH chain successfully!`) + addAssetToStorage(selectedAsset) + message.success(`${selectedAsset.symbol} added to ${selectedAsset.chain} successfully!`) } }, - [addAssetToEthChain, filteredAssets] + [addAssetToStorage, filteredAssets] ) const renderAccounts = useMemo( @@ -676,11 +701,11 @@ export const WalletSettings: React.FC = (props): JSX.Element => { : intl.formatMessage({ id: 'common.enable' })} - {chain === ETHChain && ( + {(chain === ETHChain || chain === AVAXChain || chain === BSCChain || chain === ARBChain) && (
handleAssetSearch(value, chain)} // Ensure this works correctly with AutoComplete's API onSelect={onSelectAsset} style={{ minWidth: 450, width: 'auto' }} placeholder={intl.formatMessage({ id: 'common.searchAsset' })} @@ -708,11 +733,11 @@ export const WalletSettings: React.FC = (props): JSX.Element => { renderLedgerNotSupported, enabledChains, intl, - ethAssetSearch, + assetSearch, + handleAssetSearch, + onSelectAsset, filteredAssets, - toggleChain, - handleEthAssetSearch, - onSelectAsset + toggleChain ] ) diff --git a/src/renderer/const.ts b/src/renderer/const.ts index 827071ed0..f92f5c464 100644 --- a/src/renderer/const.ts +++ b/src/renderer/const.ts @@ -1,4 +1,4 @@ -import { ARBChain, AssetARB } from '@xchainjs/xchain-arbitrum' +import { ARBChain, AssetAETH, AssetARB } from '@xchainjs/xchain-arbitrum' import { AVAXChain, AssetAVAX } from '@xchainjs/xchain-avax' import { BTCChain } from '@xchainjs/xchain-bitcoin' import { BCHChain } from '@xchainjs/xchain-bitcoincash' @@ -143,6 +143,21 @@ export const ETHAssetsFallBack = [AssetETH, AssetUSDTDAC, AssetUSDC] export const BSCAssetsFallBack = [AssetBSC, AssetUSDCBSC, AssetUSDTBSC] export const AVAXAssetsFallback = [AssetAVAX, AssetUSDTAVAX, AssetUSDCAVAX] +// for evm only +export const DEFAULT_USER_ASSETS = [ + AssetETH, + AssetUSDTDAC, + AssetUSDC, + AssetBSC, + AssetUSDCBSC, + AssetUSDTBSC, + AssetAVAX, + AssetUSDTAVAX, + AssetUSDCAVAX, + AssetARB, + AssetAETH +] + export const DEFAULT_PRICE_ASSETS: PricePoolAssets = [AssetRuneNative, AssetETH, AssetBTC, AssetCacao] // Weight of chains diff --git a/src/renderer/services/arb/balances.ts b/src/renderer/services/arb/balances.ts index e8ce214d2..96d36ec33 100644 --- a/src/renderer/services/arb/balances.ts +++ b/src/renderer/services/arb/balances.ts @@ -1,14 +1,16 @@ +import * as RD from '@devexperts/remote-data-ts' +import { ARBChain, AssetAETH } from '@xchainjs/xchain-arbitrum' import { Network } from '@xchainjs/xchain-client' import { Asset } from '@xchainjs/xchain-util' -import * as A from 'fp-ts/lib/Array' import * as FP from 'fp-ts/lib/function' +import { of } from 'rxjs' +import { switchMap } from 'rxjs/operators' import { HDMode, WalletType } from '../../../shared/wallet/types' import { ArbAssetsTestnet } from '../../const' -import { validAssetForARB } from '../../helpers/assetHelper' -import { liveData } from '../../helpers/rx/liveData' import { observableState } from '../../helpers/stateHelper' import * as C from '../clients' +import { userAssets$ } from '../storage/userChainTokens' import { client$ } from './common' /** * `ObservableState` to reload `Balances` @@ -38,21 +40,39 @@ const balances$: ({ walletAccount: number walletIndex: number hdMode: HDMode -}) => C.WalletBalancesLD = ({ walletType, walletAccount, walletIndex, network, hdMode }) => { - // For testnet we limit requests by using pre-defined assets only - const assets: Asset[] | undefined = network === Network.Testnet ? ArbAssetsTestnet : undefined +}) => C.WalletBalancesLD = ({ walletType, walletAccount, walletIndex, hdMode }) => { return FP.pipe( - C.balances$({ - client$, - trigger$: reloadBalances$, - assets, - walletType, - walletAccount, - walletIndex, - hdMode, - walletBalanceType: 'all' + userAssets$, + switchMap((assets) => { + const avaxAssets = assets.filter((asset) => asset.chain === ARBChain) + return C.balances$({ + client$, + trigger$: reloadBalances$, + assets: avaxAssets, + walletType, + walletAccount, + walletIndex, + hdMode, + walletBalanceType: 'all' + }) }), - liveData.map(FP.flow(A.filter(({ asset }) => validAssetForARB(asset, network)))) + switchMap((balanceResult) => { + // Check if the balance call failed + if (RD.isFailure(balanceResult)) { + // Retry with fallback assets + return C.balances$({ + client$, + trigger$: reloadBalances$, + assets: [AssetAETH], + walletType, + walletAccount, + walletIndex, + hdMode, + walletBalanceType: 'all' + }) + } + return of(balanceResult) + }) ) } diff --git a/src/renderer/services/avax/balances.ts b/src/renderer/services/avax/balances.ts index d7619010a..203521abe 100644 --- a/src/renderer/services/avax/balances.ts +++ b/src/renderer/services/avax/balances.ts @@ -1,17 +1,16 @@ import * as RD from '@devexperts/remote-data-ts' +import { AVAXChain } from '@xchainjs/xchain-avax' import { Network } from '@xchainjs/xchain-client' import { Asset } from '@xchainjs/xchain-util' -import * as A from 'fp-ts/lib/Array' import * as FP from 'fp-ts/lib/function' import { of } from 'rxjs' import { switchMap } from 'rxjs/operators' import { HDMode, WalletType } from '../../../shared/wallet/types' import { AVAXAssetsFallback, AvaxAssetsTestnet } from '../../const' -import { validAssetForAVAX } from '../../helpers/assetHelper' -import { liveData } from '../../helpers/rx/liveData' import { observableState } from '../../helpers/stateHelper' import * as C from '../clients' +import { userAssets$ } from '../storage/userChainTokens' import { client$ } from './common' /** @@ -42,19 +41,21 @@ const balances$: ({ walletAccount: number walletIndex: number hdMode: HDMode -}) => C.WalletBalancesLD = ({ walletType, walletAccount, walletIndex, network, hdMode }) => { - // For testnet we limit requests by using pre-defined assets only - const assets: Asset[] | undefined = network === Network.Testnet ? AvaxAssetsTestnet : undefined +}) => C.WalletBalancesLD = ({ walletType, walletAccount, walletIndex, hdMode }) => { return FP.pipe( - C.balances$({ - client$, - trigger$: reloadBalances$, - assets, - walletType, - walletAccount, - walletIndex, - hdMode, - walletBalanceType: 'all' + userAssets$, + switchMap((assets) => { + const avaxAssets = assets.filter((asset) => asset.chain === AVAXChain) + return C.balances$({ + client$, + trigger$: reloadBalances$, + assets: avaxAssets, + walletType, + walletAccount, + walletIndex, + hdMode, + walletBalanceType: 'all' + }) }), switchMap((balanceResult) => { // Check if the balance call failed @@ -72,8 +73,7 @@ const balances$: ({ }) } return of(balanceResult) - }), - liveData.map(FP.flow(A.filter(({ asset }) => validAssetForAVAX(asset, network)))) + }) ) } diff --git a/src/renderer/services/bsc/balances.ts b/src/renderer/services/bsc/balances.ts index 5930b7138..74380042a 100644 --- a/src/renderer/services/bsc/balances.ts +++ b/src/renderer/services/bsc/balances.ts @@ -1,4 +1,5 @@ import * as RD from '@devexperts/remote-data-ts' +import { BSCChain } from '@xchainjs/xchain-bsc' import { Network } from '@xchainjs/xchain-client' import { Asset } from '@xchainjs/xchain-util' import * as A from 'fp-ts/lib/Array' @@ -8,10 +9,10 @@ import { switchMap } from 'rxjs/operators' import { HDMode, WalletType } from '../../../shared/wallet/types' import { BSCAssetsFallBack, BscAssetsTestnet } from '../../const' -import { validAssetForBSC } from '../../helpers/assetHelper' import { liveData } from '../../helpers/rx/liveData' import { observableState } from '../../helpers/stateHelper' import * as C from '../clients' +import { userAssets$ } from '../storage/userChainTokens' import { WalletBalance } from '../wallet/types' import { client$ } from './common' @@ -54,21 +55,21 @@ const balances$: ({ walletAccount: number walletIndex: number hdMode: HDMode -}) => C.WalletBalancesLD = ({ walletType, walletAccount, walletIndex, network, hdMode }) => { - // For testnet we limit requests by using pre-defined assets only - - const assets: Asset[] | undefined = network === Network.Testnet ? BscAssetsTestnet : undefined - +}) => C.WalletBalancesLD = ({ walletType, walletAccount, walletIndex, hdMode }) => { return FP.pipe( - C.balances$({ - client$, - trigger$: reloadBalances$, - assets, - walletType, - walletAccount, - walletIndex, - hdMode, - walletBalanceType: 'all' + userAssets$, + switchMap((assets) => { + const bscAssets = assets.filter((asset) => asset.chain === BSCChain) + return C.balances$({ + client$, + trigger$: reloadBalances$, + assets: bscAssets, + walletType, + walletAccount, + walletIndex, + hdMode, + walletBalanceType: 'all' + }) }), // Filter assets based on BSCERC20Whitelist (mainnet only) switchMap((balanceResult) => { @@ -97,8 +98,7 @@ const balances$: ({ } }) ) - ), - liveData.map(FP.flow(A.filter(({ asset }) => validAssetForBSC(asset, network)))) + ) ) } diff --git a/src/renderer/services/clients/balances.ts b/src/renderer/services/clients/balances.ts index b5b82f211..ad96270ba 100644 --- a/src/renderer/services/clients/balances.ts +++ b/src/renderer/services/clients/balances.ts @@ -9,9 +9,9 @@ import * as RxOp from 'rxjs/operators' import { catchError, startWith, map, shareReplay, debounceTime } from 'rxjs/operators' import { HDMode, WalletBalanceType, WalletType } from '../../../shared/wallet/types' -import { AVAXAssetsFallback, BSCAssetsFallBack, ETHAssetsFallBack } from '../../const' import { liveData } from '../../helpers/rx/liveData' import { replaceSymbol } from '../bsc/balances' +import { userAssets$ } from '../storage/userChainTokens' import { ApiError, ErrorId, WalletBalance } from '../wallet/types' import { WalletBalancesLD, XChainClient$ } from './types' @@ -165,7 +165,7 @@ type BalancesByAddress$ = ({ }) => WalletBalancesLD export const balancesByAddress$: BalancesByAddress$ = - ({ client$, trigger$, assets, walletBalanceType }) => + ({ client$, trigger$, walletBalanceType }) => ({ address, walletType, walletAccount, walletIndex, hdMode }) => Rx.combineLatest([trigger$.pipe(RxOp.debounceTime(300)), client$]).pipe( RxOp.mergeMap(([_, oClient]) => { @@ -174,60 +174,36 @@ export const balancesByAddress$: BalancesByAddress$ = O.fold( () => Rx.of(RD.initial), (client) => - loadBalances$({ - client, - address, - walletType, - assets, - walletAccount, - walletIndex, - walletBalanceType, - hdMode - }).pipe( - RxOp.switchMap((result) => { - if (RD.isFailure(result)) { - const fallbackAssets = getFallbackAssets(client.getAssetInfo().asset.chain) // Implement this function to return the fallback assets for the chain - return loadBalances$({ - client, - address, - walletType, - assets: fallbackAssets, - walletAccount, - walletIndex, - walletBalanceType, - hdMode - }) - } - return Rx.of(result) - }), - liveData.map( - FP.flow( - A.map((balance: WalletBalance) => { - return { - ...balance, - asset: replaceSymbol(balance.asset) - } - }) + FP.pipe( + userAssets$, + RxOp.switchMap((assets) => { + // Filter the assets by the client's chain + const filteredAssets = assets.filter((asset) => asset.chain === client.getAssetInfo().asset.chain) + return loadBalances$({ + client, + address, + walletType, + assets: filteredAssets, // Use filtered assets + walletAccount, + walletIndex, + walletBalanceType, + hdMode + }).pipe( + liveData.map( + FP.flow( + A.map((balance: WalletBalance) => { + return { + ...balance, + asset: replaceSymbol(balance.asset) + } + }) + ) + ) ) - ) + }) ) ) ) }), shareReplay(1) ) - -// Helper function to get fallback assets based on the chain -const getFallbackAssets = (chain: string): Asset[] => { - switch (chain) { - case 'ETH': - return ETHAssetsFallBack - case 'BSC': - return BSCAssetsFallBack - case 'AVAX': - return AVAXAssetsFallback - // Add more cases as needed - default: - return [] // Return an empty array if no fallback assets are defined for the chain - } -} diff --git a/src/renderer/services/ethereum/balances.ts b/src/renderer/services/ethereum/balances.ts index 1b4cc18b5..f27775214 100644 --- a/src/renderer/services/ethereum/balances.ts +++ b/src/renderer/services/ethereum/balances.ts @@ -1,15 +1,13 @@ import * as RD from '@devexperts/remote-data-ts' import { Network } from '@xchainjs/xchain-client' +import { ETHChain } from '@xchainjs/xchain-ethereum' import { Asset } from '@xchainjs/xchain-util' -import * as A from 'fp-ts/lib/Array' import * as FP from 'fp-ts/lib/function' import { of } from 'rxjs' import { switchMap } from 'rxjs/operators' import { HDMode, WalletType } from '../../../shared/wallet/types' -import { ETHAssetsTestnet } from '../../const' -import { validAssetForETH } from '../../helpers/assetHelper' -import { liveData } from '../../helpers/rx/liveData' +import { ETHAssetsFallBack, ETHAssetsTestnet } from '../../const' import { observableState } from '../../helpers/stateHelper' import * as C from '../clients' import { userAssets$ } from '../storage/userChainTokens' @@ -42,29 +40,28 @@ const balances$: ({ walletAccount: number walletIndex: number hdMode: HDMode -}) => C.WalletBalancesLD = ({ walletType, walletAccount, walletIndex, network, hdMode }) => { - // Use userAssets$ directly for the assets +}) => C.WalletBalancesLD = ({ walletType, walletAccount, walletIndex, hdMode }) => { return FP.pipe( - userAssets$, // Directly use userAssets$ - switchMap((assets) => - C.balances$({ + userAssets$, + switchMap((assets) => { + const ethAssets = assets.filter((asset) => asset.chain === ETHChain) + return C.balances$({ client$, trigger$: reloadBalances$, - assets, + assets: ethAssets, walletType, walletAccount, walletIndex, hdMode, walletBalanceType: 'all' }) - ), + }), switchMap((balanceResult) => - // Check if the balance call failed RD.isFailure(balanceResult) ? C.balances$({ client$, trigger$: reloadBalances$, - assets: [], // Retry with an empty asset list or handle the error accordingly + assets: ETHAssetsFallBack, walletType, walletAccount, walletIndex, @@ -72,8 +69,7 @@ const balances$: ({ walletBalanceType: 'all' }) : of(balanceResult) - ), - liveData.map(FP.flow(A.filter(({ asset }) => validAssetForETH(asset, network)))) + ) ) } diff --git a/src/shared/const.ts b/src/shared/const.ts index 71fd63d03..82fe0b031 100644 --- a/src/shared/const.ts +++ b/src/shared/const.ts @@ -1,4 +1,4 @@ -import { ETHAssetsFallBack } from '../renderer/const' +import { DEFAULT_USER_ASSETS } from '../renderer/const' import { PoolsStorageEncoded } from './api/io' import { StoreFilesContent, UserAssetStorage, UserChainStorage, UserNodesStorage } from './api/types' import { DEFAULT_EVM_HD_MODE } from './evm/types' @@ -53,11 +53,12 @@ export const CHAINS_STORAGE_DEFAULT: UserChainStorage = { version: CHAINS_STORAGE_VERSION, chains: Object.keys(DEFAULT_ENABLED_CHAINS) as EnabledChain[] } -const ASSETS_STORAGE_VERSION = '1' +/// increase it by `1` if you want to ignore previous version of `common` storage +const ASSETS_STORAGE_VERSION = '2' export const ASSETS_STORAGE_DEFAULT: UserAssetStorage = { version: ASSETS_STORAGE_VERSION, - assets: ETHAssetsFallBack + assets: DEFAULT_USER_ASSETS } // increase it by `1` if you want to ignore previous version of `common` storage const POOLS_STORAGE_VERSION = '1' From 1c3ea06245fefaad4345b008aa7f6c58bb3d7469 Mon Sep 17 00:00:00 2001 From: Thorianite <100335276+Thorian1te@users.noreply.github.com> Date: Sat, 10 Aug 2024 15:34:19 +0930 Subject: [PATCH 7/8] updated wallet view logic --- .../components/settings/WalletSettings.tsx | 92 ++++++++++++++----- 1 file changed, 71 insertions(+), 21 deletions(-) diff --git a/src/renderer/components/settings/WalletSettings.tsx b/src/renderer/components/settings/WalletSettings.tsx index 5a5fc57a2..bce2b776c 100644 --- a/src/renderer/components/settings/WalletSettings.tsx +++ b/src/renderer/components/settings/WalletSettings.tsx @@ -45,7 +45,7 @@ import { useSubscriptionState } from '../../hooks/useSubscriptionState' import * as appRoutes from '../../routes/app' import * as walletRoutes from '../../routes/wallet' import { userChains$, addChain, removeChain } from '../../services/storage/userChains' -import { addAsset } from '../../services/storage/userChainTokens' +import { addAsset, removeAsset } from '../../services/storage/userChainTokens' import { KeystoreWalletsUI, RemoveKeystoreWalletHandler, @@ -617,12 +617,25 @@ export const WalletSettings: React.FC = (props): JSX.Element => { }, [exportKeystore, setExportKeystoreErrorMsg]) // Handler to update the search state - const [assetSearch, setAssetSearch] = useState('') - const [filteredAssets, setFilteredAssets] = useState([]) + const [assetSearch, setAssetSearch] = useState<{ [key in Chain]?: string }>({}) + const [filteredAssets, setFilteredAssets] = useState<{ [key in Chain]?: Asset[] }>({}) + + const [isAddingByChain, setIsAddingByChain] = useState<{ [key in Chain]?: boolean }>({}) + + const toggleStorageMode = useCallback((chain: Chain) => { + setIsAddingByChain((prevState) => ({ + ...prevState, + [chain]: !prevState[chain] // Toggle the current state for the specific chain + })) + }, []) const handleAssetSearch = useCallback((value: string, chain: Chain) => { const searchValue = value.toUpperCase() - setAssetSearch(searchValue) + + setAssetSearch((prevState) => ({ + ...prevState, + [chain]: searchValue + })) let matchedAssets: Asset[] switch (chain) { @@ -650,25 +663,50 @@ export const WalletSettings: React.FC = (props): JSX.Element => { matchedAssets = [] break } - - setFilteredAssets(matchedAssets) + setFilteredAssets((prevState) => ({ + ...prevState, + [chain]: matchedAssets + })) }, []) - const addAssetToStorage = useCallback((asset: Asset) => { + const addAssetToStorage = useCallback((asset: Asset, chain: Chain) => { addAsset(asset) - setAssetSearch('') - setFilteredAssets([]) + + setAssetSearch((prevState) => ({ + ...prevState, + [chain]: '' + })) + + setFilteredAssets((prevState) => ({ + ...prevState, + [chain]: [] + })) }, []) + const handleRemoveAsset = useCallback( + (value: string, chain: Chain) => { + const selectedAsset = (filteredAssets[chain] || []).find((asset) => asset.symbol === value) + if (selectedAsset) { + removeAsset(selectedAsset) + } + }, + [filteredAssets] + ) + const onSelectAsset = useCallback( - (value: string) => { - const selectedAsset = filteredAssets.find((asset) => asset.symbol === value) + (value: string, chain: Chain) => { + const selectedAsset = (filteredAssets[chain] || []).find((asset) => asset.symbol === value) if (selectedAsset) { - addAssetToStorage(selectedAsset) - message.success(`${selectedAsset.symbol} added to ${selectedAsset.chain} successfully!`) + if (isAddingByChain[chain]) { + addAssetToStorage(selectedAsset, chain) + message.success(`${selectedAsset.symbol} added to ${selectedAsset.chain} successfully!`) + } else { + handleRemoveAsset(selectedAsset.symbol, chain) + message.success(`${selectedAsset.symbol} removed from ${selectedAsset.chain} successfully!`) + } } }, - [addAssetToStorage, filteredAssets] + [addAssetToStorage, filteredAssets, handleRemoveAsset, isAddingByChain] ) const renderAccounts = useMemo( @@ -703,14 +741,24 @@ export const WalletSettings: React.FC = (props): JSX.Element => {
{(chain === ETHChain || chain === AVAXChain || chain === BSCChain || chain === ARBChain) && (
+ toggleStorageMode(chain)} // Pass the specific chain to toggle the state + className="mr-10px" + /> + + {isAddingByChain[chain] + ? intl.formatMessage({ id: 'common.add' }) + : intl.formatMessage({ id: 'common.remove' })} + handleAssetSearch(value, chain)} // Ensure this works correctly with AutoComplete's API - onSelect={onSelectAsset} + value={assetSearch[chain] || ''} + onChange={(value) => handleAssetSearch(value, chain)} + onSelect={(value: string) => onSelectAsset(value, chain)} style={{ minWidth: 450, width: 'auto' }} placeholder={intl.formatMessage({ id: 'common.searchAsset' })} allowClear> - {filteredAssets.map((asset) => ( + {(filteredAssets[chain] || []).map((asset: Asset) => (
{asset.symbol}
@@ -733,11 +781,13 @@ export const WalletSettings: React.FC = (props): JSX.Element => { renderLedgerNotSupported, enabledChains, intl, + isAddingByChain, assetSearch, - handleAssetSearch, - onSelectAsset, filteredAssets, - toggleChain + toggleChain, + toggleStorageMode, + handleAssetSearch, + onSelectAsset ] ) From 483c9470c367035aa0a44c19968dfd82673ffe55 Mon Sep 17 00:00:00 2001 From: Thorianite <100335276+Thorian1te@users.noreply.github.com> Date: Mon, 12 Aug 2024 12:17:33 +0930 Subject: [PATCH 8/8] updated logic --- .../components/settings/WalletSettings.tsx | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/renderer/components/settings/WalletSettings.tsx b/src/renderer/components/settings/WalletSettings.tsx index bce2b776c..b170b0cd6 100644 --- a/src/renderer/components/settings/WalletSettings.tsx +++ b/src/renderer/components/settings/WalletSettings.tsx @@ -688,6 +688,15 @@ export const WalletSettings: React.FC = (props): JSX.Element => { const selectedAsset = (filteredAssets[chain] || []).find((asset) => asset.symbol === value) if (selectedAsset) { removeAsset(selectedAsset) + setAssetSearch((prevState) => ({ + ...prevState, + [chain]: '' + })) + + setFilteredAssets((prevState) => ({ + ...prevState, + [chain]: [] + })) } }, [filteredAssets] @@ -764,7 +773,14 @@ export const WalletSettings: React.FC = (props): JSX.Element => { ))}
- +
)}