diff --git a/Sources/BraveWallet/Crypto/AssetIconView.swift b/Sources/BraveWallet/Crypto/AssetIconView.swift index 58501f75a25..7eef4a84385 100644 --- a/Sources/BraveWallet/Crypto/AssetIconView.swift +++ b/Sources/BraveWallet/Crypto/AssetIconView.swift @@ -147,7 +147,10 @@ struct NFTIconView: View { } var body: some View { - NFTImageView(urlString: url?.absoluteString ?? "", isLoading: isLoadingMetadata) { + NFTImageView( // logo populated for auto-discovered NFTs + urlString: url?.absoluteString ?? token.logo, + isLoading: isLoadingMetadata + ) { LoadingNFTView(shimmer: false) } .cornerRadius(5) diff --git a/Sources/BraveWallet/Crypto/Stores/AccountActivityStore.swift b/Sources/BraveWallet/Crypto/Stores/AccountActivityStore.swift index f2054fcfcea..d282a9a3d9f 100644 --- a/Sources/BraveWallet/Crypto/Stores/AccountActivityStore.swift +++ b/Sources/BraveWallet/Crypto/Stores/AccountActivityStore.swift @@ -304,6 +304,7 @@ class AccountActivityStore: ObservableObject, WalletObserverStore { userAssets: userAssets, allTokens: allTokens, assetRatios: assetRatios, + nftMetadata: [:], solEstimatedTxFee: solEstimatedTxFees[transaction.id], currencyFormatter: currencyFormatter ) diff --git a/Sources/BraveWallet/Crypto/Stores/AssetDetailStore.swift b/Sources/BraveWallet/Crypto/Stores/AssetDetailStore.swift index f421910343e..8a902bb38a8 100644 --- a/Sources/BraveWallet/Crypto/Stores/AssetDetailStore.swift +++ b/Sources/BraveWallet/Crypto/Stores/AssetDetailStore.swift @@ -376,6 +376,7 @@ class AssetDetailStore: ObservableObject, WalletObserverStore { userAssets: userAssets, allTokens: allTokens, assetRatios: assetRatios, + nftMetadata: [:], solEstimatedTxFee: solEstimatedTxFees[transaction.id], currencyFormatter: self.currencyFormatter ) diff --git a/Sources/BraveWallet/Crypto/Stores/CryptoStore.swift b/Sources/BraveWallet/Crypto/Stores/CryptoStore.swift index 55764db9d84..d870fd5e957 100644 --- a/Sources/BraveWallet/Crypto/Stores/CryptoStore.swift +++ b/Sources/BraveWallet/Crypto/Stores/CryptoStore.swift @@ -190,6 +190,7 @@ public class CryptoStore: ObservableObject, WalletObserverStore { blockchainRegistry: blockchainRegistry, txService: txService, solTxManagerProxy: solTxManagerProxy, + ipfsApi: ipfsApi, userAssetManager: userAssetManager ) self.marketStore = .init( diff --git a/Sources/BraveWallet/Crypto/Stores/TransactionConfirmationStore.swift b/Sources/BraveWallet/Crypto/Stores/TransactionConfirmationStore.swift index 237952277b7..e3ae22adcf0 100644 --- a/Sources/BraveWallet/Crypto/Stores/TransactionConfirmationStore.swift +++ b/Sources/BraveWallet/Crypto/Stores/TransactionConfirmationStore.swift @@ -319,6 +319,7 @@ public class TransactionConfirmationStore: ObservableObject, WalletObserverStore userAssets: userAssets, allTokens: allTokens, assetRatios: assetRatios, + nftMetadata: [:], solEstimatedTxFee: solEstimatedTxFee, currencyFormatter: currencyFormatter ) else { diff --git a/Sources/BraveWallet/Crypto/Stores/TransactionDetailsStore.swift b/Sources/BraveWallet/Crypto/Stores/TransactionDetailsStore.swift index 6f20590a199..af9b386f1d9 100644 --- a/Sources/BraveWallet/Crypto/Stores/TransactionDetailsStore.swift +++ b/Sources/BraveWallet/Crypto/Stores/TransactionDetailsStore.swift @@ -112,6 +112,7 @@ class TransactionDetailsStore: ObservableObject, WalletObserverStore { userAssets: userAssets, allTokens: allTokens, assetRatios: assetRatios, + nftMetadata: [:], solEstimatedTxFee: solEstimatedTxFee, currencyFormatter: currencyFormatter ) else { diff --git a/Sources/BraveWallet/Crypto/Stores/TransactionsActivityStore.swift b/Sources/BraveWallet/Crypto/Stores/TransactionsActivityStore.swift index 4de16fa9afb..981fe401208 100644 --- a/Sources/BraveWallet/Crypto/Stores/TransactionsActivityStore.swift +++ b/Sources/BraveWallet/Crypto/Stores/TransactionsActivityStore.swift @@ -31,6 +31,8 @@ class TransactionsActivityStore: ObservableObject, WalletObserverStore { private var solEstimatedTxFeesCache: [String: UInt64] = [:] private var assetPricesCache: [String: Double] = [:] + /// Cache of metadata for NFTs. The key is the token's `id`. + private var metadataCache: [String: NFTMetadata] = [:] private let keyringService: BraveWalletKeyringService private let rpcService: BraveWalletJsonRpcService @@ -39,6 +41,7 @@ class TransactionsActivityStore: ObservableObject, WalletObserverStore { private let blockchainRegistry: BraveWalletBlockchainRegistry private let txService: BraveWalletTxService private let solTxManagerProxy: BraveWalletSolanaTxManagerProxy + private let ipfsApi: IpfsAPI private let assetManager: WalletUserAssetManagerType private var keyringServiceObserver: KeyringServiceObserver? private var txServiceObserver: TxServiceObserver? @@ -56,6 +59,7 @@ class TransactionsActivityStore: ObservableObject, WalletObserverStore { blockchainRegistry: BraveWalletBlockchainRegistry, txService: BraveWalletTxService, solTxManagerProxy: BraveWalletSolanaTxManagerProxy, + ipfsApi: IpfsAPI, userAssetManager: WalletUserAssetManagerType ) { self.keyringService = keyringService @@ -65,6 +69,7 @@ class TransactionsActivityStore: ObservableObject, WalletObserverStore { self.blockchainRegistry = blockchainRegistry self.txService = txService self.solTxManagerProxy = solTxManagerProxy + self.ipfsApi = ipfsApi self.assetManager = userAssetManager self.setupObservers() @@ -154,6 +159,7 @@ class TransactionsActivityStore: ObservableObject, WalletObserverStore { userAssets: userAssets, allTokens: allTokens, assetRatios: assetPricesCache, + nftMetadata: metadataCache, solEstimatedTxFees: solEstimatedTxFeesCache ) guard !self.transactionSections.isEmpty else { return } @@ -174,6 +180,42 @@ class TransactionsActivityStore: ObservableObject, WalletObserverStore { userAssets: userAssets, allTokens: allTokens, assetRatios: assetPricesCache, + nftMetadata: metadataCache, + solEstimatedTxFees: solEstimatedTxFeesCache + ) + + let nftsWithoutMetadata = transactionSections.flatMap(\.transactions) + .compactMap { parsedTx in + switch parsedTx.details { + case .erc721Transfer(let details): + return details.fromToken + case .solSplTokenTransfer(let details): + if let fromToken = details.fromToken, fromToken.isNft { + return fromToken + } + return nil + default: + return nil + } + } + .filter { token in // filter out already fetched metadata + !metadataCache.keys.contains(where: { $0.caseInsensitiveCompare(token.contractAddress) == .orderedSame }) + } + guard !Task.isCancelled, !nftsWithoutMetadata.isEmpty else { return } + // fetch nft metadata for all NFTs + let allMetadata = await rpcService.fetchNFTMetadata(tokens: nftsWithoutMetadata, ipfsApi: ipfsApi) + for (key, value) in allMetadata { // update cached values + metadataCache[key] = value + } + guard !Task.isCancelled else { return } + self.transactionSections = buildTransactionSections( + transactions: allTransactions, + networksForCoin: networksForCoin, + accountInfos: allAccountInfos, + userAssets: userAssets, + allTokens: allTokens, + assetRatios: assetPricesCache, + nftMetadata: metadataCache, solEstimatedTxFees: solEstimatedTxFeesCache ) } @@ -186,6 +228,7 @@ class TransactionsActivityStore: ObservableObject, WalletObserverStore { userAssets: [BraveWallet.BlockchainToken], allTokens: [BraveWallet.BlockchainToken], assetRatios: [String: Double], + nftMetadata: [String: NFTMetadata], solEstimatedTxFees: [String: UInt64] ) -> [TransactionSection] { // Group transactions by day (only compare day/month/year) @@ -211,6 +254,7 @@ class TransactionsActivityStore: ObservableObject, WalletObserverStore { userAssets: userAssets, allTokens: allTokens, assetRatios: assetRatios, + nftMetadata: nftMetadata, solEstimatedTxFee: solEstimatedTxFees[transaction.id], currencyFormatter: currencyFormatter, decimalFormatStyle: .decimals(precision: 4) diff --git a/Sources/BraveWallet/Crypto/Transactions/TransactionParser+TransactionSummary.swift b/Sources/BraveWallet/Crypto/Transactions/TransactionParser+TransactionSummary.swift index 8b79614a845..5104fe34768 100644 --- a/Sources/BraveWallet/Crypto/Transactions/TransactionParser+TransactionSummary.swift +++ b/Sources/BraveWallet/Crypto/Transactions/TransactionParser+TransactionSummary.swift @@ -44,6 +44,7 @@ extension TransactionParser { userAssets: [BraveWallet.BlockchainToken], allTokens: [BraveWallet.BlockchainToken], assetRatios: [String: Double], + nftMetadata: [String: NFTMetadata], solEstimatedTxFee: UInt64?, currencyFormatter: NumberFormatter ) -> TransactionSummary { @@ -54,6 +55,7 @@ extension TransactionParser { userAssets: userAssets, allTokens: allTokens, assetRatios: assetRatios, + nftMetadata: nftMetadata, solEstimatedTxFee: solEstimatedTxFee, currencyFormatter: currencyFormatter, decimalFormatStyle: .balance // use 4 digit precision for summary diff --git a/Sources/BraveWallet/Crypto/Transactions/TransactionParser.swift b/Sources/BraveWallet/Crypto/Transactions/TransactionParser.swift index 41cc81f4ee3..953a1b31115 100644 --- a/Sources/BraveWallet/Crypto/Transactions/TransactionParser.swift +++ b/Sources/BraveWallet/Crypto/Transactions/TransactionParser.swift @@ -73,7 +73,7 @@ enum TransactionParser { gasFee = .init(fee: gasFeeString, fiat: "$0.00") } } - case .btc: + case .btc, .zec: break @unknown default: break @@ -103,6 +103,7 @@ enum TransactionParser { userAssets: [BraveWallet.BlockchainToken], allTokens: [BraveWallet.BlockchainToken], assetRatios: [String: Double], + nftMetadata: [String: NFTMetadata], solEstimatedTxFee: UInt64?, currencyFormatter: NumberFormatter, decimalFormatStyle: WeiFormatter.DecimalFormatStyle? = nil @@ -185,6 +186,7 @@ enum TransactionParser { fromValue: fromValue, fromAmount: fromValueFormatted, fromFiat: fromFiat, + fromTokenMetadata: nil, gasFee: gasFee( from: transaction, network: network, @@ -230,6 +232,7 @@ enum TransactionParser { fromValue: fromValue, fromAmount: fromAmount, fromFiat: fromFiat, + fromTokenMetadata: nil, gasFee: gasFee( from: transaction, network: network, @@ -359,6 +362,12 @@ enum TransactionParser { return nil } let token = token(for: tokenContractAddress, network: network, userAssets: userAssets, allTokens: allTokens) + let tokenNFTMetadata: NFTMetadata? + if let token { + tokenNFTMetadata = nftMetadata[token.id] + } else { + tokenNFTMetadata = nil + } return .init( transaction: transaction, @@ -372,6 +381,7 @@ enum TransactionParser { fromToken: token, fromValue: "1", // Can only send 1 erc721 at a time fromAmount: "1", + nftMetadata: tokenNFTMetadata, owner: owner, tokenId: tokenId, gasFee: gasFee( @@ -413,6 +423,7 @@ enum TransactionParser { fromValue: fromValue, fromAmount: fromValueFormatted, fromFiat: fromFiat, + fromTokenMetadata: nil, gasFee: gasFee( from: transaction, network: network, @@ -431,6 +442,12 @@ enum TransactionParser { return nil } let fromToken = token(for: splTokenMintAddress, network: network, userAssets: userAssets, allTokens: allTokens) + let tokenNFTMetadata: NFTMetadata? + if let fromToken { + tokenNFTMetadata = nftMetadata[fromToken.id] + } else { + tokenNFTMetadata = nil + } let fromValue = "\(amount)" var fromValueFormatted = "" var fromFiat = "$0.00" @@ -464,6 +481,7 @@ enum TransactionParser { fromValue: fromValue, fromAmount: fromValueFormatted, fromFiat: fromFiat, + fromTokenMetadata: tokenNFTMetadata, gasFee: gasFee( from: transaction, network: network, @@ -696,7 +714,9 @@ struct ParsedTransaction: Equatable { case let .solDappTransaction(details), let .solSwapTransaction(details): return details.gasFee - case .erc721Transfer, .other: + case let .erc721Transfer(details): + return details.gasFee + case .other: return nil case let .filSend(details): return details.gasFee @@ -801,6 +821,8 @@ struct SendDetails: Equatable { let fromAmount: String /// The amount formatted as currency let fromFiat: String? + /// Metadata if `fromToken` is an NFT + let fromTokenMetadata: NFTMetadata? /// Gas fee for the transaction let gasFee: GasFee? @@ -836,6 +858,8 @@ struct Eth721TransferDetails: Equatable { let fromValue: String /// From amount formatted let fromAmount: String + /// Metadata for the NFT being sent + let nftMetadata: NFTMetadata? /// Owner (must not be confused with the caller (fromAddress) let owner: String @@ -906,6 +930,7 @@ extension BraveWallet.TransactionInfo { userAssets: [BraveWallet.BlockchainToken], allTokens: [BraveWallet.BlockchainToken], assetRatios: [String: Double], + nftMetadata: [String: NFTMetadata], solEstimatedTxFee: UInt64? = nil, currencyFormatter: NumberFormatter, decimalFormatStyle: WeiFormatter.DecimalFormatStyle? = nil @@ -917,6 +942,7 @@ extension BraveWallet.TransactionInfo { userAssets: userAssets, allTokens: allTokens, assetRatios: assetRatios, + nftMetadata: nftMetadata, solEstimatedTxFee: solEstimatedTxFee, currencyFormatter: currencyFormatter, decimalFormatStyle: decimalFormatStyle @@ -984,11 +1010,13 @@ extension BraveWallet.TransactionInfo { if let erc721ContractAddress { return [erc721ContractAddress] } + case .solanaSplTokenTransfer, .solanaSplTokenTransferWithAssociatedTokenAccountCreation: + if let splTokenMintAddress = txDataUnion.solanaTxData?.splTokenMintAddress { + return [splTokenMintAddress] + } case .ethSend, .erc1155SafeTransferFrom, .other: break case .solanaSystemTransfer, - .solanaSplTokenTransfer, - .solanaSplTokenTransferWithAssociatedTokenAccountCreation, .solanaDappSignTransaction, .solanaDappSignAndSendTransaction, .solanaSwap: diff --git a/Sources/BraveWallet/Crypto/Transactions/TransactionSummaryViews.swift b/Sources/BraveWallet/Crypto/Transactions/TransactionSummaryViews.swift index ab4d607ddea..cd40ca3f390 100644 --- a/Sources/BraveWallet/Crypto/Transactions/TransactionSummaryViews.swift +++ b/Sources/BraveWallet/Crypto/Transactions/TransactionSummaryViews.swift @@ -19,6 +19,7 @@ struct TransactionSummaryViewContainer: View { SendTransactionSummaryView( sentFromAccountName: parsedTransaction.namedFromAddress, token: details.fromToken, + nftMetadata: details.fromTokenMetadata, network: parsedTransaction.network, valueSent: details.fromAmount, fiatValueSent: details.fromFiat ?? "", @@ -67,6 +68,7 @@ struct TransactionSummaryViewContainer: View { SendTransactionSummaryView( sentFromAccountName: parsedTransaction.namedFromAddress, token: details.fromToken, + nftMetadata: details.nftMetadata, network: parsedTransaction.network, valueSent: nil, fiatValueSent: nil, @@ -101,6 +103,7 @@ struct SendTransactionSummaryView: View { let sentFromAccountName: String let token: BraveWallet.BlockchainToken? + let nftMetadata: NFTMetadata? let network: BraveWallet.NetworkInfo let valueSent: String? let fiatValueSent: String? @@ -110,6 +113,7 @@ struct SendTransactionSummaryView: View { init( sentFromAccountName: String, token: BraveWallet.BlockchainToken?, + nftMetadata: NFTMetadata? = nil, network: BraveWallet.NetworkInfo, valueSent: String?, fiatValueSent: String?, @@ -117,6 +121,7 @@ struct SendTransactionSummaryView: View { time: Date ) { self.sentFromAccountName = sentFromAccountName + self.nftMetadata = nftMetadata self.token = token self.network = network self.valueSent = valueSent @@ -162,7 +167,7 @@ struct SendTransactionSummaryView: View { NFTIconView( token: token, network: network, - url: nil, + url: nftMetadata?.imageURL, shouldShowNetworkIcon: true, length: length, maxLength: maxLength, diff --git a/Sources/BraveWallet/Preview Content/MockContent.swift b/Sources/BraveWallet/Preview Content/MockContent.swift index a4f2e091963..f9449f5cc74 100644 --- a/Sources/BraveWallet/Preview Content/MockContent.swift +++ b/Sources/BraveWallet/Preview Content/MockContent.swift @@ -285,6 +285,47 @@ extension BraveWallet.TransactionInfo { effectiveRecipient: BraveWallet.BlockchainToken.previewDaiToken.contractAddress ) } + /// Sent `mockERC721NFTToken` NFT + static var previewERC721Send: BraveWallet.TransactionInfo { + BraveWallet.TransactionInfo( + id: "81111c05-612a-47c5-84b0-e95045d15b37", + fromAddress: BraveWallet.AccountInfo.previewAccount.accountId.address, + from: BraveWallet.AccountInfo.previewAccount.accountId, + txHash: "0x46d0ecf2ec9829d451154767c98ae372413bac809c25b16d1946aba100663e4b", + txDataUnion: .init( + ethTxData1559: .init( + baseData: .init( + nonce: "0x5", + gasPrice: "0xa06907542", + gasLimit: "0x12e61", + to: BraveWallet.BlockchainToken.mockERC721NFTToken.contractAddress, + value: "0x0", + data: _transactionBase64ToData("CV6nswAAAAAAAAAAAAAAAOWSQnoK7Okt4+3uHxjgFXwFhhVk//////////////////////////////////////////8="), + signOnly: false, + signedTransaction: nil + ), + chainId: BraveWallet.MainnetChainId, + maxPriorityFeePerGas: "", + maxFeePerGas: "", + gasEstimation: nil + ) + ), + txStatus: .confirmed, + txType: .erc721SafeTransferFrom, + txParams: ["address", "address", "uint256"], + txArgs: [ + "0x35dcec532e809a3daa04ed3fd958586f7bac9191", // owner + "0x3f2116ef98fcab1a9c3c2d8988e0064ab59acfca", // to address + BraveWallet.BlockchainToken.mockERC721NFTToken.tokenId // token id + ], + createdTime: Date(timeIntervalSince1970: 1636399671), // Monday, November 8, 2021 7:27:51 PM + submittedTime: Date(timeIntervalSince1970: 1636399673), // Monday, November 8, 2021 7:27:53 PM + confirmedTime: Date(timeIntervalSince1970: 1636402508), // Monday, November 8, 2021 8:15:08 PM + originInfo: .init(), + chainId: BraveWallet.MainnetChainId, + effectiveRecipient: "0x3f2116ef98fcab1a9c3c2d8988e0064ab59acfca" + ) + } /// Solana System Transfer static var previewConfirmedSolSystemTransfer: BraveWallet.TransactionInfo { BraveWallet.TransactionInfo( @@ -431,6 +472,7 @@ extension TransactionSummary { userAssets: [.previewToken, .previewDaiToken], allTokens: [], assetRatios: [BraveWallet.BlockchainToken.previewToken.assetRatioId.lowercased(): 1], + nftMetadata: [:], solEstimatedTxFee: nil, currencyFormatter: .usdCurrencyFormatter ) @@ -451,6 +493,7 @@ extension ParsedTransaction { userAssets: [.previewToken, .previewDaiToken], allTokens: [], assetRatios: [BraveWallet.BlockchainToken.previewToken.assetRatioId.lowercased(): 1], + nftMetadata: [:], solEstimatedTxFee: nil, currencyFormatter: .usdCurrencyFormatter ) diff --git a/Sources/BraveWallet/Preview Content/MockStores.swift b/Sources/BraveWallet/Preview Content/MockStores.swift index 4f87ffe86c8..fbbf6a8c7bb 100644 --- a/Sources/BraveWallet/Preview Content/MockStores.swift +++ b/Sources/BraveWallet/Preview Content/MockStores.swift @@ -236,6 +236,7 @@ extension TransactionsActivityStore { blockchainRegistry: MockBlockchainRegistry(), txService: MockTxService(), solTxManagerProxy: BraveWallet.TestSolanaTxManagerProxy.previewProxy, + ipfsApi: TestIpfsAPI(), userAssetManager: TestableWalletUserAssetManager() ) } diff --git a/Tests/BraveWalletTests/TransactionParserTests.swift b/Tests/BraveWalletTests/TransactionParserTests.swift index 59933b42385..151df05c1d1 100644 --- a/Tests/BraveWalletTests/TransactionParserTests.swift +++ b/Tests/BraveWalletTests/TransactionParserTests.swift @@ -131,6 +131,7 @@ class TransactionParserTests: XCTestCase { fromValue: "0x1b667a56d488000", fromAmount: "0.1234", fromFiat: "$0.12", + fromTokenMetadata: nil, gasFee: .init( fee: "0.000031", fiat: "$0.000031" @@ -146,6 +147,7 @@ class TransactionParserTests: XCTestCase { userAssets: tokens, allTokens: tokens, assetRatios: assetRatios, + nftMetadata: [:], solEstimatedTxFee: nil, currencyFormatter: currencyFormatter ) else { @@ -226,6 +228,7 @@ class TransactionParserTests: XCTestCase { fromValue: "0x5ff20a91f724000", fromAmount: "0.4321", fromFiat: "$0.86", + fromTokenMetadata: nil, gasFee: .init( fee: "0.000078", fiat: "$0.000078" @@ -241,6 +244,7 @@ class TransactionParserTests: XCTestCase { userAssets: tokens, allTokens: tokens, assetRatios: assetRatios, + nftMetadata: [:], solEstimatedTxFee: nil, currencyFormatter: currencyFormatter ) else { @@ -331,6 +335,7 @@ class TransactionParserTests: XCTestCase { userAssets: tokens, allTokens: tokens, assetRatios: assetRatios, + nftMetadata: [:], solEstimatedTxFee: nil, currencyFormatter: currencyFormatter ) else { @@ -421,6 +426,7 @@ class TransactionParserTests: XCTestCase { userAssets: tokens, allTokens: tokens, assetRatios: assetRatios, + nftMetadata: [:], solEstimatedTxFee: nil, currencyFormatter: currencyFormatter ) else { @@ -506,6 +512,7 @@ class TransactionParserTests: XCTestCase { userAssets: tokens, allTokens: tokens, assetRatios: assetRatios, + nftMetadata: [:], solEstimatedTxFee: nil, currencyFormatter: currencyFormatter ) else { @@ -591,6 +598,7 @@ class TransactionParserTests: XCTestCase { userAssets: tokens, allTokens: tokens, assetRatios: assetRatios, + nftMetadata: [:], solEstimatedTxFee: nil, currencyFormatter: currencyFormatter ) else { @@ -661,6 +669,7 @@ class TransactionParserTests: XCTestCase { fromToken: .previewDaiToken, fromValue: "1", fromAmount: "1", + nftMetadata: nil, owner: "0x1111111111aaaaaaaaaa2222222222bbbbbbbbbb", tokenId: "token.id", gasFee: .init( @@ -678,6 +687,7 @@ class TransactionParserTests: XCTestCase { userAssets: tokens, allTokens: tokens, assetRatios: assetRatios, + nftMetadata: [:], solEstimatedTxFee: nil, currencyFormatter: currencyFormatter ) else { @@ -746,6 +756,7 @@ class TransactionParserTests: XCTestCase { fromValue: "100000000", fromAmount: "0.1", fromFiat: "$2.00", + fromTokenMetadata: nil, gasFee: .init( fee: "0.00123", fiat: "$0.0246" @@ -761,6 +772,7 @@ class TransactionParserTests: XCTestCase { userAssets: tokens, allTokens: tokens, assetRatios: assetRatios, + nftMetadata: [:], solEstimatedTxFee: 1230000, currencyFormatter: currencyFormatter ) else { @@ -843,6 +855,7 @@ class TransactionParserTests: XCTestCase { fromValue: "43210000", fromAmount: "43.21", fromFiat: "$648.15", + fromTokenMetadata: nil, gasFee: .init( fee: "0.0123", fiat: "$0.246" @@ -858,6 +871,7 @@ class TransactionParserTests: XCTestCase { userAssets: tokens, allTokens: tokens, assetRatios: assetRatios, + nftMetadata: [:], solEstimatedTxFee: 12300000, currencyFormatter: currencyFormatter ) else { @@ -927,6 +941,7 @@ class TransactionParserTests: XCTestCase { fromValue: "1", fromAmount: "1", fromFiat: "", + fromTokenMetadata: nil, gasFee: .init( fee: "0.0123", fiat: "$0.246" @@ -942,6 +957,7 @@ class TransactionParserTests: XCTestCase { userAssets: tokens, allTokens: tokens, assetRatios: assetRatios, + nftMetadata: [:], solEstimatedTxFee: 12300000, currencyFormatter: currencyFormatter ) else { @@ -1156,6 +1172,7 @@ class TransactionParserTests: XCTestCase { userAssets: tokens, allTokens: tokens, assetRatios: assetRatios, + nftMetadata: [:], solEstimatedTxFee: nil, currencyFormatter: currencyFormatter ) else { diff --git a/Tests/BraveWalletTests/TransactionsActivityStoreTests.swift b/Tests/BraveWalletTests/TransactionsActivityStoreTests.swift index 775eacc547c..aa273482c35 100644 --- a/Tests/BraveWalletTests/TransactionsActivityStoreTests.swift +++ b/Tests/BraveWalletTests/TransactionsActivityStoreTests.swift @@ -65,6 +65,16 @@ class TransactionsActivityStoreTests: XCTestCase { completion([.mockFilecoinMainnet, .mockFilecoinTestnet]) } } + rpcService._erc721Metadata = { _, _, _, completion in + let metadata = """ + { + "image": "mock.image.url", + "name": "mock nft name", + "description": "mock nft description" + } + """ + completion( "", metadata, .success, "") + } let walletService = BraveWallet.TestBraveWalletService() walletService._addObserver = { _ in } @@ -81,6 +91,7 @@ class TransactionsActivityStoreTests: XCTestCase { } let firstTransactionDate = Date(timeIntervalSince1970: 1636399671) // Monday, November 8, 2021 7:27:51 PM + let ethNFTSendTxCopy = BraveWallet.TransactionInfo.previewERC721Send.copy() as! BraveWallet.TransactionInfo // default in mainnet let ethSendTxCopy = BraveWallet.TransactionInfo.previewConfirmedSend.copy() as! BraveWallet.TransactionInfo // default in mainnet let goerliSwapTxCopy = BraveWallet.TransactionInfo.previewConfirmedSwap.copy() as! BraveWallet.TransactionInfo goerliSwapTxCopy.chainId = BraveWallet.GoerliChainId @@ -90,7 +101,11 @@ class TransactionsActivityStoreTests: XCTestCase { let filSendTxCopy = BraveWallet.TransactionInfo.mockFilUnapprovedSend.copy() as! BraveWallet.TransactionInfo let filTestnetSendTxCopy = BraveWallet.TransactionInfo.mockFilUnapprovedSend.copy() as! BraveWallet.TransactionInfo filTestnetSendTxCopy.chainId = BraveWallet.FilecoinTestnet - let txs: [BraveWallet.TransactionInfo] = [ethSendTxCopy, goerliSwapTxCopy, solSendTxCopy, solTestnetSendTxCopy, filSendTxCopy, filTestnetSendTxCopy] + let txs: [BraveWallet.TransactionInfo] = [ + ethNFTSendTxCopy, ethSendTxCopy, goerliSwapTxCopy, + solSendTxCopy, solTestnetSendTxCopy, + filSendTxCopy, filTestnetSendTxCopy + ] var timeIntervalIncrement: TimeInterval = 0 let mockTxs: [BraveWallet.TransactionInfo] = txs.enumerated().map { (index, tx) in tx.txStatus = .unapproved @@ -106,23 +121,23 @@ class TransactionsActivityStoreTests: XCTestCase { let txService = BraveWallet.TestTxService() txService._addObserver = { _ in } txService._allTransactionInfo = { coin, chainId, address, completion in - if coin == .eth { - completion(mockTxs.filter({ $0.chainId == chainId && $0.coin == coin })) - } else if coin == .sol { - completion(mockTxs.filter({ $0.chainId == chainId && $0.coin == coin })) - } else { // .fil - completion(mockTxs.filter({ $0.chainId == chainId && $0.coin == coin })) - } + completion(mockTxs.filter({ $0.chainId == chainId && $0.coin == coin })) } let solTxManagerProxy = BraveWallet.TestSolanaTxManagerProxy() solTxManagerProxy._estimatedTxFee = { $2(UInt64(1), .success, "") } let mockUserManager = TestableWalletUserAssetManager() - mockUserManager._getAllUserAssetsInNetworkAssetsByVisibility = { [weak self] networks, _ in + mockUserManager._getAllUserAssetsInNetworkAssets = { [weak self] networks, _ in var networkAssets: [NetworkAssets] = [] for network in networks { - networkAssets.append(NetworkAssets(network: network, tokens: self?.visibleAssetsForCoins[network.coin] ?? [], sortOrder: 0)) + networkAssets.append( + NetworkAssets( + network: network, + tokens: self?.visibleAssetsForCoins[network.coin] ?? [], + sortOrder: 0 + ) + ) } return networkAssets } @@ -135,18 +150,20 @@ class TransactionsActivityStoreTests: XCTestCase { blockchainRegistry: blockchainRegistry, txService: txService, solTxManagerProxy: solTxManagerProxy, + ipfsApi: TestIpfsAPI(), userAssetManager: mockUserManager ) let transactionsExpectation = expectation(description: "transactionsExpectation") store.$transactionSections .dropFirst() - .collect(2) + .collect(3) .sink { transactionSectionsUpdates in defer { transactionsExpectation.fulfill() } guard let transactionSectionsWithoutPrices = transactionSectionsUpdates.first, - let transactionSectionsWithPrices = transactionSectionsUpdates[safe: 1] else { - XCTFail("Expected 2 updates to transactionSummaries") + let transactionSectionsWithPrices = transactionSectionsUpdates[safe: 1], + let transactionSectionsWithNFTMetadata = transactionSectionsUpdates[safe: 2] else { + XCTFail("Expected 3 updates to transactionSummaries") return } let expectedTransactions = mockTxs @@ -184,78 +201,97 @@ class TransactionsActivityStoreTests: XCTestCase { XCTAssertEqual( transactionSectionsWithPrices[safe: 0]?.transactions[safe: 0]?.transaction, filTestnetSendTxCopy) - // Day 1 Transaction 2 + // Day 2 Transaction 1 XCTAssertEqual( - transactionSectionsWithoutPrices[safe: 0]?.transactions[safe: 1]?.transaction, + transactionSectionsWithoutPrices[safe: 1]?.transactions[safe: 0]?.transaction, filSendTxCopy) XCTAssertEqual( - transactionSectionsWithoutPrices[safe: 0]?.transactions[safe: 1]?.transaction.chainId, + transactionSectionsWithoutPrices[safe: 1]?.transactions[safe: 0]?.transaction.chainId, filSendTxCopy.chainId) XCTAssertEqual( - transactionSectionsWithPrices[safe: 0]?.transactions[safe: 1]?.transaction, + transactionSectionsWithPrices[safe: 1]?.transactions[safe: 0]?.transaction, filSendTxCopy) - // Day 2 Transaction 1 + // Day 2 Transaction 2 XCTAssertEqual( - transactionSectionsWithoutPrices[safe: 1]?.transactions[safe: 0]?.transaction, + transactionSectionsWithoutPrices[safe: 1]?.transactions[safe: 1]?.transaction, solTestnetSendTxCopy) XCTAssertEqual( - transactionSectionsWithoutPrices[safe: 1]?.transactions[safe: 0]?.transaction.chainId, + transactionSectionsWithoutPrices[safe: 1]?.transactions[safe: 1]?.transaction.chainId, solTestnetSendTxCopy.chainId) XCTAssertEqual( - transactionSectionsWithPrices[safe: 1]?.transactions[safe: 0]?.transaction, + transactionSectionsWithPrices[safe: 1]?.transactions[safe: 1]?.transaction, solTestnetSendTxCopy) - // Day 2 Transaction 2 + // Day 3 Transaction 1 XCTAssertEqual( - transactionSectionsWithoutPrices[safe: 1]?.transactions[safe: 1]?.transaction, + transactionSectionsWithoutPrices[safe: 2]?.transactions[safe: 0]?.transaction, solSendTxCopy) XCTAssertEqual( - transactionSectionsWithoutPrices[safe: 1]?.transactions[safe: 1]?.transaction.chainId, + transactionSectionsWithoutPrices[safe: 2]?.transactions[safe: 0]?.transaction.chainId, solSendTxCopy.chainId) XCTAssertEqual( - transactionSectionsWithPrices[safe: 1]?.transactions[safe: 1]?.transaction, + transactionSectionsWithPrices[safe: 2]?.transactions[safe: 0]?.transaction, solSendTxCopy) - // Day 3 Transaction 1 + // Day 3 Transaction 2 XCTAssertEqual( - transactionSectionsWithoutPrices[safe: 2]?.transactions[safe: 0]?.transaction, + transactionSectionsWithoutPrices[safe: 2]?.transactions[safe: 1]?.transaction, goerliSwapTxCopy) XCTAssertEqual( - transactionSectionsWithoutPrices[safe: 2]?.transactions[safe: 0]?.transaction.chainId, + transactionSectionsWithoutPrices[safe: 2]?.transactions[safe: 1]?.transaction.chainId, goerliSwapTxCopy.chainId) XCTAssertEqual( - transactionSectionsWithPrices[safe: 2]?.transactions[safe: 0]?.transaction, + transactionSectionsWithPrices[safe: 2]?.transactions[safe: 1]?.transaction, goerliSwapTxCopy) - // Day 3 Transaction 2 + // Day 4 Transaction 1 XCTAssertEqual( - transactionSectionsWithoutPrices[safe: 2]?.transactions[safe: 1]?.transaction, + transactionSectionsWithoutPrices[safe: 3]?.transactions[safe: 0]?.transaction, ethSendTxCopy) XCTAssertEqual( - transactionSectionsWithoutPrices[safe: 2]?.transactions[safe: 1]?.transaction.chainId, + transactionSectionsWithoutPrices[safe: 3]?.transactions[safe: 0]?.transaction.chainId, ethSendTxCopy.chainId) XCTAssertEqual( - transactionSectionsWithPrices[safe: 2]?.transactions[safe: 1]?.transaction, + transactionSectionsWithPrices[safe: 3]?.transactions[safe: 0]?.transaction, ethSendTxCopy) + // Day 4 Transaction 2 + let parsedNFTTransaction = transactionSectionsWithNFTMetadata[safe: 3]?.transactions[safe: 1] + XCTAssertEqual( + parsedNFTTransaction?.transaction, + ethNFTSendTxCopy) + XCTAssertEqual( + parsedNFTTransaction?.transaction.chainId, + ethNFTSendTxCopy.chainId) + XCTAssertEqual( + parsedNFTTransaction?.transaction, + ethNFTSendTxCopy) + if case .erc721Transfer(let details) = parsedNFTTransaction?.details { + XCTAssertNotNil(details.nftMetadata) + } else { + XCTFail("Expected to find nft metadata") + } - XCTAssertNil(transactionSectionsWithPrices[safe: 3]) - + XCTAssertNil(transactionSectionsWithPrices[safe: 4]) + // verify gas fee fiat XCTAssertEqual( transactionSectionsWithPrices[safe: 0]?.transactions[safe: 0]?.gasFee?.fiat, "$0.0000006232") - XCTAssertEqual( - transactionSectionsWithPrices[safe: 0]?.transactions[safe: 1]?.gasFee?.fiat, - "$0.0000006232") XCTAssertEqual( transactionSectionsWithPrices[safe: 1]?.transactions[safe: 0]?.gasFee?.fiat, - "$0.000000002") + "$0.0000006232") XCTAssertEqual( transactionSectionsWithPrices[safe: 1]?.transactions[safe: 1]?.gasFee?.fiat, "$0.000000002") XCTAssertEqual( transactionSectionsWithPrices[safe: 2]?.transactions[safe: 0]?.gasFee?.fiat, - "$255.03792654") + "$0.000000002") XCTAssertEqual( transactionSectionsWithPrices[safe: 2]?.transactions[safe: 1]?.gasFee?.fiat, - "$10.41008598" ) + "$255.03792654") + XCTAssertEqual( + transactionSectionsWithPrices[safe: 3]?.transactions[safe: 0]?.gasFee?.fiat, + "$10.41008598") + XCTAssertEqual( + transactionSectionsWithPrices[safe: 3]?.transactions[safe: 1]?.gasFee?.fiat, + "$10.19894667") } .store(in: &cancellables)