From 7d780718fcfc6083a11c660e69054ca678fec55c Mon Sep 17 00:00:00 2001 From: Stephen Heaps Date: Tue, 2 Jan 2024 17:45:52 -0500 Subject: [PATCH 1/3] Restore Add Custom Token auto-complete, now supporting all EVMs instead of just Mainnet --- .../Crypto/Portfolio/AddCustomAssetView.swift | 60 +++++++++++++++---- .../Crypto/Stores/UserAssetsStore.swift | 32 ++++++++++ .../Extensions/RpcServiceExtensions.swift | 35 +---------- 3 files changed, 83 insertions(+), 44 deletions(-) diff --git a/Sources/BraveWallet/Crypto/Portfolio/AddCustomAssetView.swift b/Sources/BraveWallet/Crypto/Portfolio/AddCustomAssetView.swift index 0df5592fb01..91915dca933 100644 --- a/Sources/BraveWallet/Crypto/Portfolio/AddCustomAssetView.swift +++ b/Sources/BraveWallet/Crypto/Portfolio/AddCustomAssetView.swift @@ -81,8 +81,28 @@ struct AddCustomAssetView: View { header: WalletListHeaderView(title: networkSelectionStore.networkSelectionInForm?.coin == .sol ? Text(Strings.Wallet.tokenMintAddress) : Text(Strings.Wallet.tokenAddress)) ) { TextField(Strings.Wallet.enterAddress, text: $addressInput) + .onChange(of: addressInput) { newValue in + guard !newValue.isEmpty, + let network = networkSelectionStore.networkSelectionInForm else { return } + userAssetStore.tokenInfo(address: newValue, chainId: network.chainId) { token in + guard let token else { return } + if nameInput.isEmpty { + nameInput = token.name + } + if symbolInput.isEmpty { + symbolInput = token.symbol + } + if !token.isErc721, !token.isNft, decimalsInput.isEmpty { + decimalsInput = "\(token.decimals)" + } + if let network = networkStore.allChains.first(where: { $0.chainId == token.chainId }) { + networkSelectionStore.networkSelectionInForm = network + } + } + } .autocapitalization(.none) .autocorrectionDisabled() + .disabled(userAssetStore.isSearchingToken) .listRowBackground(Color(.secondaryBraveGroupedBackground)) } Section( @@ -104,27 +124,45 @@ struct AddCustomAssetView: View { Section( header: WalletListHeaderView(title: Text(Strings.Wallet.tokenName)) ) { - TextField(Strings.Wallet.enterTokenName, text: $nameInput) - .autocapitalization(.none) - .autocorrectionDisabled() - .listRowBackground(Color(.secondaryBraveGroupedBackground)) + HStack { + TextField(Strings.Wallet.enterTokenName, text: $nameInput) + .autocapitalization(.none) + .autocorrectionDisabled() + .disabled(userAssetStore.isSearchingToken) + if userAssetStore.isSearchingToken && nameInput.isEmpty { + ProgressView() + } + } + .listRowBackground(Color(.secondaryBraveGroupedBackground)) } Section( header: WalletListHeaderView(title: Text(Strings.Wallet.tokenSymbol)) ) { - TextField(Strings.Wallet.enterTokenSymbol, text: $symbolInput) - .autocapitalization(.none) - .autocorrectionDisabled() - .listRowBackground(Color(.secondaryBraveGroupedBackground)) + HStack { + TextField(Strings.Wallet.enterTokenSymbol, text: $symbolInput) + .autocapitalization(.none) + .autocorrectionDisabled() + .disabled(userAssetStore.isSearchingToken) + if userAssetStore.isSearchingToken && symbolInput.isEmpty { + ProgressView() + } + } + .listRowBackground(Color(.secondaryBraveGroupedBackground)) } switch selectedTokenType { case .token: Section( header: WalletListHeaderView(title: Text(Strings.Wallet.decimalsPrecision)) ) { - TextField(NumberFormatter().string(from: NSNumber(value: 0)) ?? "0", text: $decimalsInput) - .keyboardType(.numberPad) - .listRowBackground(Color(.secondaryBraveGroupedBackground)) + HStack { + TextField(NumberFormatter().string(from: NSNumber(value: 0)) ?? "0", text: $decimalsInput) + .keyboardType(.numberPad) + .disabled(userAssetStore.isSearchingToken) + if userAssetStore.isSearchingToken && decimalsInput.isEmpty { + ProgressView() + } + } + .listRowBackground(Color(.secondaryBraveGroupedBackground)) } Section { Button( diff --git a/Sources/BraveWallet/Crypto/Stores/UserAssetsStore.swift b/Sources/BraveWallet/Crypto/Stores/UserAssetsStore.swift index 2637087179d..1159ff604c7 100644 --- a/Sources/BraveWallet/Crypto/Stores/UserAssetsStore.swift +++ b/Sources/BraveWallet/Crypto/Stores/UserAssetsStore.swift @@ -54,6 +54,7 @@ public class AssetStore: ObservableObject, Equatable, WalletObserverStore { public class UserAssetsStore: ObservableObject, WalletObserverStore { @Published private(set) var assetStores: [AssetStore] = [] + @Published var isSearchingToken: Bool = false @Published var networkFilters: [Selectable] = [] { didSet { guard !oldValue.isEmpty else { return } // initial assignment to `networkFilters` @@ -198,6 +199,37 @@ public class UserAssetsStore: ObservableObject, WalletObserverStore { completion(true) } } + + func tokenInfo( + address: String, + chainId: String, + completion: @escaping (BraveWallet.BlockchainToken?) -> Void + ) { + // First check user's visible assets + if let assetStore = assetStores.first(where: { $0.token.contractAddress.caseInsensitiveCompare(address) == .orderedSame }) { + completion(assetStore.token) + } // else check full tokens list + else if let token = allTokens.first(where: { $0.contractAddress.caseInsensitiveCompare(address) == .orderedSame }) { + completion(token) + } // else use network request to get token info + else if address.isETHAddress { // only Eth Mainnet supported, require ethereum address + timer?.invalidate() + timer = Timer.scheduledTimer( + withTimeInterval: 0.25, repeats: false, + block: { [weak self] _ in + guard let self = self else { return } + self.isSearchingToken = true + self.rpcService.ethTokenInfo( + address, + chainId: chainId, + completion: { token, status, error in + self.isSearchingToken = false + completion(token) + } + ) + }) + } + } @MainActor func networkInfo(by chainId: String, coin: BraveWallet.CoinType) async -> BraveWallet.NetworkInfo? { let allNetworks = await rpcService.allNetworks(coin) diff --git a/Sources/BraveWallet/Extensions/RpcServiceExtensions.swift b/Sources/BraveWallet/Extensions/RpcServiceExtensions.swift index 0239445f9a4..771b51d4c13 100644 --- a/Sources/BraveWallet/Extensions/RpcServiceExtensions.swift +++ b/Sources/BraveWallet/Extensions/RpcServiceExtensions.swift @@ -385,37 +385,6 @@ extension BraveWalletJsonRpcService { }) } } - - /// Helper to fetch `symbol` and `decimals` ONLY given a token contract address & chainId. - /// This function will be replaced by a BraveCore function that will also fetch `name` & `coingeckoId`. - func getEthTokenInfo( - contractAddress: String, - chainId: String - ) async -> BraveWallet.BlockchainToken? { - // Fetches token info by contract address and chain ID. The returned token - // has the following fields populated: - // - contract_address - // - chain_id - // - coin - // - name - // - symbol - // - decimals - // - coingecko_id - // - // The following fields are always set to false, and callers must NOT rely - // on them: - // - is_erc721 - // - is_erc1155 - // - is_erc20 - // - is_nft - let (token, status, _) = await ethTokenInfo(contractAddress, chainId: chainId) - guard status == .success else { return nil } - token?.logo = "" - token?.isSpam = false - token?.visible = false - token?.tokenId = "" - return token - } /// Fetches the BlockchainToken for the given contract addresses. The token for a given contract /// address is not guaranteed to be found, and will not be provided in the result if not found. @@ -425,8 +394,8 @@ extension BraveWalletJsonRpcService { await withTaskGroup(of: [BraveWallet.BlockchainToken?].self) { @MainActor group in for contractAddressesChainIdPair in contractAddressesChainIdPairs { group.addTask { - let token = await self.getEthTokenInfo( - contractAddress: contractAddressesChainIdPair.contractAddress, + let (token, _, _) = await self.ethTokenInfo( + contractAddressesChainIdPair.contractAddress, chainId: contractAddressesChainIdPair.chainId ) if let token { From 9c32ab6cc6bd1e90c7880c21bf1510a7eb7e32bc Mon Sep 17 00:00:00 2001 From: Stephen Heaps Date: Thu, 4 Jan 2024 09:55:11 -0500 Subject: [PATCH 2/3] Update code comment --- Sources/BraveWallet/Crypto/Stores/UserAssetsStore.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/BraveWallet/Crypto/Stores/UserAssetsStore.swift b/Sources/BraveWallet/Crypto/Stores/UserAssetsStore.swift index 1159ff604c7..f54590fc361 100644 --- a/Sources/BraveWallet/Crypto/Stores/UserAssetsStore.swift +++ b/Sources/BraveWallet/Crypto/Stores/UserAssetsStore.swift @@ -212,7 +212,7 @@ public class UserAssetsStore: ObservableObject, WalletObserverStore { else if let token = allTokens.first(where: { $0.contractAddress.caseInsensitiveCompare(address) == .orderedSame }) { completion(token) } // else use network request to get token info - else if address.isETHAddress { // only Eth Mainnet supported, require ethereum address + else if address.isETHAddress { // only Eth networks supported, require ethereum address timer?.invalidate() timer = Timer.scheduledTimer( withTimeInterval: 0.25, repeats: false, From 1adc73deff7b88b23347dcab0b1212fdadd8b708 Mon Sep 17 00:00:00 2001 From: Stephen Heaps Date: Thu, 4 Jan 2024 09:56:15 -0500 Subject: [PATCH 3/3] Remove unneeded network assignment; network required to fetch auto-complete info --- Sources/BraveWallet/Crypto/Portfolio/AddCustomAssetView.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/Sources/BraveWallet/Crypto/Portfolio/AddCustomAssetView.swift b/Sources/BraveWallet/Crypto/Portfolio/AddCustomAssetView.swift index 91915dca933..8680095352c 100644 --- a/Sources/BraveWallet/Crypto/Portfolio/AddCustomAssetView.swift +++ b/Sources/BraveWallet/Crypto/Portfolio/AddCustomAssetView.swift @@ -95,9 +95,6 @@ struct AddCustomAssetView: View { if !token.isErc721, !token.isNft, decimalsInput.isEmpty { decimalsInput = "\(token.decimals)" } - if let network = networkStore.allChains.first(where: { $0.chainId == token.chainId }) { - networkSelectionStore.networkSelectionInForm = network - } } } .autocapitalization(.none)