Skip to content
This repository has been archived by the owner on May 10, 2024. It is now read-only.

Fix #8529: Update logic to support unknown tokens from non-mainnet transactions #8530

Merged
merged 1 commit into from
Dec 8, 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
26 changes: 11 additions & 15 deletions Sources/BraveWallet/Crypto/Stores/AccountActivityStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class AccountActivityStore: ObservableObject, WalletObserverStore {
private let assetManager: WalletUserAssetManagerType
/// Cache for storing `BlockchainToken`s that are not in user assets or our token registry.
/// This could occur with a dapp creating a transaction.
private var tokenInfoCache: [String: BraveWallet.BlockchainToken] = [:]
private var tokenInfoCache: [BraveWallet.BlockchainToken] = []

private var keyringServiceObserver: KeyringServiceObserver?
private var rpcServiceObserver: JsonRpcServiceObserver?
Expand Down Expand Up @@ -278,20 +278,16 @@ class AccountActivityStore: ObservableObject, WalletObserverStore {
if account.coin == .sol {
solEstimatedTxFees = await solTxManagerProxy.estimatedTxFees(for: transactions)
}
let unknownTokenContractAddresses = transactions
.flatMap { $0.tokenContractAddresses }
.filter { contractAddress in
!userAssets.contains(where: { $0.contractAddress.caseInsensitiveCompare(contractAddress) == .orderedSame })
&& !allTokens.contains(where: { $0.contractAddress.caseInsensitiveCompare(contractAddress) == .orderedSame })
&& !tokenInfoCache.keys.contains(where: { $0.caseInsensitiveCompare(contractAddress) == .orderedSame })
}
var allTokens = allTokens
if !unknownTokenContractAddresses.isEmpty {
let unknownTokens = await assetRatioService.fetchTokens(for: unknownTokenContractAddresses)
for unknownToken in unknownTokens {
tokenInfoCache[unknownToken.contractAddress] = unknownToken
let ethTransactions = transactions.filter { $0.coin == .eth }
if !ethTransactions.isEmpty {
// Gather known information about the transaction(s) tokens
let unknownTokenInfo = ethTransactions.unknownTokenContractAddressChainIdPairs(
knownTokens: userAssets + allTokens + tokenInfoCache
)
if !unknownTokenInfo.isEmpty {
let unknownTokens: [BraveWallet.BlockchainToken] = await rpcService.fetchEthTokens(for: unknownTokenInfo)
tokenInfoCache.append(contentsOf: unknownTokens)
}
allTokens.append(contentsOf: unknownTokens)
}
return transactions
.compactMap { transaction in
Expand All @@ -303,7 +299,7 @@ class AccountActivityStore: ObservableObject, WalletObserverStore {
network: network,
accountInfos: accountInfos,
userAssets: userAssets,
allTokens: allTokens,
allTokens: allTokens + tokenInfoCache,
assetRatios: assetRatios,
nftMetadata: [:],
solEstimatedTxFee: solEstimatedTxFees[transaction.id],
Expand Down
33 changes: 31 additions & 2 deletions Sources/BraveWallet/Crypto/Stores/AssetDetailStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ class AssetDetailStore: ObservableObject, WalletObserverStore {
/// A list of tokens that are supported with the current selected network for all supported
/// on-ramp providers.
private var allBuyTokensAllOptions: [BraveWallet.OnRampProvider: [BraveWallet.BlockchainToken]] = [:]
/// Cache for storing `BlockchainToken`s that are not in user assets or our token registry.
/// This could occur with a dapp creating a transaction.
private var tokenInfoCache: [BraveWallet.BlockchainToken] = []
private var keyringServiceObserver: KeyringServiceObserver?
private var txServiceObserver: TxServiceObserver?
private var walletServiceObserver: WalletServiceObserver?
Expand Down Expand Up @@ -340,8 +343,19 @@ class AssetDetailStore: ObservableObject, WalletObserverStore {
})
}
var solEstimatedTxFees: [String: UInt64] = [:]
if token.coin == .sol {
switch token.coin {
case .eth:
let ethTransactions = allTransactions.filter { $0.coin == .eth }
if !ethTransactions.isEmpty { // we can only fetch unknown Ethereum tokens
let unknownTokenInfo = ethTransactions.unknownTokenContractAddressChainIdPairs(
knownTokens: userAssets + allTokens + tokenInfoCache
)
updateUnknownTokens(for: unknownTokenInfo)
}
case .sol:
solEstimatedTxFees = await solTxManagerProxy.estimatedTxFees(for: allTransactions)
default:
break
}
return allTransactions
.filter { tx in
Expand Down Expand Up @@ -378,7 +392,7 @@ class AssetDetailStore: ObservableObject, WalletObserverStore {
network: network,
accountInfos: accounts,
userAssets: userAssets,
allTokens: allTokens,
allTokens: allTokens + tokenInfoCache,
assetRatios: assetRatios,
nftMetadata: [:],
solEstimatedTxFee: solEstimatedTxFees[transaction.id],
Expand Down Expand Up @@ -420,6 +434,21 @@ class AssetDetailStore: ObservableObject, WalletObserverStore {
return false
}
}

private func updateUnknownTokens(
for contractAddressesChainIdPairs: [ContractAddressChainIdPair]
) {
guard !contractAddressesChainIdPairs.isEmpty else { return }
Task { @MainActor in
// Gather known information about the transaction(s) tokens
let unknownTokens: [BraveWallet.BlockchainToken] = await rpcService.fetchEthTokens(
for: contractAddressesChainIdPairs
)
guard !unknownTokens.isEmpty else { return }
tokenInfoCache.append(contentsOf: unknownTokens)
update()
}
}
}

extension AssetDetailStore: BraveWalletKeyringServiceObserver {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ public class TransactionConfirmationStore: ObservableObject, WalletObserverStore
}
return
}
let allTokens = await blockchainRegistry.allTokens(network.chainId, coin: coin) + tokenInfoCache.map(\.value)
let allTokens = await blockchainRegistry.allTokens(network.chainId, coin: coin) + tokenInfoCache
let userAssets = assetManager.getAllUserAssetsInNetworkAssets(networks: [network], includingUserDeleted: true).flatMap { $0.tokens }
let solEstimatedTxFee: UInt64? = solEstimatedTxFeeCache[transaction.id]

Expand Down Expand Up @@ -393,7 +393,7 @@ public class TransactionConfirmationStore: ObservableObject, WalletObserverStore
private var gasTokenBalanceCache: [String: Double] = [:]
/// Cache for storing `BlockchainToken`s that are not in user assets or our token registry.
/// This could occur with a dapp creating a transaction.
private var tokenInfoCache: [String: BraveWallet.BlockchainToken] = [:]
private var tokenInfoCache: [BraveWallet.BlockchainToken] = []
/// Cache for storing the estimated transaction fee for each Solana transaction. The key is the transaction id.
private var solEstimatedTxFeeCache: [String: UInt64] = [:]

Expand Down Expand Up @@ -444,27 +444,19 @@ public class TransactionConfirmationStore: ObservableObject, WalletObserverStore
@MainActor private func fetchUnknownTokens(
for transactions: [BraveWallet.TransactionInfo]
) async {
// `AssetRatioService` can only fetch tokens from Ethereum Mainnet
let mainnetTransactions = transactions.filter { $0.chainId == BraveWallet.MainnetChainId }
guard !mainnetTransactions.isEmpty else { return }
let ethTransactions = transactions.filter { $0.coin == .eth }
guard !ethTransactions.isEmpty else { return } // we can only fetch unknown Ethereum tokens
let coin: BraveWallet.CoinType = .eth
let allNetworks = await rpcService.allNetworks(coin)
guard let network = allNetworks.first(where: { $0.chainId == BraveWallet.MainnetChainId }) else {
return
}
let userAssets = assetManager.getAllUserAssetsInNetworkAssets(networks: [network], includingUserDeleted: true).flatMap { $0.tokens }
let allTokens = await blockchainRegistry.allTokens(network.chainId, coin: network.coin)
let unknownTokenContractAddresses = mainnetTransactions.flatMap(\.tokenContractAddresses)
.filter { contractAddress in
!userAssets.contains(where: { $0.contractAddress(in: network).caseInsensitiveCompare(contractAddress) == .orderedSame })
&& !allTokens.contains(where: { $0.contractAddress(in: network).caseInsensitiveCompare(contractAddress) == .orderedSame })
&& !tokenInfoCache.keys.contains(where: { $0.caseInsensitiveCompare(contractAddress) == .orderedSame })
}
guard !unknownTokenContractAddresses.isEmpty else { return }
let unknownTokens = await assetRatioService.fetchTokens(for: unknownTokenContractAddresses)
for unknownToken in unknownTokens {
tokenInfoCache[unknownToken.contractAddress] = unknownToken
}
let userAssets = assetManager.getAllUserAssetsInNetworkAssets(networks: allNetworks, includingUserDeleted: true).flatMap(\.tokens)
let allTokens = await blockchainRegistry.allTokens(in: allNetworks).flatMap(\.tokens)
// Gather known information about the transaction(s) tokens
let unknownTokenInfo = ethTransactions.unknownTokenContractAddressChainIdPairs(
knownTokens: userAssets + allTokens + tokenInfoCache
)
guard !unknownTokenInfo.isEmpty else { return } // Only if we have unknown tokens
let unknownTokens: [BraveWallet.BlockchainToken] = await rpcService.fetchEthTokens(for: unknownTokenInfo)
tokenInfoCache.append(contentsOf: unknownTokens)
updateTransaction(with: activeTransaction, shouldFetchCurrentAllowance: false, shouldFetchGasTokenBalance: false)
}

Expand Down
37 changes: 12 additions & 25 deletions Sources/BraveWallet/Crypto/Stores/TransactionDetailsStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class TransactionDetailsStore: ObservableObject, WalletObserverStore {
private let assetManager: WalletUserAssetManagerType
/// Cache for storing `BlockchainToken`s that are not in user assets or our token registry.
/// This could occur with a dapp creating a transaction.
private var tokenInfoCache: [String: BraveWallet.BlockchainToken] = [:]
private var tokenInfoCache: [BraveWallet.BlockchainToken] = []
private var nftMetadataCache: [String: NFTMetadata] = [:]

var isObserving: Bool {
Expand Down Expand Up @@ -111,20 +111,17 @@ class TransactionDetailsStore: ObservableObject, WalletObserverStore {
return
}
self.network = network
var allTokens: [BraveWallet.BlockchainToken] = await blockchainRegistry.allTokens(network.chainId, coin: network.coin) + tokenInfoCache.map(\.value)
let userAssets: [BraveWallet.BlockchainToken] = assetManager.getAllUserAssetsInNetworkAssets(networks: [network], includingUserDeleted: true).flatMap { $0.tokens }
let unknownTokenContractAddresses = transaction.tokenContractAddresses
.filter { contractAddress in
!userAssets.contains(where: { $0.contractAddress(in: network).caseInsensitiveCompare(contractAddress) == .orderedSame })
&& !allTokens.contains(where: { $0.contractAddress(in: network).caseInsensitiveCompare(contractAddress) == .orderedSame })
&& !tokenInfoCache.keys.contains(where: { $0.caseInsensitiveCompare(contractAddress) == .orderedSame })
var allTokens: [BraveWallet.BlockchainToken] = await blockchainRegistry.allTokens(network.chainId, coin: network.coin)
let userAssets: [BraveWallet.BlockchainToken] = assetManager.getAllUserAssetsInNetworkAssets(networks: [network], includingUserDeleted: true).flatMap(\.tokens)
if coin == .eth {
// Gather known information about the transaction(s) tokens
let unknownTokenInfo = [transaction].unknownTokenContractAddressChainIdPairs(
knownTokens: userAssets + allTokens + tokenInfoCache
)
if !unknownTokenInfo.isEmpty {
let unknownTokens: [BraveWallet.BlockchainToken] = await rpcService.fetchEthTokens(for: unknownTokenInfo)
tokenInfoCache.append(contentsOf: unknownTokens)
}
if !unknownTokenContractAddresses.isEmpty {
let unknownTokens = await assetRatioService.fetchTokens(for: unknownTokenContractAddresses)
for unknownToken in unknownTokens {
tokenInfoCache[unknownToken.contractAddress] = unknownToken
}
allTokens.append(contentsOf: unknownTokens)
}

let priceResult = await assetRatioService.priceWithIndividualRetry(
Expand All @@ -144,7 +141,7 @@ class TransactionDetailsStore: ObservableObject, WalletObserverStore {
network: network,
accountInfos: allAccounts,
userAssets: userAssets,
allTokens: allTokens,
allTokens: allTokens + tokenInfoCache,
assetRatios: assetRatios,
nftMetadata: nftMetadataCache,
solEstimatedTxFee: solEstimatedTxFee,
Expand Down Expand Up @@ -191,14 +188,4 @@ class TransactionDetailsStore: ObservableObject, WalletObserverStore {
self.parsedTransaction = parsedTransaction
}
}

@MainActor private func fetchTokenInfo(for contractAddress: String) async -> BraveWallet.BlockchainToken? {
if let cachedToken = tokenInfoCache[contractAddress] {
return cachedToken
}
let tokenInfo = await assetRatioService.tokenInfo(contractAddress)
guard let tokenInfo = tokenInfo else { return nil }
self.tokenInfoCache[contractAddress] = tokenInfo
return tokenInfo
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ class TransactionsActivityStore: ObservableObject, WalletObserverStore {
private var assetPricesCache: [String: Double] = [:]
/// Cache of metadata for NFTs. The key is the token's `id`.
private var metadataCache: [String: NFTMetadata] = [:]
/// Cache for storing `BlockchainToken`s that are not in user assets or our token registry.
/// This could occur with a dapp creating a transaction.
private var tokenInfoCache: [BraveWallet.BlockchainToken] = []

private let keyringService: BraveWalletKeyringService
private let rpcService: BraveWalletJsonRpcService
Expand Down Expand Up @@ -147,10 +150,20 @@ class TransactionsActivityStore: ObservableObject, WalletObserverStore {
let allTransactions = await txService.allTransactions(
networksForCoin: networksForCoin, for: allAccountInfos
).filter { $0.txStatus != .rejected }
let userAssets = assetManager.getAllUserAssetsInNetworkAssets(networks: allNetworksAllCoins, includingUserDeleted: true).flatMap(\.tokens)
let userAssets = assetManager.getAllUserAssetsInNetworkAssets(
networks: allNetworksAllCoins,
includingUserDeleted: true
).flatMap(\.tokens)
let allTokens = await blockchainRegistry.allTokens(
in: allNetworksAllCoins
).flatMap(\.tokens)
let ethTransactions = allTransactions.filter { $0.coin == .eth }
if !ethTransactions.isEmpty { // we can only fetch unknown Ethereum tokens
let unknownTokenInfo = ethTransactions.unknownTokenContractAddressChainIdPairs(
knownTokens: userAssets + allTokens + tokenInfoCache
)
updateUnknownTokens(for: unknownTokenInfo)
}
guard !Task.isCancelled else { return }
// display transactions prior to network request to fetch
// estimated solana tx fees & asset prices
Expand All @@ -159,7 +172,7 @@ class TransactionsActivityStore: ObservableObject, WalletObserverStore {
networksForCoin: networksForCoin,
accountInfos: allAccountInfos,
userAssets: userAssets,
allTokens: allTokens,
allTokens: allTokens + tokenInfoCache,
assetRatios: assetPricesCache,
nftMetadata: metadataCache,
solEstimatedTxFees: solEstimatedTxFeesCache
Expand Down Expand Up @@ -254,7 +267,7 @@ class TransactionsActivityStore: ObservableObject, WalletObserverStore {
network: network,
accountInfos: accountInfos,
userAssets: userAssets,
allTokens: allTokens,
allTokens: allTokens + tokenInfoCache,
assetRatios: assetRatios,
nftMetadata: nftMetadata,
solEstimatedTxFee: solEstimatedTxFees[transaction.id],
Expand Down Expand Up @@ -287,6 +300,21 @@ class TransactionsActivityStore: ObservableObject, WalletObserverStore {
}
}

private func updateUnknownTokens(
for contractAddressesChainIdPairs: [ContractAddressChainIdPair]
) {
guard !contractAddressesChainIdPairs.isEmpty else { return }
Task { @MainActor in
// Gather known information about the transaction(s) tokens
let unknownTokens: [BraveWallet.BlockchainToken] = await rpcService.fetchEthTokens(
for: contractAddressesChainIdPairs
)
guard !unknownTokens.isEmpty else { return }
tokenInfoCache.append(contentsOf: unknownTokens)
update()
}
}

private var transactionDetailsStore: TransactionDetailsStore?
func transactionDetailsStore(
for transaction: BraveWallet.TransactionInfo
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,20 +108,4 @@ extension BraveWalletAssetRatioService {
let prices = Dictionary(uniqueKeysWithValues: priceResult.assetPrices.map { ($0.fromAsset, $0.price) })
return prices
}

/// 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.
@MainActor func fetchTokens(
for contractAddresses: [String]
) async -> [BraveWallet.BlockchainToken] {
await withTaskGroup(of: [BraveWallet.BlockchainToken?].self) { @MainActor group in
for contractAddress in contractAddresses {
group.addTask { @MainActor in
let token = await self.tokenInfo(contractAddress)
return [token]
}
}
return await group.reduce([BraveWallet.BlockchainToken?](), { $0 + $1 })
}.compactMap { $0 }
}
}
Loading