From b064cfaa99dc02d1c53a1a272b08e5472c650506 Mon Sep 17 00:00:00 2001 From: Nick von Pentz <12549658+nvonpentz@users.noreply.github.com> Date: Mon, 24 Oct 2022 10:03:44 -0400 Subject: [PATCH] Asset discovery refactor * Move asset discovery logic from JsonRpcService to new AssetDiscoveryManager class * Add DiscoverAssetsOnAllOnRefresh and DiscoverAssetsOnAllSupportedChains APIs to AssetDiscoveryManager, and expose DiscoverAssetsOnAllOnRefresh to frontend via mojo API on BraveWalletService * Add AccountsAdded event to KeyringService emitted when a new account is added via any method (hardware import, private key import, etc) that is triggers the AssetDiscoveryManager * Add kBraveWalletAssetsLastDiscoveredAt and kBraveWalletNextAssetDiscoveryFromBlocks prefs to manage asset discovery state including rate limiting and last successful asset search per coin type / chain ID --- .../chrome/browser/app/domain/DappsModel.java | 3 + .../browser/app/domain/KeyringModel.java | 3 + .../observers/KeyringServiceObserverImpl.java | 8 + browser/brave_wallet/BUILD.gn | 1 + .../asset_discovery_manager_unittest.cc | 1206 +++++++++++++++++ .../brave_wallet_service_browsertest.cc | 2 + .../brave_wallet_service_unittest.cc | 3 + .../brave_wallet/keyring_service_unittest.cc | 238 +--- components/brave_wallet/browser/BUILD.gn | 2 + .../browser/asset_discovery_manager.cc | 348 +++++ .../browser/asset_discovery_manager.h | 145 ++ .../browser/brave_wallet_constants.h | 5 + .../brave_wallet/browser/brave_wallet_p3a.h | 5 + .../browser/brave_wallet_prefs.cc | 2 + .../browser/brave_wallet_service.cc | 25 + .../browser/brave_wallet_service.h | 7 + .../browser/ethereum_provider_impl.h | 3 + .../brave_wallet/browser/json_rpc_service.cc | 274 +--- .../brave_wallet/browser/json_rpc_service.h | 41 +- .../browser/json_rpc_service_unittest.cc | 318 ++--- .../brave_wallet/browser/keyring_service.cc | 30 +- .../brave_wallet/browser/keyring_service.h | 11 +- components/brave_wallet/browser/pref_names.cc | 4 + components/brave_wallet/browser/pref_names.h | 2 + .../browser/solana_provider_impl.h | 2 + components/brave_wallet/browser/tx_manager.h | 3 + .../brave_wallet/common/brave_wallet.mojom | 9 + .../common/wallet_api_proxy.ts | 6 + 28 files changed, 2026 insertions(+), 680 deletions(-) create mode 100644 browser/brave_wallet/asset_discovery_manager_unittest.cc create mode 100644 components/brave_wallet/browser/asset_discovery_manager.cc create mode 100644 components/brave_wallet/browser/asset_discovery_manager.h diff --git a/android/java/org/chromium/chrome/browser/app/domain/DappsModel.java b/android/java/org/chromium/chrome/browser/app/domain/DappsModel.java index 56ba3fc2695..5496723e82f 100644 --- a/android/java/org/chromium/chrome/browser/app/domain/DappsModel.java +++ b/android/java/org/chromium/chrome/browser/app/domain/DappsModel.java @@ -315,6 +315,9 @@ public class DappsModel implements KeyringServiceObserver { @Override public void accountsChanged() {} + @Override + public void accountsAdded(int coin, String[] addresses) {} + @Override public void autoLockMinutesChanged() {} diff --git a/android/java/org/chromium/chrome/browser/app/domain/KeyringModel.java b/android/java/org/chromium/chrome/browser/app/domain/KeyringModel.java index de22d0ae787..3121f8d0eeb 100644 --- a/android/java/org/chromium/chrome/browser/app/domain/KeyringModel.java +++ b/android/java/org/chromium/chrome/browser/app/domain/KeyringModel.java @@ -329,6 +329,9 @@ public class KeyringModel implements KeyringServiceObserver { update(); } + @Override + public void accountsAdded(int coin, String[] addresses) {} + @Override public void autoLockMinutesChanged() {} diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/observers/KeyringServiceObserverImpl.java b/android/java/org/chromium/chrome/browser/crypto_wallet/observers/KeyringServiceObserverImpl.java index f8ef8a7ca41..cf0615976b2 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/observers/KeyringServiceObserverImpl.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/observers/KeyringServiceObserverImpl.java @@ -17,6 +17,7 @@ public class KeyringServiceObserverImpl implements KeyringServiceObserver { default void keyringReset() {} default void unlocked() {} default void accountsChanged() {} + default void accountsAdded(int coin, String[] addresses) {} default void autoLockMinutesChanged() {} default void selectedAccountChanged(int coin) {} } @@ -76,6 +77,13 @@ public class KeyringServiceObserverImpl implements KeyringServiceObserver { mDelegate.accountsChanged(); } + @Override + public void accountsAdded(int coin, String[] addresses) { + if (mDelegate == null) return; + + mDelegate.accountsAdded(coin, addresses); + } + @Override public void autoLockMinutesChanged() { if (mDelegate == null) return; diff --git a/browser/brave_wallet/BUILD.gn b/browser/brave_wallet/BUILD.gn index 16fdd4bbe6f..d471f53014b 100644 --- a/browser/brave_wallet/BUILD.gn +++ b/browser/brave_wallet/BUILD.gn @@ -144,6 +144,7 @@ source_set("tab_helper") { source_set("unit_tests") { testonly = true sources = [ + "asset_discovery_manager_unittest.cc", "blockchain_images_source_unittest.cc", "brave_wallet_p3a_unittest.cc", "brave_wallet_prefs_unittest.cc", diff --git a/browser/brave_wallet/asset_discovery_manager_unittest.cc b/browser/brave_wallet/asset_discovery_manager_unittest.cc new file mode 100644 index 00000000000..6e5692237b7 --- /dev/null +++ b/browser/brave_wallet/asset_discovery_manager_unittest.cc @@ -0,0 +1,1206 @@ +/* Copyright (c) 2022 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "brave/components/brave_wallet/browser/asset_discovery_manager.h" + +#include "base/memory/raw_ptr.h" +#include "base/strings/strcat.h" +#include "base/test/bind.h" +#include "base/test/scoped_feature_list.h" +#include "base/time/time.h" +#include "brave/browser/brave_wallet/json_rpc_service_factory.h" +#include "brave/browser/brave_wallet/keyring_service_factory.h" +#include "brave/browser/brave_wallet/tx_service_factory.h" +#include "brave/components/brave_wallet/browser/blockchain_list_parser.h" +#include "brave/components/brave_wallet/browser/blockchain_registry.h" +#include "brave/components/brave_wallet/browser/brave_wallet_service.h" +#include "brave/components/brave_wallet/browser/brave_wallet_service_delegate.h" +#include "brave/components/brave_wallet/browser/brave_wallet_utils.h" +#include "brave/components/brave_wallet/browser/json_rpc_service.h" +#include "brave/components/brave_wallet/browser/keyring_service.h" +#include "brave/components/brave_wallet/browser/pref_names.h" +#include "brave/components/brave_wallet/browser/tx_service.h" +#include "brave/components/brave_wallet/common/features.h" +#include "brave/components/brave_wallet/common/hex_utils.h" +#include "brave/components/brave_wallet/common/test_utils.h" +#include "brave/components/brave_wallet/common/value_conversion_utils.h" +#include "chrome/browser/prefs/browser_prefs.h" +#include "chrome/test/base/testing_browser_process.h" +#include "chrome/test/base/testing_profile.h" +#include "components/prefs/pref_service.h" +#include "components/prefs/scoped_user_pref_update.h" +#include "components/sync_preferences/testing_pref_service_syncable.h" +#include "content/public/test/browser_task_environment.h" +#include "services/data_decoder/public/cpp/test_support/in_process_data_decoder.h" +#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h" +#include "services/network/test/test_url_loader_factory.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/base/l10n/l10n_util.h" + +namespace brave_wallet { + +namespace { + +const char kMnemonic1[] = + "divide cruise upon flag harsh carbon filter merit once advice bright " + "drive"; +const char kPasswordBrave[] = "brave"; + +void UpdateCustomNetworks(PrefService* prefs, + std::vector* values) { + DictionaryPrefUpdate update(prefs, kBraveWalletCustomNetworks); + base::Value* dict = update.Get(); + ASSERT_TRUE(dict); + base::Value* list = dict->FindKey(kEthereumPrefKey); + if (!list) { + list = dict->SetKey(kEthereumPrefKey, base::Value(base::Value::Type::LIST)); + } + ASSERT_TRUE(list); + auto& list_value = list->GetList(); + list_value.clear(); + for (auto& it : *values) { + list_value.Append(std::move(it)); + } +} + +const std::vector& GetAssetDiscoverySupportedChainsForTest() { + static base::NoDestructor> + asset_discovery_supported_chains({mojom::kMainnetChainId, + mojom::kPolygonMainnetChainId, + mojom::kOptimismMainnetChainId}); + return *asset_discovery_supported_chains; +} + +} // namespace + +class TestBraveWalletServiceObserverForAssetDiscovery + : public brave_wallet::mojom::BraveWalletServiceObserver { + public: + TestBraveWalletServiceObserverForAssetDiscovery() = default; + + void OnDefaultEthereumWalletChanged(mojom::DefaultWallet wallet) override {} + void OnDefaultSolanaWalletChanged(mojom::DefaultWallet wallet) override {} + void OnActiveOriginChanged(mojom::OriginInfoPtr origin_info) override {} + void OnDefaultBaseCurrencyChanged(const std::string& currency) override {} + void OnDefaultBaseCryptocurrencyChanged( + const std::string& cryptocurrency) override {} + void OnNetworkListChanged() override {} + void OnDiscoverAssetsCompleted( + std::vector discovered_assets) override { + ASSERT_EQ(expected_contract_addresses_.size(), discovered_assets.size()); + for (size_t i = 0; i < discovered_assets.size(); i++) { + EXPECT_EQ(expected_contract_addresses_[i], + discovered_assets[i]->contract_address); + } + on_discover_assets_completed_fired_ = true; + run_loop_asset_discovery_->Quit(); + } + + void WaitForOnDiscoverAssetsCompleted( + const std::vector& addresses) { + expected_contract_addresses_ = addresses; + run_loop_asset_discovery_ = std::make_unique(); + run_loop_asset_discovery_->Run(); + } + + bool OnDiscoverAssetsCompletedFired() { + return on_discover_assets_completed_fired_; + } + + mojo::PendingRemote + GetReceiver() { + return observer_receiver_.BindNewPipeAndPassRemote(); + } + void Reset() { + expected_contract_addresses_.clear(); + on_discover_assets_completed_fired_ = false; + } + + private: + std::unique_ptr run_loop_asset_discovery_; + std::vector expected_contract_addresses_; + bool on_discover_assets_completed_fired_ = false; + mojo::Receiver + observer_receiver_{this}; +}; + +class AssetDiscoveryManagerUnitTest : public testing::Test { + public: + AssetDiscoveryManagerUnitTest() + : shared_url_loader_factory_( + base::MakeRefCounted( + &url_loader_factory_)), + task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {} + ~AssetDiscoveryManagerUnitTest() override = default; + + protected: + void SetUp() override { + scoped_feature_list_.InitAndEnableFeature( + features::kNativeBraveWalletFeature); + + TestingProfile::Builder builder; + auto prefs = + std::make_unique(); + RegisterUserProfilePrefs(prefs->registry()); + builder.SetPrefService(std::move(prefs)); + profile_ = builder.Build(); + keyring_service_ = + KeyringServiceFactory::GetServiceForContext(profile_.get()); + json_rpc_service_ = + JsonRpcServiceFactory::GetServiceForContext(profile_.get()); + json_rpc_service_->SetAPIRequestHelperForTesting( + shared_url_loader_factory_); + tx_service = TxServiceFactory::GetServiceForContext(profile_.get()); + wallet_service_ = std::make_unique( + BraveWalletServiceDelegate::Create(profile_.get()), keyring_service_, + json_rpc_service_, tx_service, GetPrefs()); + asset_discovery_manager_ = std::make_unique( + wallet_service_.get(), json_rpc_service_, keyring_service_, GetPrefs()); + asset_discovery_manager_->SetSupportedChainsForTesting( + GetAssetDiscoverySupportedChainsForTest()); + wallet_service_observer_ = + std::make_unique(); + wallet_service_->AddObserver(wallet_service_observer_->GetReceiver()); + } + + void SetInterceptor(const GURL& intended_url, const std::string& content) { + url_loader_factory_.SetInterceptor(base::BindLambdaForTesting( + [&, intended_url, content](const network::ResourceRequest& request) { + if (request.url.spec() == intended_url) { + url_loader_factory_.ClearResponses(); + url_loader_factory_.AddResponse(request.url.spec(), content); + } + })); + } + + void SetLimitExceededJsonErrorResponse() { + url_loader_factory_.SetInterceptor(base::BindLambdaForTesting( + [&](const network::ResourceRequest& request) { + url_loader_factory_.ClearResponses(); + url_loader_factory_.AddResponse(request.url.spec(), + R"({ + "jsonrpc":"2.0", + "id":1, + "error": { + "code":-32005, + "message": "Request exceeds defined limit" + } + })"); + })); + } + + // SetDiscoverAssetsOnAllSupportedChainsInterceptor sets the response + // based on the requested URL and verifies the from / to blocks + // specified in the eth_getLogs query is expected for each URL/chain ID + // requested. + void SetDiscoverAssetsOnAllSupportedChainsInterceptor( + const std::map responses, + const std::map expected_from_blocks, + const std::map expected_to_blocks) { + url_loader_factory_.SetInterceptor(base::BindLambdaForTesting( + [&, responses, expected_from_blocks, + expected_to_blocks](const network::ResourceRequest& request) { + base::StringPiece request_string(request.request_body->elements() + ->at(0) + .As() + .AsStringPiece()); + const auto expected_from_block = + expected_from_blocks.find(request.url.spec())->second; + const auto expected_to_block = + expected_to_blocks.find(request.url.spec())->second; + + EXPECT_NE(request_string.find(base::StringPrintf( + "\"fromBlock\":\"%s\"", expected_from_block.c_str())), + std::string::npos); + EXPECT_NE(request_string.find(base::StringPrintf( + "\"toBlock\":\"%s\"", expected_to_block.c_str())), + std::string::npos); + url_loader_factory_.ClearResponses(); + for (const auto& kv : responses) { + url_loader_factory_.AddResponse(kv.first, kv.second); + } + return; + })); + } + + void TestDiscoverAssets( + const std::string& chain_id, + mojom::CoinType coin, + const std::vector& account_addresses, + const std::vector& expected_token_contract_addresses, + mojom::ProviderError expected_error, + const std::string& expected_error_message, + const std::string& expected_next_asset_discovery_from_block) { + // Set remaining chains to 1 in order since this value needs to end at + // 0 by the end of the DiscoverAssets call in order to trigger the + // event and it will not be set by an outer + // DiscoverAssetsOnAllSupportedChains* call in this unit test. + asset_discovery_manager_->remaining_chains_ = 1; + asset_discovery_manager_->SetDiscoverAssetsCompletedCallbackForTesting( + base::BindLambdaForTesting( + [&](const std::string& chain_id, + const std::vector discovered_assets, + mojom::ProviderError error, const std::string& error_message) { + EXPECT_EQ(chain_id, chain_id); + EXPECT_EQ(expected_error, error); + EXPECT_EQ(expected_error_message, error_message); + ASSERT_EQ(expected_token_contract_addresses.size(), + discovered_assets.size()); + for (size_t i = 0; i < discovered_assets.size(); i++) { + EXPECT_EQ(expected_token_contract_addresses[i], + discovered_assets[i]->contract_address); + } + })); + asset_discovery_manager_->DiscoverAssets( + chain_id, mojom::CoinType::ETH, account_addresses, false, + kEthereumBlockTagEarliest, kEthereumBlockTagLatest); + wallet_service_observer_->WaitForOnDiscoverAssetsCompleted( + expected_token_contract_addresses); + auto next_asset_discovery_from_blocks = + GetPrefs()->GetDict(kBraveWalletNextAssetDiscoveryFromBlocks).Clone(); + const auto path = base::StrCat({kEthereumPrefKey, ".", chain_id}); + const std::string* next_asset_discovery_from_block = + next_asset_discovery_from_blocks.FindStringByDottedPath(path); + if (next_asset_discovery_from_block) { + EXPECT_EQ(*next_asset_discovery_from_block, + expected_next_asset_discovery_from_block); + } else { + ASSERT_EQ(expected_next_asset_discovery_from_block, ""); + } + wallet_service_observer_->Reset(); + } + + void TestDiscoverAssetsOnAllSupportedChainsAccountsAdded( + const std::vector& account_addresses, + const base::Time expected_assets_last_discovered_at_pref, + const base::Value::Dict& expected_next_asset_discovery_from_blocks, + const std::vector& expected_token_contract_addresses = {}) { + std::vector expected_chain_ids_remaining = + GetAssetDiscoverySupportedChainsForTest(); + asset_discovery_manager_->SetDiscoverAssetsCompletedCallbackForTesting( + base::BindLambdaForTesting( + [&](const std::string& chain_id, + const std::vector discovered_assets, + mojom::ProviderError error, const std::string& error_message) { + expected_chain_ids_remaining.erase( + std::remove(expected_chain_ids_remaining.begin(), + expected_chain_ids_remaining.end(), chain_id), + expected_chain_ids_remaining.end()); + })); + asset_discovery_manager_->DiscoverAssetsOnAllSupportedChainsAccountsAdded( + account_addresses); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(expected_chain_ids_remaining.size(), 0u); + EXPECT_EQ(GetPrefs()->GetTime(kBraveWalletLastDiscoveredAssetsAt), + expected_assets_last_discovered_at_pref); + EXPECT_EQ(GetPrefs()->GetDict(kBraveWalletNextAssetDiscoveryFromBlocks), + expected_next_asset_discovery_from_blocks); + EXPECT_FALSE(wallet_service_observer_->OnDiscoverAssetsCompletedFired()); + wallet_service_observer_->Reset(); + } + + void TestDiscoverAssetsOnAllSupportedChainsRefresh( + const std::vector& account_addresses, + base::OnceCallback + assets_last_discovered_at_test_fn, + const base::Value::Dict& expected_next_asset_discovery_from_blocks, + const std::vector& expected_token_contract_addresses, + std::vector expected_chain_ids_remaining = + GetAssetDiscoverySupportedChainsForTest()) { + const base::Time previous_assets_last_discovered_at = + GetPrefs()->GetTime(kBraveWalletLastDiscoveredAssetsAt); + asset_discovery_manager_->SetDiscoverAssetsCompletedCallbackForTesting( + base::BindLambdaForTesting( + [&](const std::string& chain_id, + const std::vector discovered_assets, + mojom::ProviderError error, const std::string& error_message) { + expected_chain_ids_remaining.erase( + std::remove(expected_chain_ids_remaining.begin(), + expected_chain_ids_remaining.end(), chain_id), + expected_chain_ids_remaining.end()); + })); + asset_discovery_manager_->DiscoverAssetsOnAllSupportedChainsRefresh( + account_addresses); + wallet_service_observer_->WaitForOnDiscoverAssetsCompleted( + expected_token_contract_addresses); + EXPECT_EQ(expected_chain_ids_remaining.size(), 0u); + base::Time current_assets_last_discovered_at = + GetPrefs()->GetTime(kBraveWalletLastDiscoveredAssetsAt); + std::move(assets_last_discovered_at_test_fn) + .Run(previous_assets_last_discovered_at, + current_assets_last_discovered_at); + const base::Value::Dict current_next_asset_discovery_from_blocks = + GetPrefs()->GetDict(kBraveWalletNextAssetDiscoveryFromBlocks).Clone(); + EXPECT_EQ(expected_next_asset_discovery_from_blocks, + current_next_asset_discovery_from_blocks); + wallet_service_observer_->Reset(); + } + + PrefService* GetPrefs() { return profile_->GetPrefs(); } + GURL GetNetwork(const std::string& chain_id, mojom::CoinType coin) { + return brave_wallet::GetNetworkURL(GetPrefs(), chain_id, coin); + } + + network::TestURLLoaderFactory url_loader_factory_; + scoped_refptr shared_url_loader_factory_; + std::unique_ptr + wallet_service_observer_; + content::BrowserTaskEnvironment task_environment_; + std::unique_ptr profile_; + std::unique_ptr wallet_service_; + std::unique_ptr asset_discovery_manager_; + raw_ptr keyring_service_ = nullptr; + JsonRpcService* json_rpc_service_; + TxService* tx_service; + base::test::ScopedFeatureList scoped_feature_list_; + data_decoder::test::InProcessDataDecoder in_process_data_decoder_; +}; + +TEST_F(AssetDiscoveryManagerUnitTest, DiscoverAssets) { + auto* blockchain_registry = BlockchainRegistry::GetInstance(); + TokenListMap token_list_map; + std::string response; + + // Unsupported chainId is not supported + TestDiscoverAssets( + mojom::kBinanceSmartChainMainnetChainId, mojom::CoinType::ETH, + {"0xB4B2802129071b2B9eBb8cBB01EA1E4D14B34961"}, {}, + mojom::ProviderError::kMethodNotSupported, + l10n_util::GetStringUTF8(IDS_WALLET_METHOD_NOT_SUPPORTED_ERROR), ""); + + // Empty address is invalid + TestDiscoverAssets(mojom::kMainnetChainId, mojom::CoinType::ETH, {}, {}, + mojom::ProviderError::kInvalidParams, + l10n_util::GetStringUTF8(IDS_WALLET_INVALID_PARAMETERS), + ""); + + // Invalid address is invalid + TestDiscoverAssets(mojom::kMainnetChainId, mojom::CoinType::ETH, + {"0xinvalid"}, {}, mojom::ProviderError::kInvalidParams, + l10n_util::GetStringUTF8(IDS_WALLET_INVALID_PARAMETERS), + ""); + + // Invalid RPC response json response triggers parsing error + auto expected_network = + GetNetwork(mojom::kMainnetChainId, mojom::CoinType::ETH); + std::string token_list_json = R"({ + "0x0d8775f648430679a709e98d2b0cb6250d2887ef": { + "name": "Basic Attention Token", + "logo": "bat.svg", + "erc20": true, + "symbol": "BAT", + "decimals": 18 + } + })"; + ASSERT_TRUE( + ParseTokenList(token_list_json, &token_list_map, mojom::CoinType::ETH)); + blockchain_registry->UpdateTokenList(std::move(token_list_map)); + SetInterceptor(expected_network, "invalid eth_getLogs response"); + TestDiscoverAssets(mojom::kMainnetChainId, mojom::CoinType::ETH, + {"0xB4B2802129071b2B9eBb8cBB01EA1E4D14B34961"}, {}, + mojom::ProviderError::kParsingError, + l10n_util::GetStringUTF8(IDS_WALLET_PARSING_ERROR), ""); + + // Invalid limit exceeded response triggers parsing error + SetLimitExceededJsonErrorResponse(); + TestDiscoverAssets(mojom::kMainnetChainId, mojom::CoinType::ETH, + {"0xB4B2802129071b2B9eBb8cBB01EA1E4D14B34961"}, {}, + mojom::ProviderError::kParsingError, + l10n_util::GetStringUTF8(IDS_WALLET_PARSING_ERROR), ""); + + // Invalid logs (missing addresses) triggers parsing error + response = R"({ + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "blockHash": "0xaefb023131aa58e533c09c0eae29c280460d3976f5235a1ff53159ef37f73073", + "blockNumber": "0xa72603", + "data": "0x000000000000000000000000000000000000000000000006e83695ab1f893c00", + "logIndex": "0x14", + "removed": false, + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000897bb1e945f5aa7ed7f81646e7991eaba63aa4b0", + "0x000000000000000000000000b4b2802129071b2b9ebb8cbb01ea1e4d14b34961" + ], + "transactionHash": "0x5c655301d386f45af116a4aef418491ee27b71ac30be70a593ccffa3754797d4", + "transactionIndex": "0xa" + }, + ] + })"; + SetInterceptor(expected_network, response); + TestDiscoverAssets(mojom::kMainnetChainId, mojom::CoinType::ETH, + {"0xB4B2802129071b2B9eBb8cBB01EA1E4D14B34961"}, {}, + mojom::ProviderError::kParsingError, + l10n_util::GetStringUTF8(IDS_WALLET_PARSING_ERROR), ""); + + // Valid registry token DAI is discovered and added. + // Valid registry token WETH is discovered and added (tests insensitivity to + // lower case addresses in provider logs response). + // Valid BAT is not added because it is already a user asset. + // Invalid LilNoun is not added because it is an ERC721. + token_list_json = R"( + { + "0x0d8775f648430679a709e98d2b0cb6250d2887ef": { + "name": "Basic Attention Token", + "logo": "bat.svg", + "erc20": true, + "symbol": "BAT", + "decimals": 18 + }, + "0x6B175474E89094C44Da98b954EedeAC495271d0F": { + "name": "Dai Stablecoin", + "logo": "dai.svg", + "erc20": true, + "symbol": "DAI", + "decimals": 18 + }, + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2": { + "name": "Wrapped Eth", + "logo": "weth.svg", + "erc20": true, + "symbol": "WETH", + "decimals": 18, + "chainId": "0x1" + }, + "0x4b10701Bfd7BFEdc47d50562b76b436fbB5BdB3B": { + "name": "Lil Nouns", + "logo": "lilnouns.svg", + "erc20": false, + "erc721": true, + "symbol": "LilNouns", + "chainId": "0x1" + } + })"; + ASSERT_TRUE( + ParseTokenList(token_list_json, &token_list_map, mojom::CoinType::ETH)); + blockchain_registry->UpdateTokenList(std::move(token_list_map)); + + // Note: the matching transfer log for WETH uses an all lowercase address + // while the token registry uses checksum address (contains uppercase) + response = R"({ + "jsonrpc":"2.0", + "id":1, + "result":[ + { + "address":"0x6B175474E89094C44Da98b954EedeAC495271d0F", + "blockHash":"0x2961ceb6c16bab72a55f79e394a35f2bf1c62b30446e3537280f7c22c3115e6e", + "blockNumber":"0xd6464a", + "data":"0x00000000000000000000000000000000000000000000000555aff1f0fae8c000", + "logIndex":"0x159", + "removed":false, + "topics":[ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000503828976d22510aad0201ac7ec88293211d23da", + "0x000000000000000000000000b4b2802129071b2b9ebb8cbb01ea1e4d14b34961" + ], + "transactionHash":"0x2e652b70966c6a05f4b3e68f20d6540b7a5ab712385464a7ccf62774d39b7066", + "transactionIndex":"0x9f" + }, + { + "address":"0x4b10701Bfd7BFEdc47d50562b76b436fbB5BdB3B", + "blockHash":"0x2961ceb6c16bab72a55f79e394a35f2bf1c62b30446e3537280f7c22c3115e6e", + "blockNumber":"0xd6464b", + "data":"0x00000000000000000000000000000000000000000000000555aff1f0fae8c000", + "logIndex":"0x159", + "removed":false, + "topics":[ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000503828976d22510aad0201ac7ec88293211d23da", + "0x000000000000000000000000b4b2802129071b2b9ebb8cbb01ea1e4d14b34961" + ], + "transactionHash":"0x2e652b70966c6a05f4b3e68f20d6540b7a5ab712385464a7ccf62774d39b7066", + "transactionIndex":"0x9f" + }, + { + "address":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "blockHash":"0x2961ceb6c16bab72a55f79e394a35f2bf1c62b30446e3537280f7c22c3115e6e", + "blockNumber":"0xd6464c", + "data":"0x00000000000000000000000000000000000000000000000555aff1f0fae8c000", + "logIndex":"0x159", + "removed":false, + "topics":[ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000503828976d22510aad0201ac7ec88293211d23da", + "0x000000000000000000000000b4b2802129071b2b9ebb8cbb01ea1e4d14b34961" + ], + "transactionHash":"0x2e652b70966c6a05f4b3e68f20d6540b7a5ab712385464a7ccf62774d39b7066", + "transactionIndex":"0x9f" + } + ] + })"; + + SetInterceptor(expected_network, response); + TestDiscoverAssets(mojom::kMainnetChainId, mojom::CoinType::ETH, + {"0xB4B2802129071b2B9eBb8cBB01EA1E4D14B34961"}, + {"0x6B175474E89094C44Da98b954EedeAC495271d0F", + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"}, + mojom::ProviderError::kSuccess, "", "0xd6464d"); + + // Discover assets should not run unless using Infura proxy + std::vector values; + mojom::NetworkInfo chain = GetTestNetworkInfo1("0x1"); + values.push_back(brave_wallet::NetworkInfoToValue(chain)); + UpdateCustomNetworks(GetPrefs(), &values); + TestDiscoverAssets( + mojom::kMainnetChainId, mojom::CoinType::ETH, + {"0xB4B2802129071b2B9eBb8cBB01EA1E4D14B34961"}, {}, + mojom::ProviderError::kMethodNotSupported, + l10n_util::GetStringUTF8(IDS_WALLET_METHOD_NOT_SUPPORTED_ERROR), + "0xd6464d"); + + // Discover assets should be supported on Polygon + token_list_json = R"( + { + "0x6B175474E89094C44Da98b954EedeAC495271d0F": { + "name": "Dai Stablecoin", + "logo": "dai.svg", + "erc20": true, + "symbol": "DAI", + "chainId": "0x89", + "decimals": 18 + } + })"; + ASSERT_TRUE( + ParseTokenList(token_list_json, &token_list_map, mojom::CoinType::ETH)); + blockchain_registry->UpdateTokenList(std::move(token_list_map)); + response = R"({ + "jsonrpc":"2.0", + "id":1, + "result":[ + { + "address":"0x6B175474E89094C44Da98b954EedeAC495271d0F", + "blockHash":"0x2961ceb6c16bab72a55f79e394a35f2bf1c62b30446e3537280f7c22c3115e6e", + "blockNumber":"0xd6464c", + "data":"0x00000000000000000000000000000000000000000000000555aff1f0fae8c000", + "logIndex":"0x159", + "removed":false, + "topics":[ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000503828976d22510aad0201ac7ec88293211d23da", + "0x000000000000000000000000b4b2802129071b2b9ebb8cbb01ea1e4d14b34961" + ], + "transactionHash":"0x2e652b70966c6a05f4b3e68f20d6540b7a5ab712385464a7ccf62774d39b7066", + "transactionIndex":"0x9f" + } + ] + })"; + expected_network = + GetNetwork(mojom::kPolygonMainnetChainId, mojom::CoinType::ETH); + SetInterceptor(expected_network, response); + TestDiscoverAssets(mojom::kPolygonMainnetChainId, mojom::CoinType::ETH, + {"0xB4B2802129071b2B9eBb8cBB01EA1E4D14B34961"}, + {"0x6B175474E89094C44Da98b954EedeAC495271d0F"}, + mojom::ProviderError::kSuccess, "", "0xd6464d"); + + // Discover assets should be supported on Optimism + token_list_json = R"({ + "0x6B175474E89094C44Da98b954EedeAC495271d0F": { + "name": "Dai Stablecoin", + "logo": "dai.svg", + "erc20": true, + "symbol": "DAI", + "chainId": "0xa", + "decimals": 18 + }, + "0x4200000000000000000000000000000000000006": { + "name": "WETH optimism", + "logo": "eth.svg", + "erc20": true, + "symbol": "WETH", + "chainId": "0xa", + "decimals": 18 + } + })"; + ASSERT_TRUE( + ParseTokenList(token_list_json, &token_list_map, mojom::CoinType::ETH)); + blockchain_registry->UpdateTokenList(std::move(token_list_map)); + response = R"({ + "jsonrpc":"2.0", + "id":1, + "result":[ + { + "address":"0x6B175474E89094C44Da98b954EedeAC495271d0F", + "blockHash":"0x2961ceb6c16bab72a55f79e394a35f2bf1c62b30446e3537280f7c22c3115e6e", + "blockNumber":"0xd6464c", + "data":"0x00000000000000000000000000000000000000000000000555aff1f0fae8c000", + "logIndex":"0x159", + "removed":false, + "topics":[ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000503828976d22510aad0201ac7ec88293211d23da", + "0x000000000000000000000000b4b2802129071b2b9ebb8cbb01ea1e4d14b34961" + ], + "transactionHash":"0x2e652b70966c6a05f4b3e68f20d6540b7a5ab712385464a7ccf62774d39b7066", + "transactionIndex":"0x9f" + } + ] + })"; + expected_network = + GetNetwork(mojom::kOptimismMainnetChainId, mojom::CoinType::ETH); + SetInterceptor(expected_network, response); + TestDiscoverAssets(mojom::kOptimismMainnetChainId, mojom::CoinType::ETH, + {"0xB4B2802129071b2B9eBb8cBB01EA1E4D14B34961"}, + {"0x6B175474E89094C44Da98b954EedeAC495271d0F"}, + mojom::ProviderError::kSuccess, "", "0xd6464d"); + + // Another Optimism asset discovered at later block + // Asset discovered through block pref should update when newer transfers + // for a given network are found + response = R"({ + "jsonrpc":"2.0", + "id":1, + "result":[ + { + "address":"0x4200000000000000000000000000000000000006", + "blockHash":"0x2961ceb6c16bab72a55f79e394a35f2bf1c62b30446e3537280f7c22c3115e6e", + "blockNumber":"0xd6464d", + "data":"0x00000000000000000000000000000000000000000000000555aff1f0fae8c000", + "logIndex":"0x159", + "removed":false, + "topics":[ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000503828976d22510aad0201ac7ec88293211d23da", + "0x000000000000000000000000b4b2802129071b2b9ebb8cbb01ea1e4d14b34961" + ], + "transactionHash":"0x2e652b70966c6a05f4b3e68f20d6540b7a5ab712385464a7ccf62774d39b7066", + "transactionIndex":"0x9f" + } + ] + })"; + SetInterceptor(expected_network, response); + TestDiscoverAssets(mojom::kOptimismMainnetChainId, mojom::CoinType::ETH, + {"0xB4B2802129071b2B9eBb8cBB01EA1E4D14B34961"}, + {"0x4200000000000000000000000000000000000006"}, + mojom::ProviderError::kSuccess, "", "0xd6464e"); +} + +TEST_F(AssetDiscoveryManagerUnitTest, + DiscoverAssetsOnAllSupportedChainsAccountsAdded) { + // Send valid requests that yield no results and verify + // 1. OnDiscoverAssetsCompleted is called exactly once for each supported + // chain + // 2. All eth_getLogs requests specify all block ranges (earliest to latest) + // 3. kBraveWalletNextAssetDiscoveryFromBlocks & + // kBraveWalletAssetsLastDiscoveredAt prefs are not updated + std::map responses; + std::map expected_from_blocks; + std::map expected_to_blocks; + const std::string default_response = + R"({ "jsonrpc":"2.0", "id":1, "result":[] })"; + for (const std::string& supported_chain_id : + GetAssetDiscoverySupportedChainsForTest()) { + GURL network_url = GetNetwork(supported_chain_id, mojom::CoinType::ETH); + responses[network_url.spec()] = default_response; + expected_from_blocks[network_url.spec()] = kEthereumBlockTagEarliest; + expected_to_blocks[network_url.spec()] = kEthereumBlockTagLatest; + } + const base::Time assets_last_discovered_at = + GetPrefs()->GetTime(kBraveWalletLastDiscoveredAssetsAt); + auto expected_next_asset_discovery_from_blocks = + GetPrefs()->GetDict(kBraveWalletNextAssetDiscoveryFromBlocks).Clone(); + SetDiscoverAssetsOnAllSupportedChainsInterceptor( + responses, expected_from_blocks, expected_to_blocks); + TestDiscoverAssetsOnAllSupportedChainsAccountsAdded( + {"0xB4B2802129071b2B9eBb8cBB01EA1E4D14B34961"}, assets_last_discovered_at, + expected_next_asset_discovery_from_blocks); +} + +TEST_F(AssetDiscoveryManagerUnitTest, + DiscoverAssetsOnAllSupportedChainsRefresh) { + // Send valid requests that yield no results and verify + // 1. OnDiscoverAssetsCompleted is called exactly once for each supported + // chain + // 2. kBraveWalletAssetsLastDiscoveredAt pref is updated + // 3. kBraveWalletNextAssetDiscoveryFromBlocks pref is not updated + const std::vector& supported_chain_ids = + GetAssetDiscoverySupportedChainsForTest(); + std::map responses; + std::map expected_from_blocks; + std::map expected_to_blocks; + std::string default_response = R"({ "jsonrpc":"2.0", "id":1, "result":[] })"; + for (const std::string& chain_id : supported_chain_ids) { + GURL network_url = GetNetwork(chain_id, mojom::CoinType::ETH); + responses[network_url.spec()] = default_response; + expected_from_blocks[network_url.spec()] = kEthereumBlockTagEarliest; + expected_to_blocks[network_url.spec()] = kEthereumBlockTagLatest; + } + + base::Value::Dict expected_next_asset_discovery_from_blocks; // Expect empty + SetDiscoverAssetsOnAllSupportedChainsInterceptor( + responses, expected_from_blocks, expected_to_blocks); + TestDiscoverAssetsOnAllSupportedChainsRefresh( + {"0xB4B2802129071b2B9eBb8cBB01EA1E4D14B34961"}, + base::BindLambdaForTesting([&](base::Time previous, base::Time current) { + EXPECT_TRUE(previous < current); + }), + expected_next_asset_discovery_from_blocks, {}); + + // Send valid requests that yield no results that are aborted because of rate + // limiting rules and verify + // 1. OnDiscoverAssetsCompleted is called exactly once for each supported + // chain + // 2. kBraveWalletLastDiscoveredAssetsAt stays the same + // 3. kBraveWalletNextAssetDiscoveryFromBlocks stays the same (not + // actually tested since the value is null...) + expected_next_asset_discovery_from_blocks.clear(); + TestDiscoverAssetsOnAllSupportedChainsRefresh( + {"0xB4B2802129071b2B9eBb8cBB01EA1E4D14B34961"}, + base::BindLambdaForTesting([&](base::Time previous, base::Time current) { + EXPECT_EQ(previous, current); + }), + expected_next_asset_discovery_from_blocks, {}, {}); + + // Send valid requests that yield new assets on multiple chains and verify + // 1. OnDiscoverAssetsCompleted is called exactly once for each supported + // chain + // 2. Rate limit pref is updated + // 3. Dict pref is is updated + expected_next_asset_discovery_from_blocks.clear(); + responses.clear(); + task_environment_.FastForwardBy( + base::Minutes(kAssetDiscoveryMinutesPerRequest)); + + std::string token_list_json = R"({ + "0x0d8775f648430679a709e98d2b0cb6250d2887ef": { + "name": "Basic Attention Token", + "logo": "bat.svg", + "erc20": true, + "symbol": "BAT", + "chainId": "0x1", + "decimals": 18 + }, + "0x6B175474E89094C44Da98b954EedeAC495271d0F": { + "name": "Dai Stablecoin", + "logo": "dai.svg", + "erc20": true, + "symbol": "DAI", + "chainId": "0x1", + "decimals": 18 + }, + "0x4200000000000000000000000000000000000006": { + "name": "WETH Optimism", + "logo": "weth.svg", + "erc20": true, + "symbol": "WETH", + "chainId": "0xa", + "decimals": 18 + }, + "0x0000000000000000000000000000000000001010": { + "name": "MATIC Polygon", + "logo": "matic.svg", + "erc20": true, + "symbol": "WETH", + "chainId": "0x89", + "decimals": 18 + }})"; + + auto* blockchain_registry = BlockchainRegistry::GetInstance(); + TokenListMap token_list_map; + + ASSERT_TRUE( + ParseTokenList(token_list_json, &token_list_map, mojom::CoinType::ETH)); + blockchain_registry->UpdateTokenList(std::move(token_list_map)); + + // Add responses for mainnet Ethereum, Optimism, then Polygon + // Mainnet Ethereum + GURL network_url = GetNetwork(mojom::kMainnetChainId, mojom::CoinType::ETH); + responses[network_url.spec()] = R"({ + "jsonrpc":"2.0", + "id":1, + "result":[ + { + "address":"0x0d8775f648430679a709e98d2b0cb6250d2887ef", + "blockHash":"0x2961ceb6c16bab72a55f79e394a35f2bf1c62b30446e3537280f7c22c3115e6e", + "blockNumber":"0xd6464d", + "data":"0x00000000000000000000000000000000000000000000000555aff1f0fae8c000", + "logIndex":"0x159", + "removed":false, + "topics":[ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000503828976d22510aad0201ac7ec88293211d23da", + "0x000000000000000000000000b4b2802129071b2b9ebb8cbb01ea1e4d14b34961" + ], + "transactionHash":"0x2e652b70966c6a05f4b3e68f20d6540b7a5ab712385464a7ccf62774d39b7066", + "transactionIndex":"0x9f" + } + ] + })"; + expected_from_blocks[network_url.spec()] = kEthereumBlockTagEarliest; + expected_next_asset_discovery_from_blocks.SetByDottedPath( + base::StrCat({kEthereumPrefKey, ".", mojom::kMainnetChainId}), + "0xd6464e"); + + // Optimism + network_url = + GetNetwork(mojom::kOptimismMainnetChainId, mojom::CoinType::ETH); + responses[network_url.spec()] = R"({ + "jsonrpc":"2.0", + "id":1, + "result":[ + { + "address":"0x4200000000000000000000000000000000000006", + "blockHash":"0x2961ceb6c16bab72a55f79e394a35f2bf1c62b30446e3537280f7c22c3115e6e", + "blockNumber":"0xd6464d", + "data":"0x00000000000000000000000000000000000000000000000555aff1f0fae8c000", + "logIndex":"0x159", + "removed":false, + "topics":[ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000503828976d22510aad0201ac7ec88293211d23da", + "0x000000000000000000000000b4b2802129071b2b9ebb8cbb01ea1e4d14b34961" + ], + "transactionHash":"0x2e652b70966c6a05f4b3e68f20d6540b7a5ab712385464a7ccf62774d39b7066", + "transactionIndex":"0x9f" + } + ] + })"; + expected_from_blocks[network_url.spec()] = kEthereumBlockTagEarliest; + expected_next_asset_discovery_from_blocks.SetByDottedPath( + base::StrCat({kEthereumPrefKey, ".", mojom::kOptimismMainnetChainId}), + "0xd6464e"); + + // Polygon + network_url = GetNetwork(mojom::kPolygonMainnetChainId, mojom::CoinType::ETH); + responses[network_url.spec()] = R"({ + "jsonrpc":"2.0", + "id":1, + "result":[ + { + "address":"0x0000000000000000000000000000000000001010", + "blockHash":"0x2961ceb6c16bab72a55f79e394a35f2bf1c62b30446e3537280f7c22c3115e6e", + "blockNumber":"0xd6464d", + "data":"0x00000000000000000000000000000000000000000000000555aff1f0fae8c000", + "logIndex":"0x159", + "removed":false, + "topics":[ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000503828976d22510aad0201ac7ec88293211d23da", + "0x000000000000000000000000b4b2802129071b2b9ebb8cbb01ea1e4d14b34961" + ], + "transactionHash":"0x2e652b70966c6a05f4b3e68f20d6540b7a5ab712385464a7ccf62774d39b7066", + "transactionIndex":"0x9f" + } + ] + })"; + expected_from_blocks[network_url.spec()] = kEthereumBlockTagEarliest; + expected_next_asset_discovery_from_blocks.SetByDottedPath( + base::StrCat({kEthereumPrefKey, ".", mojom::kPolygonMainnetChainId}), + "0xd6464e"); + SetDiscoverAssetsOnAllSupportedChainsInterceptor( + responses, expected_from_blocks, expected_to_blocks); + TestDiscoverAssetsOnAllSupportedChainsRefresh( + {"0xB4B2802129071b2B9eBb8cBB01EA1E4D14B34961"}, + base::BindLambdaForTesting([&](base::Time previous, base::Time current) { + EXPECT_TRUE(previous < current); + }), + expected_next_asset_discovery_from_blocks, + {"0x0000000000000000000000000000000000001010", + "0x4200000000000000000000000000000000000006"}); + + // Send valid request that for Ethereum mainnet (not rate limited) that yields + // new assets at blocks after the previous largest block and verify + // 1. from_block in eth_getLogs request is the + // kBraveWalletNextAssetDiscoveryFromBlocks set from the previous + // request Ethereum mainnet + // 2. kBraveWalletNextAssetDiscoveryFromBlocks is incremented for + // Ethereum mainnet + expected_next_asset_discovery_from_blocks.clear(); + responses.clear(); + expected_from_blocks.clear(); + task_environment_.FastForwardBy( + base::Minutes(kAssetDiscoveryMinutesPerRequest)); + GetPrefs()->GetTime(kBraveWalletLastDiscoveredAssetsAt); + + network_url = GetNetwork(mojom::kMainnetChainId, mojom::CoinType::ETH); + responses[network_url.spec()] = R"({ + "jsonrpc":"2.0", + "id":1, + "result":[ + { + "address":"0x6B175474E89094C44Da98b954EedeAC495271d0F", + "blockHash":"0x2961ceb6c16bab72a55f79e394a35f2bf1c62b30446e3537280f7c22c3115e6e", + "blockNumber":"0xd6464e", + "data":"0x00000000000000000000000000000000000000000000000555aff1f0fae8c000", + "logIndex":"0x159", + "removed":false, + "topics":[ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000503828976d22510aad0201ac7ec88293211d23da", + "0x000000000000000000000000b4b2802129071b2b9ebb8cbb01ea1e4d14b34961" + ], + "transactionHash":"0x2e652b70966c6a05f4b3e68f20d6540b7a5ab712385464a7ccf62774d39b7066", + "transactionIndex":"0x9f" + } + ] + })"; + expected_from_blocks[network_url.spec()] = "0xd6464e"; + expected_next_asset_discovery_from_blocks.SetByDottedPath( + base::StrCat({kEthereumPrefKey, ".", mojom::kMainnetChainId}), + "0xd6464f"); + expected_next_asset_discovery_from_blocks.SetByDottedPath( + base::StrCat({kEthereumPrefKey, ".", mojom::kOptimismMainnetChainId}), + "0xd6464e"); + expected_next_asset_discovery_from_blocks.SetByDottedPath( + base::StrCat({kEthereumPrefKey, ".", mojom::kPolygonMainnetChainId}), + "0xd6464e"); + + SetDiscoverAssetsOnAllSupportedChainsInterceptor( + responses, expected_from_blocks, expected_to_blocks); + TestDiscoverAssetsOnAllSupportedChainsRefresh( + {"0xB4B2802129071b2B9eBb8cBB01EA1E4D14B34961"}, + base::BindLambdaForTesting([&](base::Time previous, base::Time current) { + EXPECT_TRUE(previous < current); + }), + expected_next_asset_discovery_from_blocks, + {"0x6B175474E89094C44Da98b954EedeAC495271d0F"}); +} + +TEST_F(AssetDiscoveryManagerUnitTest, KeyringServiceObserver) { + // Verifies that the AssetDiscoveryManager is added as an observer to the + // KeyringService, and that discovery is run when new accounts are added + auto* blockchain_registry = BlockchainRegistry::GetInstance(); + TokenListMap token_list_map; + std::string token_list_json = R"({ + "0x6b175474e89094c44da98b954eedeac495271d0f":{ + "name":"Dai Stablecoin", + "logo":"dai.svg", + "erc20":true, + "symbol":"DAI", + "decimals":18, + "chainId":"0x1" + }, + "0x4b10701Bfd7BFEdc47d50562b76b436fbB5BdB3B":{ + "name":"Lil Nouns", + "logo":"lilnouns.svg", + "erc20":false, + "erc721":true, + "symbol":"LilNouns", + "chainId":"0x1" + }, + "0x6e84a6216eA6dACC71eE8E6b0a5B7322EEbC0fDd":{ + "name":"JoeToken", + "logo":"joe.svg", + "erc20":true, + "symbol":"JOE", + "decimals":18, + "chainId":"0xa86a" + }, + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48":{ + "name":"USD Coin", + "logo":"usdc.svg", + "erc20":true, + "symbol":"USDC", + "decimals":18, + "chainId":"0x1" + }, + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2":{ + "name":"Wrapped Eth", + "logo":"weth.svg", + "erc20":true, + "symbol":"WETH", + "decimals":18, + "chainId":"0x1" + }, + "0x03ab458634910aad20ef5f1c8ee96f1d6ac54919":{ + "name":"Rai Reflex Index", + "logo":"rai.svg", + "erc20":true, + "symbol":"RAI", + "decimals":18, + "chainId":"0x1" + } + })"; + ASSERT_TRUE( + ParseTokenList(token_list_json, &token_list_map, mojom::CoinType::ETH)); + blockchain_registry->UpdateTokenList(std::move(token_list_map)); + + // RestoreWallet + // Mock an eth_getLogs response that includes + // * DAI transfers to next account, + // 0xf81229FE54D8a20fBc1e1e2a3451D1c7489437Db (valid) + // * JOE transfers to next account (invalid, wrong network) + // * LilNouns transfers to next account (invalid, not an ERC20 token) + std::string response = R"({ + "jsonrpc":"2.0", + "id":1, + "result":[ + { + "address":"0x6b175474e89094c44da98b954eedeac495271d0f", + "blockHash":"0x2961ceb6c16bab72a55f79e394a35f2bf1c62b30446e3537280f7c22c3115e6e", + "blockNumber":"0xd6464c", + "data":"0x00000000000000000000000000000000000000000000000555aff1f0fae8c000", + "logIndex":"0x159", + "removed":false, + "topics":[ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000503828976d22510aad0201ac7ec88293211d23da", + "0x000000000000000000000000f81229FE54D8a20fBc1e1e2a3451D1c7489437Db" + ], + "transactionHash":"0x2e652b70966c6a05f4b3e68f20d6540b7a5ab712385464a7ccf62774d39b7066", + "transactionIndex":"0x9f" + }, + { + "address":"0x6e84a6216eA6dACC71eE8E6b0a5B7322EEbC0fDd", + "blockHash":"0x2961ceb6c16bab72a55f79e394a35f2bf1c62b30446e3537280f7c22c3115e6e", + "blockNumber":"0xd6464c", + "data":"0x00000000000000000000000000000000000000000000000555aff1f0fae8c000", + "logIndex":"0x159", + "removed":false, + "topics":[ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000503828976d22510aad0201ac7ec88293211d23da", + "0x000000000000000000000000f81229FE54D8a20fBc1e1e2a3451D1c7489437Db" + ], + "transactionHash":"0x2e652b70966c6a05f4b3e68f20d6540b7a5ab712385464a7ccf62774d39b7066", + "transactionIndex":"0x9f" + }, + { + "address":"0x4b10701Bfd7BFEdc47d50562b76b436fbB5BdB3B", + "blockHash":"0x2961ceb6c16bab72a55f79e394a35f2bf1c62b30446e3537280f7c22c3115e6e", + "blockNumber":"0xd6464c", + "data":"0x00000000000000000000000000000000000000000000000555aff1f0fae8c000", + "logIndex":"0x159", + "removed":false, + "topics":[ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000503828976d22510aad0201ac7ec88293211d23da", + "0x000000000000000000000000f81229FE54D8a20fBc1e1e2a3451D1c7489437Db" + ], + "transactionHash":"0x2e652b70966c6a05f4b3e68f20d6540b7a5ab712385464a7ccf62774d39b7066", + "transactionIndex":"0x9f" + } + ] + })"; + auto intended_network = + GetNetwork(mojom::kMainnetChainId, mojom::CoinType::ETH); + SetInterceptor(intended_network, response); + keyring_service_->RestoreWallet( + kMnemonic1, kPasswordBrave, false, + base::DoNothing()); // Creates account + // 0xf81229FE54D8a20fBc1e1e2a3451D1c7489437Db + base::RunLoop().RunUntilIdle(); + std::vector account_infos = + keyring_service_->GetAccountInfosForKeyring(mojom::kDefaultKeyringId); + EXPECT_EQ(account_infos.size(), 1u); + std::vector user_assets = + BraveWalletService::GetUserAssets(mojom::kMainnetChainId, + mojom::CoinType::ETH, GetPrefs()); + EXPECT_EQ(user_assets.size(), 3u); + EXPECT_EQ(user_assets[user_assets.size() - 1]->symbol, "DAI"); + + // AddAccount + // Mock an eth_getLogs response that includes logs for WETH transfers to the + // account to be added, 0x00c0f72E601C31DEb7890612cB92Ac0Fb7090EB0 + response = R"({ + "jsonrpc":"2.0", + "id":1, + "result":[ + { + "address":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "blockHash":"0x2961ceb6c16bab72a55f79e394a35f2bf1c62b30446e3537280f7c22c3115e6e", + "blockNumber":"0xd6464c", + "data":"0x00000000000000000000000000000000000000000000000555aff1f0fae8c000", + "logIndex":"0x159", + "removed":false, + "topics":[ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000503828976d22510aad0201ac7ec88293211d23da", + "0x00000000000000000000000000c0f72E601C31DEb7890612cB92Ac0Fb7090EB0" + ], + "transactionHash":"0x2e652b70966c6a05f4b3e68f20d6540b7a5ab712385464a7ccf62774d39b7066", + "transactionIndex":"0x9f" + } + ] + })"; + SetInterceptor(intended_network, response); + keyring_service_->AddAccount( + "Account", mojom::CoinType::ETH, + base::DoNothing()); // Creates account + // 0x00c0f72E601C31DEb7890612cB92Ac0Fb7090EB0 + base::RunLoop().RunUntilIdle(); + user_assets = BraveWalletService::GetUserAssets( + mojom::kMainnetChainId, mojom::CoinType::ETH, GetPrefs()); + EXPECT_EQ(user_assets.size(), 4u); + EXPECT_EQ(user_assets[user_assets.size() - 1]->symbol, "WETH"); + + // AddHardwareAccounts + // Mock an eth_getLogs response that includes logs for USDC transfers to the + // hardware account to be added, 0x595a0583621FDe81A935021707e81343f75F9324 + response = R"({ + "jsonrpc":"2.0", + "id":1, + "result":[ + { + "address":"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "blockHash":"0x2961ceb6c16bab72a55f79e394a35f2bf1c62b30446e3537280f7c22c3115e6e", + "blockNumber":"0xd6464c", + "data":"0x00000000000000000000000000000000000000000000000555aff1f0fae8c000", + "logIndex":"0x159", + "removed":false, + "topics":[ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000503828976d22510aad0201ac7ec88293211d23da", + "0x000000000000000000000000595a0583621FDe81A935021707e81343f75F9324" + ], + "transactionHash":"0x2e652b70966c6a05f4b3e68f20d6540b7a5ab712385464a7ccf62774d39b7066", + "transactionIndex":"0x9f" + } + ] + })"; + SetInterceptor(intended_network, response); + std::vector hardware_accounts; + hardware_accounts.push_back(mojom::HardwareWalletAccount::New( + "0x595a0583621FDe81A935021707e81343f75F9324", "m/44'/60'/1'/0/0", + "name 1", "Ledger", "device1", mojom::CoinType::ETH, absl::nullopt)); + keyring_service_->AddHardwareAccounts(std::move(hardware_accounts)); + base::RunLoop().RunUntilIdle(); + account_infos = + keyring_service_->GetAccountInfosForKeyring(mojom::kDefaultKeyringId); + user_assets = BraveWalletService::GetUserAssets( + mojom::kMainnetChainId, mojom::CoinType::ETH, GetPrefs()); + EXPECT_EQ(user_assets[user_assets.size() - 1]->symbol, "USDC"); + EXPECT_EQ(user_assets.size(), 5u); + + // ImportAccountForKeyring + // Mock an eth_getLogs response that includes logs for RAI transfers to the + // account to be imported, 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + response = R"({ + "jsonrpc":"2.0", + "id":1, + "result":[ + { + "address":"0x03ab458634910aad20ef5f1c8ee96f1d6ac54919", + "blockHash":"0x2961ceb6c16bab72a55f79e394a35f2bf1c62b30446e3537280f7c22c3115e6e", + "blockNumber":"0xd6464c", + "data":"0x00000000000000000000000000000000000000000000000555aff1f0fae8c000", + "logIndex":"0x159", + "removed":false, + "topics":[ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000503828976d22510aad0201ac7ec88293211d23da", + "0x000000000000000000000000f39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + ], + "transactionHash":"0x2e652b70966c6a05f4b3e68f20d6540b7a5ab712385464a7ccf62774d39b7066", + "transactionIndex":"0x9f" + } + ] + })"; + SetInterceptor(intended_network, response); + const std::string private_key_str = + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; + std::vector private_key_bytes; + ASSERT_TRUE(PrefixedHexStringToBytes(private_key_str, &private_key_bytes)); + ASSERT_TRUE(keyring_service_->ImportAccountForKeyring( + mojom::kDefaultKeyringId, "Imported Account", private_key_bytes)); + base::RunLoop().RunUntilIdle(); + user_assets = BraveWalletService::GetUserAssets( + mojom::kMainnetChainId, mojom::CoinType::ETH, GetPrefs()); + EXPECT_EQ(user_assets[user_assets.size() - 1]->symbol, "RAI"); + EXPECT_EQ(user_assets.size(), 6u); +} + +} // namespace brave_wallet diff --git a/browser/brave_wallet/brave_wallet_service_browsertest.cc b/browser/brave_wallet/brave_wallet_service_browsertest.cc index 95da6efe0ab..91ed609d9d4 100644 --- a/browser/brave_wallet/brave_wallet_service_browsertest.cc +++ b/browser/brave_wallet/brave_wallet_service_browsertest.cc @@ -53,6 +53,8 @@ class TestBraveWalletServiceObserver void OnActiveOriginChanged(mojom::OriginInfoPtr origin_info) override { active_origin_info_ = origin_info->Clone(); } + void OnDiscoverAssetsCompleted( + std::vector discovered_assets) override {} const mojom::OriginInfoPtr& active_origin_info() const { return active_origin_info_; diff --git a/browser/brave_wallet/brave_wallet_service_unittest.cc b/browser/brave_wallet/brave_wallet_service_unittest.cc index 306c4ad856b..78e1b34acbb 100644 --- a/browser/brave_wallet/brave_wallet_service_unittest.cc +++ b/browser/brave_wallet/brave_wallet_service_unittest.cc @@ -180,6 +180,9 @@ class TestBraveWalletServiceObserver void OnNetworkListChanged() override { network_list_changed_fired_ = true; } + void OnDiscoverAssetsCompleted( + std::vector discovered_assets) override {} + mojom::DefaultWallet GetDefaultEthereumWallet() { return default_ethereum_wallet_; } diff --git a/browser/brave_wallet/keyring_service_unittest.cc b/browser/brave_wallet/keyring_service_unittest.cc index 21a1f31b4d6..ce874276f0b 100644 --- a/browser/brave_wallet/keyring_service_unittest.cc +++ b/browser/brave_wallet/keyring_service_unittest.cc @@ -121,7 +121,10 @@ class TestKeyringServiceObserver } void AccountsChanged() override { accounts_changed_fired_count_++; } - + void AccountsAdded(mojom::CoinType coin, + const std::vector& addresses) override { + addresses_added_ = addresses; + } bool AutoLockMinutesChangedFired() { return auto_lock_minutes_changed_fired_; } @@ -142,6 +145,10 @@ class TestKeyringServiceObserver GetReceiver() { return observer_receiver_.BindNewPipeAndPassRemote(); } + void ExpectAddressesAddedEq( + const std::vector expected_addresses) { + EXPECT_EQ(expected_addresses, addresses_added_); + } void Reset() { auto_lock_minutes_changed_fired_ = false; @@ -150,9 +157,11 @@ class TestKeyringServiceObserver selected_account_change_fired_.clear(); keyring_created_.clear(); keyring_restored_.clear(); + addresses_added_.clear(); } private: + std::vector addresses_added_; bool auto_lock_minutes_changed_fired_ = false; int accounts_changed_fired_count_ = 0; bool keyring_reset_fired_ = false; @@ -4355,235 +4364,48 @@ TEST_F(KeyringServiceEncryptionKeysMigrationUnitTest, EXPECT_TRUE(ValidatePassword(&service, "brave")); } -TEST_F(KeyringServiceUnitTest, DiscoverAssets) { - // Verifies JsonRpcService::DiscoverAssets is run as expected in AddAccount +TEST_F(KeyringServiceUnitTest, AccountsAdded) { + // Verifies AccountsAdded event is emitted as expected in AddAccount // CreateWallet, RestoreWallet, AddHardwareAccounts, and // ImportAccountForKeyring KeyringService service(json_rpc_service(), GetPrefs()); - auto* blockchain_registry = BlockchainRegistry::GetInstance(); - TokenListMap token_list_map; - std::string token_list_json = R"({ - "0x6b175474e89094c44da98b954eedeac495271d0f":{ - "name":"Dai Stablecoin", - "logo":"dai.svg", - "erc20":true, - "symbol":"DAI", - "decimals":18, - "chainId":"0x1" - }, - "0x4b10701Bfd7BFEdc47d50562b76b436fbB5BdB3B":{ - "name":"Lil Nouns", - "logo":"lilnouns.svg", - "erc20":false, - "erc721":true, - "symbol":"LilNouns", - "chainId":"0x1" - }, - "0x6e84a6216eA6dACC71eE8E6b0a5B7322EEbC0fDd":{ - "name":"JoeToken", - "logo":"joe.svg", - "erc20":true, - "symbol":"JOE", - "decimals":18, - "chainId":"0xa86a" - }, - "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48":{ - "name":"USD Coin", - "logo":"usdc.svg", - "erc20":true, - "symbol":"USDC", - "decimals":18, - "chainId":"0x1" - }, - "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2":{ - "name":"Wrapped Eth", - "logo":"weth.svg", - "erc20":true, - "symbol":"WETH", - "decimals":18, - "chainId":"0x1" - }, - "0x03ab458634910aad20ef5f1c8ee96f1d6ac54919":{ - "name":"Rai Reflex Index", - "logo":"rai.svg", - "erc20":true, - "symbol":"RAI", - "decimals":18, - "chainId":"0x1" - } - })"; - ASSERT_TRUE( - ParseTokenList(token_list_json, &token_list_map, mojom::CoinType::ETH)); - blockchain_registry->UpdateTokenList(std::move(token_list_map)); + TestKeyringServiceObserver observer; + service.AddObserver(observer.GetReceiver()); // RestoreWallet - // Mock an eth_getLogs response that includes - // * DAI transfers to next account, - // 0xf81229FE54D8a20fBc1e1e2a3451D1c7489437Db (valid) - // * JOE transfers to next account (invalid, wrong network) - // * LilNouns transfers to next account (invalid, not an ERC20 token) - std::string response = R"({ - "jsonrpc":"2.0", - "id":1, - "result":[ - { - "address":"0x6b175474e89094c44da98b954eedeac495271d0f", - "blockHash":"0x2961ceb6c16bab72a55f79e394a35f2bf1c62b30446e3537280f7c22c3115e6e", - "blockNumber":"0xd6464c", - "data":"0x00000000000000000000000000000000000000000000000555aff1f0fae8c000", - "logIndex":"0x159", - "removed":false, - "topics":[ - "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", - "0x000000000000000000000000503828976d22510aad0201ac7ec88293211d23da", - "0x000000000000000000000000f81229FE54D8a20fBc1e1e2a3451D1c7489437Db" - ], - "transactionHash":"0x2e652b70966c6a05f4b3e68f20d6540b7a5ab712385464a7ccf62774d39b7066", - "transactionIndex":"0x9f" - }, - { - "address":"0x6e84a6216eA6dACC71eE8E6b0a5B7322EEbC0fDd", - "blockHash":"0x2961ceb6c16bab72a55f79e394a35f2bf1c62b30446e3537280f7c22c3115e6e", - "blockNumber":"0xd6464c", - "data":"0x00000000000000000000000000000000000000000000000555aff1f0fae8c000", - "logIndex":"0x159", - "removed":false, - "topics":[ - "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", - "0x000000000000000000000000503828976d22510aad0201ac7ec88293211d23da", - "0x000000000000000000000000f81229FE54D8a20fBc1e1e2a3451D1c7489437Db" - ], - "transactionHash":"0x2e652b70966c6a05f4b3e68f20d6540b7a5ab712385464a7ccf62774d39b7066", - "transactionIndex":"0x9f" - }, - { - "address":"0x4b10701Bfd7BFEdc47d50562b76b436fbB5BdB3B", - "blockHash":"0x2961ceb6c16bab72a55f79e394a35f2bf1c62b30446e3537280f7c22c3115e6e", - "blockNumber":"0xd6464c", - "data":"0x00000000000000000000000000000000000000000000000555aff1f0fae8c000", - "logIndex":"0x159", - "removed":false, - "topics":[ - "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", - "0x000000000000000000000000503828976d22510aad0201ac7ec88293211d23da", - "0x000000000000000000000000f81229FE54D8a20fBc1e1e2a3451D1c7489437Db" - ], - "transactionHash":"0x2e652b70966c6a05f4b3e68f20d6540b7a5ab712385464a7ccf62774d39b7066", - "transactionIndex":"0x9f" - } - ] - })"; - SetInterceptor(response); RestoreWallet( &service, kMnemonic1, kPasswordBrave, false); // Creates account 0xf81229FE54D8a20fBc1e1e2a3451D1c7489437Db base::RunLoop().RunUntilIdle(); - std::vector account_infos = - service.GetAccountInfosForKeyring(mojom::kDefaultKeyringId); - EXPECT_EQ(account_infos.size(), 1u); - std::vector user_assets = - BraveWalletService::GetUserAssets(mojom::kMainnetChainId, - mojom::CoinType::ETH, GetPrefs()); - EXPECT_EQ(user_assets.size(), 3u); - EXPECT_EQ(user_assets[user_assets.size() - 1]->symbol, "DAI"); + observer.ExpectAddressesAddedEq( + {"0xf81229FE54D8a20fBc1e1e2a3451D1c7489437Db"}); + task_environment_.FastForwardBy( + base::Minutes(kAssetDiscoveryMinutesPerRequest)); // AddAccount - // Mock an eth_getLogs response that includes logs for WETH transfers to the - // account to be added, 0x00c0f72E601C31DEb7890612cB92Ac0Fb7090EB0 - response = R"({ - "jsonrpc":"2.0", - "id":1, - "result":[ - { - "address":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", - "blockHash":"0x2961ceb6c16bab72a55f79e394a35f2bf1c62b30446e3537280f7c22c3115e6e", - "blockNumber":"0xd6464c", - "data":"0x00000000000000000000000000000000000000000000000555aff1f0fae8c000", - "logIndex":"0x159", - "removed":false, - "topics":[ - "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", - "0x000000000000000000000000503828976d22510aad0201ac7ec88293211d23da", - "0x00000000000000000000000000c0f72E601C31DEb7890612cB92Ac0Fb7090EB0" - ], - "transactionHash":"0x2e652b70966c6a05f4b3e68f20d6540b7a5ab712385464a7ccf62774d39b7066", - "transactionIndex":"0x9f" - } - ] - })"; - SetInterceptor(response); - EXPECT_TRUE(AddAccount( + ASSERT_TRUE(AddAccount( &service, "Account", mojom::CoinType::ETH)); // Creates account // 0x00c0f72E601C31DEb7890612cB92Ac0Fb7090EB0 base::RunLoop().RunUntilIdle(); - user_assets = BraveWalletService::GetUserAssets( - mojom::kMainnetChainId, mojom::CoinType::ETH, GetPrefs()); - EXPECT_EQ(user_assets.size(), 4u); - EXPECT_EQ(user_assets[user_assets.size() - 1]->symbol, "WETH"); + observer.ExpectAddressesAddedEq( + {"0x00c0f72E601C31DEb7890612cB92Ac0Fb7090EB0"}); + task_environment_.FastForwardBy( + base::Minutes(kAssetDiscoveryMinutesPerRequest)); // AddHardwareAccounts - // Mock an eth_getLogs response that includes logs for USDC transfers to the - // hardware account to be added, 0x595a0583621FDe81A935021707e81343f75F9324 - response = R"({ - "jsonrpc":"2.0", - "id":1, - "result":[ - { - "address":"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", - "blockHash":"0x2961ceb6c16bab72a55f79e394a35f2bf1c62b30446e3537280f7c22c3115e6e", - "blockNumber":"0xd6464c", - "data":"0x00000000000000000000000000000000000000000000000555aff1f0fae8c000", - "logIndex":"0x159", - "removed":false, - "topics":[ - "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", - "0x000000000000000000000000503828976d22510aad0201ac7ec88293211d23da", - "0x000000000000000000000000595a0583621FDe81A935021707e81343f75F9324" - ], - "transactionHash":"0x2e652b70966c6a05f4b3e68f20d6540b7a5ab712385464a7ccf62774d39b7066", - "transactionIndex":"0x9f" - } - ] - })"; - SetInterceptor(response); std::vector hardware_accounts; hardware_accounts.push_back(mojom::HardwareWalletAccount::New( "0x595a0583621FDe81A935021707e81343f75F9324", "m/44'/60'/1'/0/0", "name 1", "Ledger", "device1", mojom::CoinType::ETH, absl::nullopt)); service.AddHardwareAccounts(std::move(hardware_accounts)); base::RunLoop().RunUntilIdle(); - account_infos = service.GetAccountInfosForKeyring(mojom::kDefaultKeyringId); - user_assets = BraveWalletService::GetUserAssets( - mojom::kMainnetChainId, mojom::CoinType::ETH, GetPrefs()); - EXPECT_EQ(user_assets[user_assets.size() - 1]->symbol, "USDC"); - EXPECT_EQ(user_assets.size(), 5u); + observer.ExpectAddressesAddedEq( + {"0x595a0583621FDe81A935021707e81343f75F9324"}); + task_environment_.FastForwardBy( + base::Minutes(kAssetDiscoveryMinutesPerRequest)); // ImportAccountForKeyring - // Mock an eth_getLogs response that includes logs for RAI transfers to the - // account to be imported, 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 - response = R"({ - "jsonrpc":"2.0", - "id":1, - "result":[ - { - "address":"0x03ab458634910aad20ef5f1c8ee96f1d6ac54919", - "blockHash":"0x2961ceb6c16bab72a55f79e394a35f2bf1c62b30446e3537280f7c22c3115e6e", - "blockNumber":"0xd6464c", - "data":"0x00000000000000000000000000000000000000000000000555aff1f0fae8c000", - "logIndex":"0x159", - "removed":false, - "topics":[ - "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", - "0x000000000000000000000000503828976d22510aad0201ac7ec88293211d23da", - "0x000000000000000000000000f39Fd6e51aad88F6F4ce6aB8827279cffFb92266" - ], - "transactionHash":"0x2e652b70966c6a05f4b3e68f20d6540b7a5ab712385464a7ccf62774d39b7066", - "transactionIndex":"0x9f" - } - ] - })"; - SetInterceptor(response); const std::string private_key_str = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; std::vector private_key_bytes; @@ -4591,10 +4413,8 @@ TEST_F(KeyringServiceUnitTest, DiscoverAssets) { ASSERT_TRUE(service.ImportAccountForKeyring( mojom::kDefaultKeyringId, "Imported Account", private_key_bytes)); base::RunLoop().RunUntilIdle(); - user_assets = BraveWalletService::GetUserAssets( - mojom::kMainnetChainId, mojom::CoinType::ETH, GetPrefs()); - EXPECT_EQ(user_assets[user_assets.size() - 1]->symbol, "RAI"); - EXPECT_EQ(user_assets.size(), 6u); + observer.ExpectAddressesAddedEq( + {"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"}); } TEST_F(KeyringServiceUnitTest, DevWalletPassword) { diff --git a/components/brave_wallet/browser/BUILD.gn b/components/brave_wallet/browser/BUILD.gn index 94d1e6468ee..43442db2c3b 100644 --- a/components/brave_wallet/browser/BUILD.gn +++ b/components/brave_wallet/browser/BUILD.gn @@ -15,6 +15,8 @@ if (is_official_build) { static_library("browser") { sources = [ + "asset_discovery_manager.cc", + "asset_discovery_manager.h", "asset_ratio_response_parser.cc", "asset_ratio_response_parser.h", "asset_ratio_service.cc", diff --git a/components/brave_wallet/browser/asset_discovery_manager.cc b/components/brave_wallet/browser/asset_discovery_manager.cc new file mode 100644 index 00000000000..e78da968788 --- /dev/null +++ b/components/brave_wallet/browser/asset_discovery_manager.cc @@ -0,0 +1,348 @@ +/* Copyright 2022 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "brave/components/brave_wallet/browser/asset_discovery_manager.h" + +#include + +#include "base/strings/strcat.h" +#include "brave/components/brave_wallet/browser/blockchain_registry.h" +#include "brave/components/brave_wallet/browser/brave_wallet_service.h" +#include "brave/components/brave_wallet/browser/brave_wallet_utils.h" +#include "brave/components/brave_wallet/browser/eth_topics_builder.h" +#include "brave/components/brave_wallet/browser/json_rpc_service.h" +#include "brave/components/brave_wallet/browser/keyring_service.h" +#include "brave/components/brave_wallet/browser/pref_names.h" +#include "brave/components/brave_wallet/common/eth_address.h" +#include "brave/components/brave_wallet/common/hex_utils.h" +#include "components/prefs/pref_service.h" +#include "components/prefs/scoped_user_pref_update.h" +#include "ui/base/l10n/l10n_util.h" + +namespace brave_wallet { + +AssetDiscoveryManager::AssetDiscoveryManager(BraveWalletService* wallet_service, + JsonRpcService* json_rpc_service, + KeyringService* keyring_service, + PrefService* prefs) + : wallet_service_(wallet_service), + json_rpc_service_(json_rpc_service), + keyring_service_(keyring_service), + prefs_(prefs), + weak_ptr_factory_(this) { + keyring_service_->AddObserver( + keyring_service_observer_receiver_.BindNewPipeAndPassRemote()); +} + +AssetDiscoveryManager::~AssetDiscoveryManager() = default; + +const std::vector& +AssetDiscoveryManager::GetAssetDiscoverySupportedChains() { + if (supported_chains_for_testing_.size() > 0) { + return supported_chains_for_testing_; + } + static base::NoDestructor> + asset_discovery_supported_chains({mojom::kMainnetChainId}); + return *asset_discovery_supported_chains; +} + +void AssetDiscoveryManager::DiscoverAssets( + const std::string& chain_id, + mojom::CoinType coin, + const std::vector& account_addresses, + bool triggered_by_accounts_added, + const std::string& from_block, + const std::string& to_block) { + // Asset discovery only supported on select EVM chains + if (coin != mojom::CoinType::ETH || + !base::Contains(GetAssetDiscoverySupportedChains(), chain_id)) { + CompleteDiscoverAssets( + chain_id, std::vector(), + mojom::ProviderError::kMethodNotSupported, + l10n_util::GetStringUTF8(IDS_WALLET_METHOD_NOT_SUPPORTED_ERROR), + triggered_by_accounts_added); + return; + } + + // Asset discovery only supported when using Infura proxy + GURL infura_url = GetInfuraURLForKnownChainId(chain_id); + GURL active_url = GetNetworkURL(prefs_, chain_id, coin); + if (infura_url.host() != active_url.host()) { + CompleteDiscoverAssets( + chain_id, std::vector(), + mojom::ProviderError::kMethodNotSupported, + l10n_util::GetStringUTF8(IDS_WALLET_METHOD_NOT_SUPPORTED_ERROR), + triggered_by_accounts_added); + return; + } + + if (account_addresses.empty()) { + CompleteDiscoverAssets( + chain_id, std::vector(), + mojom::ProviderError::kInvalidParams, + l10n_util::GetStringUTF8(IDS_WALLET_INVALID_PARAMETERS), + triggered_by_accounts_added); + return; + } + + for (const auto& account_address : account_addresses) { + if (!EthAddress::IsValidAddress(account_address)) { + CompleteDiscoverAssets( + chain_id, std::vector(), + mojom::ProviderError::kInvalidParams, + l10n_util::GetStringUTF8(IDS_WALLET_INVALID_PARAMETERS), + triggered_by_accounts_added); + return; + } + } + + std::vector user_assets = + BraveWalletService::GetUserAssets(chain_id, mojom::CoinType::ETH, prefs_); + auto internal_callback = + base::BindOnce(&AssetDiscoveryManager::OnGetAllTokensDiscoverAssets, + weak_ptr_factory_.GetWeakPtr(), chain_id, + account_addresses, std::move(user_assets), + triggered_by_accounts_added, from_block, to_block); + + BlockchainRegistry::GetInstance()->GetAllTokens( + chain_id, mojom::CoinType::ETH, std::move(internal_callback)); +} + +void AssetDiscoveryManager::OnGetAllTokensDiscoverAssets( + const std::string& chain_id, + const std::vector& account_addresses, + std::vector user_assets, + bool triggered_by_accounts_added, + const std::string& from_block, + const std::string& to_block, + std::vector token_registry) { + auto network_url = GetNetworkURL(prefs_, chain_id, mojom::CoinType::ETH); + if (!network_url.is_valid()) { + CompleteDiscoverAssets( + chain_id, std::vector(), + mojom::ProviderError::kInvalidParams, + l10n_util::GetStringUTF8(IDS_WALLET_INVALID_PARAMETERS), + triggered_by_accounts_added); + return; + } + + base::Value::List topics; + if (!MakeAssetDiscoveryTopics(account_addresses, &topics)) { + CompleteDiscoverAssets( + chain_id, std::vector(), + mojom::ProviderError::kInvalidParams, + l10n_util::GetStringUTF8(IDS_WALLET_INVALID_PARAMETERS), + triggered_by_accounts_added); + return; + } + + // Create set of contract addresses the user already has for easy lookups + base::flat_set user_asset_contract_addresses; + for (const auto& user_asset : user_assets) { + user_asset_contract_addresses.insert(user_asset->contract_address); + } + + // Create a list of contract addresses to search by removing + // all erc20s and assets the user has already added. + base::Value::List contract_addresses_to_search; + // Also create a map for addresses to blockchain tokens for easy lookup + // for blockchain tokens in OnGetTransferLogs + base::flat_map tokens_to_search; + for (auto& registry_token : token_registry) { + if (registry_token->is_erc20 && !registry_token->contract_address.empty() && + !user_asset_contract_addresses.contains( + registry_token->contract_address)) { + // Use lowercase representation of hex address for comparisons + // because providers may return all lowercase addresses. + const std::string lower_case_contract_address = + base::ToLowerASCII(registry_token->contract_address); + contract_addresses_to_search.Append(lower_case_contract_address); + tokens_to_search[lower_case_contract_address] = std::move(registry_token); + } + } + + if (contract_addresses_to_search.size() == 0) { + CompleteDiscoverAssets(chain_id, std::vector(), + mojom::ProviderError::kSuccess, "", + triggered_by_accounts_added); + return; + } + + auto callback = base::BindOnce(&AssetDiscoveryManager::OnGetTransferLogs, + weak_ptr_factory_.GetWeakPtr(), + base::OwnedRef(std::move(tokens_to_search)), + triggered_by_accounts_added, chain_id); + + json_rpc_service_->EthGetLogs(chain_id, from_block, to_block, + std::move(contract_addresses_to_search), + std::move(topics), std::move(callback)); +} + +void AssetDiscoveryManager::OnGetTransferLogs( + base::flat_map& tokens_to_search, + bool triggered_by_accounts_added, + const std::string& chain_id, + const std::vector& logs, + mojom::ProviderError error, + const std::string& error_message) { + if (error != mojom::ProviderError::kSuccess) { + CompleteDiscoverAssets(chain_id, std::vector(), + std::move(error), error_message, + triggered_by_accounts_added); + return; + } + + // Create unique list of addresses that matched eth_getLogs query + // and keep track of largest block discovered + base::flat_set matching_contract_addresses; + uint256_t largest_block = 0; + for (const auto& log : logs) { + matching_contract_addresses.insert(base::ToLowerASCII(log.address)); + if (log.block_number > largest_block) { + largest_block = log.block_number; + } + } + std::vector discovered_assets; + + for (const auto& contract_address : matching_contract_addresses) { + if (!tokens_to_search.contains(contract_address)) { + continue; + } + mojom::BlockchainTokenPtr token = + std::move(tokens_to_search.at(contract_address)); + + if (!BraveWalletService::AddUserAsset(token.Clone(), prefs_)) { + continue; + } + discovered_assets.push_back(std::move(token)); + } + + if (!triggered_by_accounts_added) { + DictionaryPrefUpdate update(prefs_, + kBraveWalletNextAssetDiscoveryFromBlocks); + auto* next_asset_discovery_from_blocks = update.Get()->GetIfDict(); + DCHECK(next_asset_discovery_from_blocks); + const auto path = base::StrCat({kEthereumPrefKey, ".", chain_id}); + const std::string* current = + next_asset_discovery_from_blocks->FindStringByDottedPath(path); + uint256_t current_int = 0; + if (current) { + if (!HexValueToUint256(*current, ¤t_int)) { + CompleteDiscoverAssets( + chain_id, std::vector(), + mojom::ProviderError::kInternalError, + l10n_util::GetStringUTF8(IDS_WALLET_INTERNAL_ERROR), + triggered_by_accounts_added); + return; + } + } + if ((!current || current_int <= largest_block) && largest_block > 0) { + next_asset_discovery_from_blocks->SetByDottedPath( + path, Uint256ValueToHex(largest_block + 1)); + } + } + CompleteDiscoverAssets(chain_id, std::move(discovered_assets), + mojom::ProviderError::kSuccess, "", + triggered_by_accounts_added); +} + +void AssetDiscoveryManager::CompleteDiscoverAssets( + const std::string& chain_id, + std::vector discovered_assets_for_chain, + mojom::ProviderError error, + const std::string& error_message, + bool triggered_by_accounts_added) { + if (discover_assets_completed_callback_for_testing_) { + std::vector discovered_assets_for_chain_clone; + for (const auto& asset : discovered_assets_for_chain) { + discovered_assets_for_chain_clone.push_back(asset.Clone()); + } + discover_assets_completed_callback_for_testing_.Run( + chain_id, std::move(discovered_assets_for_chain_clone), + std::move(error), error_message); + } + + // Do not emit event or modify remaining_chains_ count if DiscoverAssets call + // was triggered by an AccountsAdded event + if (triggered_by_accounts_added) { + return; + } + + // Complete the call by decrementing remaining_chains_, storing the discovered + // assets for later, and emitting the event if this was the final chain to + // finish + remaining_chains_--; + for (auto& asset : discovered_assets_for_chain) { + discovered_assets_.push_back(std::move(asset)); + } + + if (remaining_chains_ == 0) { + wallet_service_->OnDiscoverAssetsCompleted(std::move(discovered_assets_)); + discovered_assets_.clear(); + } +} + +void AssetDiscoveryManager::DiscoverAssetsOnAllSupportedChainsAccountsAdded( + const std::vector& account_addresses) { + for (const auto& chain_id : GetAssetDiscoverySupportedChains()) { + DiscoverAssets(chain_id, mojom::CoinType::ETH, account_addresses, true, + kEthereumBlockTagEarliest, kEthereumBlockTagLatest); + } +} + +void AssetDiscoveryManager::DiscoverAssetsOnAllSupportedChainsRefresh( + const std::vector& account_addresses) { + // Simple client side rate limiting (only applies to refreshes) + const base::Time assets_last_discovered_at = + prefs_->GetTime(kBraveWalletLastDiscoveredAssetsAt); + if (!assets_last_discovered_at.is_null() && + ((base::Time::Now() - base::Minutes(kAssetDiscoveryMinutesPerRequest)) < + assets_last_discovered_at)) { + wallet_service_->OnDiscoverAssetsCompleted({}); + return; + } + prefs_->SetTime(kBraveWalletLastDiscoveredAssetsAt, base::Time::Now()); + + // Return early and do not send a notification + // if a discover assets process is flight already + if (remaining_chains_ != 0) { + return; + } + + const std::vector& supported_chain_ids = + GetAssetDiscoverySupportedChains(); + remaining_chains_ = supported_chain_ids.size(); + + // Fetch block numbers for which asset discovery has been run through + auto& next_asset_discovery_from_blocks = + prefs_->GetDict(kBraveWalletNextAssetDiscoveryFromBlocks); + for (const auto& chain_id : supported_chain_ids) { + // Call DiscoverAssets for the supported chain ID + // using the kBraveWalletNextAssetDiscoveryFromBlocks pref + // as the from_block of the eth_getLogs query. + std::string from_block = kEthereumBlockTagEarliest; + std::string to_block = kEthereumBlockTagLatest; + const auto path = base::StrCat({kEthereumPrefKey, ".", chain_id}); + const std::string* next_asset_discovery_from_block = + next_asset_discovery_from_blocks.FindStringByDottedPath(path); + if (next_asset_discovery_from_block) { + from_block = *next_asset_discovery_from_block; + } + + DiscoverAssets(chain_id, mojom::CoinType::ETH, account_addresses, false, + from_block, to_block); + } +} + +void AssetDiscoveryManager::AccountsAdded( + mojom::CoinType coin, + const std::vector& addresses) { + if (coin != mojom::CoinType::ETH || addresses.size() == 0u) { + return; + } + DiscoverAssetsOnAllSupportedChainsAccountsAdded(addresses); +} + +} // namespace brave_wallet diff --git a/components/brave_wallet/browser/asset_discovery_manager.h b/components/brave_wallet/browser/asset_discovery_manager.h new file mode 100644 index 00000000000..86a46416bab --- /dev/null +++ b/components/brave_wallet/browser/asset_discovery_manager.h @@ -0,0 +1,145 @@ +/* Copyright 2022 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ASSET_DISCOVERY_MANAGER_H_ +#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ASSET_DISCOVERY_MANAGER_H_ + +#include +#include +#include + +#include "base/memory/raw_ptr.h" +#include "base/memory/weak_ptr.h" +#include "brave/components/api_request_helper/api_request_helper.h" +#include "brave/components/brave_wallet/common/brave_wallet.mojom.h" +#include "brave/components/brave_wallet/common/brave_wallet_types.h" +#include "mojo/public/cpp/bindings/receiver.h" + +class PrefService; + +namespace brave_wallet { + +class BraveWalletService; +class JsonRpcService; +class KeyringService; + +class AssetDiscoveryManager : public mojom::KeyringServiceObserver { + public: + AssetDiscoveryManager(BraveWalletService* wallet_service, + JsonRpcService* json_rpc_service, + KeyringService* keyring_service, + PrefService* prefs); + + AssetDiscoveryManager(const AssetDiscoveryManager&) = delete; + AssetDiscoveryManager& operator=(AssetDiscoveryManager&) = delete; + ~AssetDiscoveryManager() override; + + // KeyringServiceObserver + void KeyringCreated(const std::string& keyring_id) override {} + void KeyringRestored(const std::string& keyring_id) override {} + void KeyringReset() override {} + void Locked() override {} + void Unlocked() override {} + void BackedUp() override {} + void AccountsChanged() override {} + void AccountsAdded(mojom::CoinType coin, + const std::vector& addresses) override; + void AutoLockMinutesChanged() override {} + void SelectedAccountChanged(mojom::CoinType coin) override {} + + using APIRequestResult = api_request_helper::APIRequestResult; + using EthGetLogsCallback = + base::OnceCallback& logs, + mojom::ProviderError error, + const std::string& error_message)>; + using DiscoverAssetsCompletedCallbackForTesting = + base::RepeatingCallback discovered_assets_for_chain, + mojom::ProviderError error, + const std::string& error_message)>; + // Called by frontend via BraveWalletService. + // Subject to client side rate limiting based on + // kBraveWalletLastDiscoveredAssetsAt pref value. Only runs eth_getLogs + // against block range between + // kBraveWalletNextAssetDiscoveryFromBlocks pref and "latest". + void DiscoverAssetsOnAllSupportedChainsRefresh( + const std::vector& account_addresses); + + void SetSupportedChainsForTesting( + const std::vector supported_chains_for_testing) { + supported_chains_for_testing_ = supported_chains_for_testing; + } + + void SetDiscoverAssetsCompletedCallbackForTesting( + DiscoverAssetsCompletedCallbackForTesting callback) { + discover_assets_completed_callback_for_testing_ = std::move(callback); + } + + private: + friend class AssetDiscoveryManagerUnitTest; + FRIEND_TEST_ALL_PREFIXES(AssetDiscoveryManagerUnitTest, DiscoverAssets); + FRIEND_TEST_ALL_PREFIXES(AssetDiscoveryManagerUnitTest, + DiscoverAssetsOnAllSupportedChainsRefresh); + + const std::vector& GetAssetDiscoverySupportedChains(); + + void DiscoverAssets(const std::string& chain_id, + mojom::CoinType coin, + const std::vector& account_addresses, + bool update_prefs, + const std::string& from_block, + const std::string& to_block); + + void OnGetAllTokensDiscoverAssets( + const std::string& chain_id, + const std::vector& account_addresses, + std::vector user_assets, + bool update_prefs, + const std::string& from_block, + const std::string& to_block, + std::vector token_list); + + void OnGetTransferLogs( + base::flat_map& tokens_to_search, + bool triggered_by_accounts_added, + const std::string& chain_id, + const std::vector& logs, + mojom::ProviderError error, + const std::string& error_message); + + void CompleteDiscoverAssets( + const std::string& chain_id, + std::vector discovered_assets, + mojom::ProviderError error, + const std::string& error_message, + bool triggered_by_accounts_added); + + // Triggered by when KeyringService emits AccountsAdded event. + // Rate limits will be ignored, and eth_getLogs query + // will run against all blocks, "earliest" to "latest". + void DiscoverAssetsOnAllSupportedChainsAccountsAdded( + const std::vector& account_addresses); + + // The number of supported chain_ids to search for assets for the current + // DiscoverAssetsOnAllSupportedChainsRefresh request. Not used for + // DiscoverAssetsOnAllSupportedChainsAccountsAdded requests. + int remaining_chains_ = 0; + std::vector discovered_assets_; + std::vector supported_chains_for_testing_; + DiscoverAssetsCompletedCallbackForTesting + discover_assets_completed_callback_for_testing_; + raw_ptr wallet_service_; + raw_ptr json_rpc_service_; + raw_ptr keyring_service_; + raw_ptr prefs_; + mojo::Receiver + keyring_service_observer_receiver_{this}; + base::WeakPtrFactory weak_ptr_factory_; +}; + +} // namespace brave_wallet + +#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ASSET_DISCOVERY_MANAGER_H_ diff --git a/components/brave_wallet/browser/brave_wallet_constants.h b/components/brave_wallet/browser/brave_wallet_constants.h index e21a2960b63..f1f02e31719 100644 --- a/components/brave_wallet/browser/brave_wallet_constants.h +++ b/components/brave_wallet/browser/brave_wallet_constants.h @@ -27,6 +27,8 @@ constexpr uint256_t kDefaultERC20ApproveGasLimit = 300000; constexpr int32_t kAutoLockMinutesMin = 1; constexpr int32_t kAutoLockMinutesMax = 10080; +constexpr int32_t kAssetDiscoveryMinutesPerRequest = 1; + constexpr char kWalletBaseDirectory[] = "BraveWallet"; constexpr char kImageSourceHost[] = "erc-token-images"; constexpr char kWyreID[] = "AC_MGNVBGHPA9T"; @@ -1022,6 +1024,9 @@ constexpr char kEthereumPrefKey[] = "ethereum"; constexpr char kFilecoinPrefKey[] = "filecoin"; constexpr char kSolanaPrefKey[] = "solana"; +constexpr char kEthereumBlockTagEarliest[] = "earliest"; +constexpr char kEthereumBlockTagLatest[] = "latest"; + const std::vector& GetWyreBuyTokens(); const std::vector& GetRampBuyTokens(); const std::vector& GetOnRampCurrenciesList(); diff --git a/components/brave_wallet/browser/brave_wallet_p3a.h b/components/brave_wallet/browser/brave_wallet_p3a.h index ce45586b61a..0c62a338345 100644 --- a/components/brave_wallet/browser/brave_wallet_p3a.h +++ b/components/brave_wallet/browser/brave_wallet_p3a.h @@ -7,6 +7,7 @@ #define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_BRAVE_WALLET_P3A_H_ #include +#include #include "base/memory/raw_ptr.h" #include "brave/components/brave_wallet/common/brave_wallet.mojom.h" @@ -67,6 +68,8 @@ class BraveWalletP3A : public mojom::BraveWalletServiceObserver, void Unlocked() override {} void BackedUp() override {} void AccountsChanged() override {} + void AccountsAdded(mojom::CoinType coin, + const std::vector& addresses) override {} void AutoLockMinutesChanged() override {} void SelectedAccountChanged(mojom::CoinType coin) override {} @@ -80,6 +83,8 @@ class BraveWalletP3A : public mojom::BraveWalletServiceObserver, void OnDefaultBaseCryptocurrencyChanged( const std::string& cryptocurrency) override {} void OnNetworkListChanged() override {} + void OnDiscoverAssetsCompleted( + std::vector discovered_assets) override {} private: void RecordInitialBraveWalletP3AState(); diff --git a/components/brave_wallet/browser/brave_wallet_prefs.cc b/components/brave_wallet/browser/brave_wallet_prefs.cc index ecfc5e3dde5..8d489d0d7be 100644 --- a/components/brave_wallet/browser/brave_wallet_prefs.cc +++ b/components/brave_wallet/browser/brave_wallet_prefs.cc @@ -86,6 +86,8 @@ void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) { kBraveWalletP3ALastUnlockTime, kBraveWalletP3AUsedSecondDay, nullptr); registry->RegisterDictionaryPref(kBraveWalletLastTransactionSentTimeDict); + registry->RegisterDictionaryPref(kBraveWalletNextAssetDiscoveryFromBlocks); + registry->RegisterTimePref(kBraveWalletLastDiscoveredAssetsAt, base::Time()); } void RegisterProfilePrefsForMigration( diff --git a/components/brave_wallet/browser/brave_wallet_service.cc b/components/brave_wallet/browser/brave_wallet_service.cc index 75e13a43443..65b1312ab39 100644 --- a/components/brave_wallet/browser/brave_wallet_service.cc +++ b/components/brave_wallet/browser/brave_wallet_service.cc @@ -168,6 +168,7 @@ BraveWalletService::BraveWalletService( tx_service_(tx_service), prefs_(prefs), brave_wallet_p3a_(this, keyring_service, prefs), + asset_discovery_manager_(this, json_rpc_service, keyring_service, prefs), weak_ptr_factory_(this) { if (delegate_) delegate_->AddObserver(this); @@ -1115,6 +1116,17 @@ void BraveWalletService::OnActiveOriginChanged( } } +void BraveWalletService::OnDiscoverAssetsCompleted( + std::vector discovered_assets) { + for (const auto& observer : observers_) { + std::vector discovered_assets_copy; + for (auto& asset : discovered_assets) { + discovered_assets_copy.push_back(asset.Clone()); + } + observer->OnDiscoverAssetsCompleted(std::move(discovered_assets_copy)); + } +} + void BraveWalletService::OnGetImportInfo( const std::string& new_password, base::OnceCallback&)> callback, @@ -1441,6 +1453,19 @@ void BraveWalletService::Base58Encode( std::move(callback).Run(std::move(encoded_addresses)); } +void BraveWalletService::DiscoverAssetsOnAllSupportedChains() { + mojom::KeyringInfoPtr keyring_info = keyring_service_->GetKeyringInfoSync( + brave_wallet::mojom::kDefaultKeyringId); + + std::vector account_addresses; + for (auto& account_info : keyring_info->account_infos) { + account_addresses.push_back(account_info->address); + } + + asset_discovery_manager_.DiscoverAssetsOnAllSupportedChainsRefresh( + account_addresses); +} + void BraveWalletService::CancelAllSuggestedTokenCallbacks() { add_suggest_token_requests_.clear(); // Reject pending suggest token requests when network changed. diff --git a/components/brave_wallet/browser/brave_wallet_service.h b/components/brave_wallet/browser/brave_wallet_service.h index 7d2e0cbac77..6194a55ecc3 100644 --- a/components/brave_wallet/browser/brave_wallet_service.h +++ b/components/brave_wallet/browser/brave_wallet_service.h @@ -15,6 +15,7 @@ #include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" #include "base/time/time.h" +#include "brave/components/brave_wallet/browser/asset_discovery_manager.h" #include "brave/components/brave_wallet/browser/brave_wallet_p3a.h" #include "brave/components/brave_wallet/browser/brave_wallet_service_delegate.h" #include "brave/components/brave_wallet/common/brave_wallet.mojom.h" @@ -190,9 +191,14 @@ class BraveWalletService : public KeyedService, void Base58Encode(const std::vector>& addresses, Base58EncodeCallback callback) override; + void DiscoverAssetsOnAllSupportedChains() override; + // BraveWalletServiceDelegate::Observer: void OnActiveOriginChanged(const mojom::OriginInfoPtr& origin_info) override; + void OnDiscoverAssetsCompleted( + std::vector discovered_assets); + // Resets things back to the original state of BraveWalletService. // To be used when the Wallet is reset / erased void Reset() override; @@ -312,6 +318,7 @@ class BraveWalletService : public KeyedService, raw_ptr tx_service_ = nullptr; raw_ptr prefs_ = nullptr; BraveWalletP3A brave_wallet_p3a_; + AssetDiscoveryManager asset_discovery_manager_; mojo::ReceiverSet receivers_; PrefChangeRegistrar pref_change_registrar_; base::RepeatingTimer p3a_periodic_timer_; diff --git a/components/brave_wallet/browser/ethereum_provider_impl.h b/components/brave_wallet/browser/ethereum_provider_impl.h index 86b77033ce0..6c0d3886792 100644 --- a/components/brave_wallet/browser/ethereum_provider_impl.h +++ b/components/brave_wallet/browser/ethereum_provider_impl.h @@ -180,6 +180,7 @@ class EthereumProviderImpl final const std::string& error) override; void OnIsEip1559Changed(const std::string& chain_id, bool is_eip1559) override {} + void OnSwitchEthereumChainRequested(const std::string& chain_id, const GURL& origin) {} void OnSwitchEthereumChainRequestProcessed(bool approved, @@ -312,6 +313,8 @@ class EthereumProviderImpl final void Unlocked() override; void BackedUp() override {} void AccountsChanged() override {} + void AccountsAdded(mojom::CoinType coin, + const std::vector& addresses) override {} void AutoLockMinutesChanged() override {} void SelectedAccountChanged(mojom::CoinType coin) override; diff --git a/components/brave_wallet/browser/json_rpc_service.cc b/components/brave_wallet/browser/json_rpc_service.cc index 6926b54c3b9..6dfdcb68d27 100644 --- a/components/brave_wallet/browser/json_rpc_service.cc +++ b/components/brave_wallet/browser/json_rpc_service.cc @@ -37,6 +37,7 @@ #include "brave/components/brave_wallet/browser/unstoppable_domains_dns_resolve.h" #include "brave/components/brave_wallet/browser/unstoppable_domains_multichain_calls.h" #include "brave/components/brave_wallet/common/brave_wallet_response_helpers.h" +#include "brave/components/brave_wallet/common/brave_wallet_types.h" #include "brave/components/brave_wallet/common/eth_abi_utils.h" #include "brave/components/brave_wallet/common/eth_address.h" #include "brave/components/brave_wallet/common/eth_request_helper.h" @@ -692,10 +693,11 @@ void JsonRpcService::GetFeeHistory(GetFeeHistoryCallback callback) { base::BindOnce(&JsonRpcService::OnGetFeeHistory, weak_ptr_factory_.GetWeakPtr(), std::move(callback)); - RequestInternal( - eth::eth_feeHistory("0x28", // blockCount = 40 - "latest", std::vector{20, 50, 80}), - true, network_urls_[mojom::CoinType::ETH], std::move(internal_callback)); + RequestInternal(eth::eth_feeHistory("0x28", // blockCount = 40 + kEthereumBlockTagLatest, + std::vector{20, 50, 80}), + true, network_urls_[mojom::CoinType::ETH], + std::move(internal_callback)); } void JsonRpcService::OnGetFeeHistory(GetFeeHistoryCallback callback, @@ -743,8 +745,8 @@ void JsonRpcService::GetBalance(const std::string& address, auto internal_callback = base::BindOnce(&JsonRpcService::OnEthGetBalance, weak_ptr_factory_.GetWeakPtr(), std::move(callback)); - RequestInternal(eth::eth_getBalance(address, "latest"), true, network_url, - std::move(internal_callback)); + RequestInternal(eth::eth_getBalance(address, kEthereumBlockTagLatest), true, + network_url, std::move(internal_callback)); return; } else if (coin == mojom::CoinType::FIL) { auto internal_callback = @@ -889,8 +891,9 @@ void JsonRpcService::GetEthTransactionCount(const std::string& address, base::BindOnce(&JsonRpcService::OnEthGetTransactionCount, weak_ptr_factory_.GetWeakPtr(), std::move(callback)); - RequestInternal(eth::eth_getTransactionCount(address, "latest"), true, - network_url, std::move(internal_callback)); + RequestInternal( + eth::eth_getTransactionCount(address, kEthereumBlockTagLatest), true, + network_url, std::move(internal_callback)); } void JsonRpcService::OnFilGetTransactionCount( @@ -1019,8 +1022,9 @@ void JsonRpcService::GetERC20TokenBalance( auto internal_callback = base::BindOnce(&JsonRpcService::OnGetERC20TokenBalance, weak_ptr_factory_.GetWeakPtr(), std::move(callback)); - RequestInternal(eth::eth_call("", contract, "", "", "", data, "latest"), true, - network_url, std::move(internal_callback)); + RequestInternal( + eth::eth_call("", contract, "", "", "", data, kEthereumBlockTagLatest), + true, network_url, std::move(internal_callback)); } void JsonRpcService::OnGetERC20TokenBalance( @@ -1069,9 +1073,10 @@ void JsonRpcService::GetERC20TokenAllowance( auto internal_callback = base::BindOnce(&JsonRpcService::OnGetERC20TokenAllowance, weak_ptr_factory_.GetWeakPtr(), std::move(callback)); - RequestInternal( - eth::eth_call("", contract_address, "", "", "", data, "latest"), true, - network_urls_[mojom::CoinType::ETH], std::move(internal_callback)); + RequestInternal(eth::eth_call("", contract_address, "", "", "", data, + kEthereumBlockTagLatest), + true, network_urls_[mojom::CoinType::ETH], + std::move(internal_callback)); } void JsonRpcService::OnGetERC20TokenAllowance( @@ -1129,9 +1134,9 @@ void JsonRpcService::EnsRegistryGetResolver(const std::string& domain, auto internal_callback = base::BindOnce(&JsonRpcService::OnEnsRegistryGetResolver, weak_ptr_factory_.GetWeakPtr(), std::move(callback)); - RequestInternal( - eth::eth_call("", contract_address, "", "", "", data, "latest"), true, - network_url, std::move(internal_callback)); + RequestInternal(eth::eth_call("", contract_address, "", "", "", data, + kEthereumBlockTagLatest), + true, network_url, std::move(internal_callback)); } void JsonRpcService::OnEnsRegistryGetResolver( @@ -1234,9 +1239,9 @@ void JsonRpcService::ContinueEnsGetContentHash( auto internal_callback = base::BindOnce(&JsonRpcService::OnEnsGetContentHash, weak_ptr_factory_.GetWeakPtr(), std::move(callback)); - RequestInternal( - eth::eth_call("", resolver_address, "", "", "", data, "latest"), true, - network_url, std::move(internal_callback)); + RequestInternal(eth::eth_call("", resolver_address, "", "", "", data, + kEthereumBlockTagLatest), + true, network_url, std::move(internal_callback)); } void JsonRpcService::OnEnsGetContentHash(EnsGetContentHashCallback callback, @@ -1556,9 +1561,10 @@ void JsonRpcService::ContinueEnsGetEthAddr(const std::string& domain, auto internal_callback = base::BindOnce(&JsonRpcService::OnEnsGetEthAddr, weak_ptr_factory_.GetWeakPtr(), std::move(callback)); - RequestInternal( - eth::eth_call("", resolver_address, "", "", "", data, "latest"), true, - network_urls_[mojom::CoinType::ETH], std::move(internal_callback)); + RequestInternal(eth::eth_call("", resolver_address, "", "", "", data, + kEthereumBlockTagLatest), + true, network_urls_[mojom::CoinType::ETH], + std::move(internal_callback)); } void JsonRpcService::OnEnsGetEthAddr(EnsGetEthAddrCallback callback, @@ -1623,7 +1629,7 @@ void JsonRpcService::UnstoppableDomainsResolveDns( weak_ptr_factory_.GetWeakPtr(), domain, chain_id); auto eth_call = eth::eth_call( "", GetUnstoppableDomainsProxyReaderContractAddress(chain_id), "", "", - "", *data, "latest"); + "", *data, kEthereumBlockTagLatest); RequestInternal(std::move(eth_call), true, GetUnstoppableDomainsRpcUrl(chain_id), std::move(internal_callback)); @@ -1869,8 +1875,8 @@ void JsonRpcService::GetIsEip1559(GetIsEip1559Callback callback) { auto internal_callback = base::BindOnce(&JsonRpcService::OnGetIsEip1559, weak_ptr_factory_.GetWeakPtr(), std::move(callback)); - RequestInternal(eth::eth_getBlockByNumber("latest", false), true, - network_urls_[mojom::CoinType::ETH], + RequestInternal(eth::eth_getBlockByNumber(kEthereumBlockTagLatest, false), + true, network_urls_[mojom::CoinType::ETH], std::move(internal_callback)); } @@ -1941,8 +1947,9 @@ void JsonRpcService::GetERC721OwnerOf(const std::string& contract, auto internal_callback = base::BindOnce(&JsonRpcService::OnGetERC721OwnerOf, weak_ptr_factory_.GetWeakPtr(), std::move(callback)); - RequestInternal(eth::eth_call("", contract, "", "", "", data, "latest"), true, - network_url, std::move(internal_callback)); + RequestInternal( + eth::eth_call("", contract, "", "", "", data, kEthereumBlockTagLatest), + true, network_url, std::move(internal_callback)); } void JsonRpcService::OnGetERC721OwnerOf(GetERC721OwnerOfCallback callback, @@ -2109,7 +2116,7 @@ void JsonRpcService::OnGetSupportsInterfaceTokenMetadata( weak_ptr_factory_.GetWeakPtr(), std::move(callback)); RequestInternal(eth::eth_call("", contract_address, "", "", "", - function_signature, "latest"), + function_signature, kEthereumBlockTagLatest), true, network_url, std::move(internal_callback)); } @@ -2263,194 +2270,51 @@ void JsonRpcService::GetERC1155TokenBalance( auto internal_callback = base::BindOnce(&JsonRpcService::OnEthGetBalance, weak_ptr_factory_.GetWeakPtr(), std::move(callback)); - RequestInternal( - eth::eth_call("", contract_address, "", "", "", data, "latest"), true, - network_url, std::move(internal_callback)); -} - -// Called by KeyringService::CreateWallet, KeyringService::RestoreWallet, -// KeyringService::AddAccount, KeyringService::ImportAccountForKeyring, -// and KeyringService::AddHardwareAccounts -void JsonRpcService::DiscoverAssets( - const std::string& chain_id, - mojom::CoinType coin, - const std::vector& account_addresses) { - auto callback = base::BindOnce(&JsonRpcService::OnDiscoverAssetsCompleted, - weak_ptr_factory_.GetWeakPtr()); - DiscoverAssetsInternal(chain_id, coin, account_addresses, - std::move(callback)); -} - -void JsonRpcService::OnDiscoverAssetsCompleted( - const std::vector discovered_assets, - mojom::ProviderError error, - const std::string& error_message) { - if (error != mojom::ProviderError::kSuccess) { - VLOG(1) << __func__ << "Encountered error during asset discovery " - << error_message; - } -} - -void JsonRpcService::DiscoverAssetsInternal( - const std::string& chain_id, - mojom::CoinType coin, - const std::vector& account_addresses, - DiscoverAssetsCallback callback) { - if (coin != mojom::CoinType::ETH || chain_id != mojom::kMainnetChainId) { - std::move(callback).Run( - std::vector(), - mojom::ProviderError::kMethodNotSupported, - l10n_util::GetStringUTF8(IDS_WALLET_METHOD_NOT_SUPPORTED_ERROR)); - return; - } - - // Asset discovery only supported when using Infura proxy - GURL infura_url = GetInfuraURLForKnownChainId(chain_id); - GURL active_url = GetNetworkURL(prefs_, chain_id, coin); - if (infura_url.host() != active_url.host()) { - std::move(callback).Run( - std::vector(), - mojom::ProviderError::kMethodNotSupported, - l10n_util::GetStringUTF8(IDS_WALLET_METHOD_NOT_SUPPORTED_ERROR)); - return; - } - - if (account_addresses.empty()) { - std::move(callback).Run( - std::vector(), - mojom::ProviderError::kInvalidParams, - l10n_util::GetStringUTF8(IDS_WALLET_INVALID_PARAMETERS)); - return; - } - - for (const auto& account_address : account_addresses) { - if (!EthAddress::IsValidAddress(account_address)) { - std::move(callback).Run( - std::vector(), - mojom::ProviderError::kInvalidParams, - l10n_util::GetStringUTF8(IDS_WALLET_INVALID_PARAMETERS)); - return; - } - } - - std::vector user_assets = - BraveWalletService::GetUserAssets(chain_id, mojom::CoinType::ETH, prefs_); - auto internal_callback = base::BindOnce( - &JsonRpcService::OnGetAllTokensDiscoverAssets, - weak_ptr_factory_.GetWeakPtr(), chain_id, account_addresses, - std::move(user_assets), std::move(callback)); - - BlockchainRegistry::GetInstance()->GetAllTokens( - chain_id, mojom::CoinType::ETH, std::move(internal_callback)); -} - -void JsonRpcService::OnGetAllTokensDiscoverAssets( - const std::string& chain_id, - const std::vector& account_addresses, - std::vector user_assets, - DiscoverAssetsCallback callback, - std::vector token_registry) { - auto network_url = GetNetworkURL(prefs_, chain_id, mojom::CoinType::ETH); - if (!network_url.is_valid()) { - std::move(callback).Run( - std::vector(), - mojom::ProviderError::kInvalidParams, - l10n_util::GetStringUTF8(IDS_WALLET_INVALID_PARAMETERS)); - return; - } - - base::Value::List topics; - if (!MakeAssetDiscoveryTopics(account_addresses, &topics)) { - std::move(callback).Run( - std::vector(), - mojom::ProviderError::kInvalidParams, - l10n_util::GetStringUTF8(IDS_WALLET_INVALID_PARAMETERS)); - return; - } - - // Create set of contract addresses the user already has for easy lookups - base::flat_set user_asset_contract_addresses; - for (const auto& user_asset : user_assets) { - user_asset_contract_addresses.insert(user_asset->contract_address); - } - - // Create a list of contract addresses to search by removing - // all erc20s and assets the user has already added. - base::Value::List contract_addresses_to_search; - // Also create a map for addresses to blockchain tokens for easy lookup - // for blockchain tokens in OnGetTransferLogs - base::flat_map tokens_to_search; - for (auto& registry_token : token_registry) { - if (registry_token->is_erc20 && !registry_token->contract_address.empty() && - !user_asset_contract_addresses.contains( - registry_token->contract_address)) { - // Use lowercase representation of hex address for comparisons - // because providers may return all lowercase addresses. - const std::string lower_case_contract_address = - base::ToLowerASCII(registry_token->contract_address); - contract_addresses_to_search.Append(lower_case_contract_address); - tokens_to_search[lower_case_contract_address] = std::move(registry_token); - } - } - - if (contract_addresses_to_search.size() == 0) { - std::move(callback).Run(std::vector(), - mojom::ProviderError::kSuccess, ""); - return; - } - - auto internal_callback = base::BindOnce( - &JsonRpcService::OnGetTransferLogs, weak_ptr_factory_.GetWeakPtr(), - std::move(callback), base::OwnedRef(std::move(tokens_to_search))); - - RequestInternal(eth::eth_getLogs("earliest", "latest", - std::move(contract_addresses_to_search), - std::move(topics), ""), + RequestInternal(eth::eth_call("", contract_address, "", "", "", data, + kEthereumBlockTagLatest), true, network_url, std::move(internal_callback)); } -void JsonRpcService::OnGetTransferLogs( - DiscoverAssetsCallback callback, - base::flat_map& tokens_to_search, - APIRequestResult api_request_result) { - if (!api_request_result.Is2XXResponseCode()) { +void JsonRpcService::EthGetLogs(const std::string& chain_id, + const std::string& from_block, + const std::string& to_block, + base::Value::List contract_addresses, + base::Value::List topics, + EthGetLogsCallback callback) { + auto network_url = GetNetworkURL(prefs_, chain_id, mojom::CoinType::ETH); + if (!network_url.is_valid()) { std::move(callback).Run( - std::vector(), - mojom::ProviderError::kInternalError, + std::vector(), mojom::ProviderError::kInternalError, l10n_util::GetStringUTF8(IDS_WALLET_INTERNAL_ERROR)); return; } + auto internal_callback = + base::BindOnce(&JsonRpcService::OnEthGetLogs, + weak_ptr_factory_.GetWeakPtr(), std::move(callback)); + RequestInternal( + eth::eth_getLogs(from_block, to_block, std::move(contract_addresses), + std::move(topics), ""), + true, network_url, std::move(internal_callback)); +} + +void JsonRpcService::OnEthGetLogs(EthGetLogsCallback callback, + APIRequestResult api_request_result) { std::vector logs; + if (!api_request_result.Is2XXResponseCode()) { + std::move(callback).Run( + logs, mojom::ProviderError::kInternalError, + l10n_util::GetStringUTF8(IDS_WALLET_INTERNAL_ERROR)); + return; + } + if (!eth::ParseEthGetLogs(api_request_result.body(), &logs)) { - std::move(callback).Run(std::vector(), - mojom::ProviderError::kParsingError, + std::move(callback).Run(logs, mojom::ProviderError::kParsingError, l10n_util::GetStringUTF8(IDS_WALLET_PARSING_ERROR)); return; } - // Create unique list of addresses that matched eth_getLogs query - base::flat_set matching_contract_addresses; - for (const auto& log : logs) { - matching_contract_addresses.insert(base::ToLowerASCII(log.address)); - } - std::vector discovered_assets; - - for (const auto& contract_address : matching_contract_addresses) { - if (!tokens_to_search.contains(contract_address)) { - continue; - } - mojom::BlockchainTokenPtr token = - std::move(tokens_to_search.at(contract_address)); - - if (!BraveWalletService::AddUserAsset(token.Clone(), prefs_)) { - continue; - } - discovered_assets.push_back(std::move(token)); - } - - std::move(callback).Run(std::move(discovered_assets), - mojom::ProviderError::kSuccess, ""); + std::move(callback).Run(logs, mojom::ProviderError::kSuccess, ""); } void JsonRpcService::GetSupportsInterface( @@ -2479,9 +2343,9 @@ void JsonRpcService::GetSupportsInterface( base::BindOnce(&JsonRpcService::OnGetSupportsInterface, weak_ptr_factory_.GetWeakPtr(), std::move(callback)); DCHECK(network_urls_.contains(mojom::CoinType::ETH)); - RequestInternal( - eth::eth_call("", contract_address, "", "", "", data, "latest"), true, - network_url, std::move(internal_callback)); + RequestInternal(eth::eth_call("", contract_address, "", "", "", data, + kEthereumBlockTagLatest), + true, network_url, std::move(internal_callback)); } void JsonRpcService::OnGetSupportsInterface( diff --git a/components/brave_wallet/browser/json_rpc_service.h b/components/brave_wallet/browser/json_rpc_service.h index ecfae3a9614..a695c3248a1 100644 --- a/components/brave_wallet/browser/json_rpc_service.h +++ b/components/brave_wallet/browser/json_rpc_service.h @@ -86,6 +86,11 @@ class JsonRpcService : public KeyedService, public mojom::JsonRpcService { const std::vector>& reward, mojom::ProviderError error, const std::string& error_message)>; + + using EthGetLogsCallback = + base::OnceCallback& logs, + mojom::ProviderError error, + const std::string& error_message)>; void GetBlockNumber(GetBlockNumberCallback callback); void GetFeeHistory(GetFeeHistoryCallback callback); @@ -315,35 +320,15 @@ class JsonRpcService : public KeyedService, public mojom::JsonRpcService { const std::string& chain_id, GetTokenMetadataCallback callback) override; - using DiscoverAssetsCallback = base::OnceCallback discovered_assets, - mojom::ProviderError error, - const std::string& error_message)>; - void DiscoverAssets(const std::string& chain_id, - mojom::CoinType coin, - const std::vector& account_addresses); + void EthGetLogs(const std::string& chain_id, + const std::string& from_block, + const std::string& to_block, + base::Value::List addresses, + base::Value::List topics, + EthGetLogsCallback callback); - void DiscoverAssetsInternal(const std::string& chain_id, - mojom::CoinType coin, - const std::vector& account_addresses, - DiscoverAssetsCallback callback); - - void OnGetAllTokensDiscoverAssets( - const std::string& chain_id, - const std::vector& account_addresses, - std::vector user_assets, - DiscoverAssetsCallback callback, - std::vector token_list); - - void OnGetTransferLogs( - DiscoverAssetsCallback callback, - base::flat_map& user_assets_map, - APIRequestResult api_request_result); - - void OnDiscoverAssetsCompleted( - std::vector discovered_assets, - mojom::ProviderError error, - const std::string& error_message); + void OnEthGetLogs(EthGetLogsCallback callback, + APIRequestResult api_request_result); // Resets things back to the original state of BraveWalletService. // To be used when the Wallet is reset / erased diff --git a/components/brave_wallet/browser/json_rpc_service_unittest.cc b/components/brave_wallet/browser/json_rpc_service_unittest.cc index 57be6847d03..1e4ecbeafee 100644 --- a/components/brave_wallet/browser/json_rpc_service_unittest.cc +++ b/components/brave_wallet/browser/json_rpc_service_unittest.cc @@ -30,7 +30,6 @@ #include "base/test/task_environment.h" #include "base/threading/scoped_blocking_call.h" #include "base/values.h" -#include "brave/components/brave_wallet/browser/blockchain_registry.h" #include "brave/components/brave_wallet/browser/brave_wallet_constants.h" #include "brave/components/brave_wallet/browser/brave_wallet_prefs.h" #include "brave/components/brave_wallet/browser/brave_wallet_utils.h" @@ -1126,31 +1125,26 @@ class JsonRpcServiceUnitTest : public testing::Test { run_loop.Run(); } - void TestDiscoverAssetsInternal( - const std::string& chain_id, - mojom::CoinType coin, - const std::vector& account_addresses, - const std::vector& expected_token_contract_addresses, - mojom::ProviderError expected_error, - const std::string& expected_error_message) { + void TestEthGetLogs(const std::string& chain_id, + const std::string& from_block, + const std::string& to_block, + base::Value::List contract_addresses, + base::Value::List topics, + const std::vector& expected_logs, + mojom::ProviderError expected_error, + const std::string& expected_error_message) { base::RunLoop run_loop; - std::vector expected_tokens; - json_rpc_service_->DiscoverAssetsInternal( - chain_id, mojom::CoinType::ETH, account_addresses, - base::BindLambdaForTesting( - [&](const std::vector tokens, - mojom::ProviderError error, const std::string& error_message) { - ASSERT_EQ(tokens.size(), - expected_token_contract_addresses.size()); - for (size_t i = 0; i < expected_token_contract_addresses.size(); - i++) { - EXPECT_EQ(tokens[i]->contract_address, - expected_token_contract_addresses[i]); - } - EXPECT_EQ(error, expected_error); - EXPECT_EQ(error_message, expected_error_message); - run_loop.Quit(); - })); + json_rpc_service_->EthGetLogs( + chain_id, from_block, to_block, std::move(contract_addresses), + std::move(topics), + base::BindLambdaForTesting([&](const std::vector& logs, + mojom::ProviderError error, + const std::string& error_message) { + EXPECT_EQ(logs, expected_logs); + EXPECT_EQ(error, expected_error); + EXPECT_EQ(error_message, expected_error_message); + run_loop.Quit(); + })); run_loop.Run(); } @@ -3883,204 +3877,6 @@ TEST_F(JsonRpcServiceUnitTest, GetSupportsInterface) { EXPECT_TRUE(callback_called); } -TEST_F(JsonRpcServiceUnitTest, DiscoverAssets) { - auto* blockchain_registry = BlockchainRegistry::GetInstance(); - TokenListMap token_list_map; - - std::string response; - - // Unsupported chainId is not supported - TestDiscoverAssetsInternal( - mojom::kPolygonMainnetChainId, mojom::CoinType::ETH, - {"0xB4B2802129071b2B9eBb8cBB01EA1E4D14B34961"}, {}, - mojom::ProviderError::kMethodNotSupported, - l10n_util::GetStringUTF8(IDS_WALLET_METHOD_NOT_SUPPORTED_ERROR)); - - // Empty address is invalid - TestDiscoverAssetsInternal( - mojom::kMainnetChainId, mojom::CoinType::ETH, {}, {}, - mojom::ProviderError::kInvalidParams, - l10n_util::GetStringUTF8(IDS_WALLET_INVALID_PARAMETERS)); - - // Invalid address is invalid - TestDiscoverAssetsInternal( - mojom::kMainnetChainId, mojom::CoinType::ETH, {"0xinvalid"}, {}, - mojom::ProviderError::kInvalidParams, - l10n_util::GetStringUTF8(IDS_WALLET_INVALID_PARAMETERS)); - - // Invalid RPC response json response triggers parsing error - auto expected_network = - GetNetwork(mojom::kMainnetChainId, mojom::CoinType::ETH); - std::string token_list_json = R"({ - "0x0d8775f648430679a709e98d2b0cb6250d2887ef": { - "name": "Basic Attention Token", - "logo": "bat.svg", - "erc20": true, - "symbol": "BAT", - "decimals": 18 - } - })"; - ASSERT_TRUE( - ParseTokenList(token_list_json, &token_list_map, mojom::CoinType::ETH)); - blockchain_registry->UpdateTokenList(std::move(token_list_map)); - SetInterceptor(expected_network, "eth_getLogs", "", - "invalid eth_getLogs response"); - TestDiscoverAssetsInternal( - mojom::kMainnetChainId, mojom::CoinType::ETH, - {"0xB4B2802129071b2B9eBb8cBB01EA1E4D14B34961"}, {}, - mojom::ProviderError::kParsingError, - l10n_util::GetStringUTF8(IDS_WALLET_PARSING_ERROR)); - - // Invalid limit exceeded response triggers parsing error - SetLimitExceededJsonErrorResponse(); - TestDiscoverAssetsInternal( - mojom::kMainnetChainId, mojom::CoinType::ETH, - {"0xB4B2802129071b2B9eBb8cBB01EA1E4D14B34961"}, {}, - mojom::ProviderError::kParsingError, - l10n_util::GetStringUTF8(IDS_WALLET_PARSING_ERROR)); - - // Invalid logs (missing addresses) triggers parsing error - response = R"({ - "jsonrpc": "2.0", - "id": 1, - "result": [ - { - "blockHash": "0xaefb023131aa58e533c09c0eae29c280460d3976f5235a1ff53159ef37f73073", - "blockNumber": "0xa72603", - "data": "0x000000000000000000000000000000000000000000000006e83695ab1f893c00", - "logIndex": "0x14", - "removed": false, - "topics": [ - "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", - "0x000000000000000000000000897bb1e945f5aa7ed7f81646e7991eaba63aa4b0", - "0x000000000000000000000000b4b2802129071b2b9ebb8cbb01ea1e4d14b34961" - ], - "transactionHash": "0x5c655301d386f45af116a4aef418491ee27b71ac30be70a593ccffa3754797d4", - "transactionIndex": "0xa" - }, - ] - })"; - SetInterceptor(expected_network, "eth_getLogs", "", response); - TestDiscoverAssetsInternal( - mojom::kMainnetChainId, mojom::CoinType::ETH, - {"0xB4B2802129071b2B9eBb8cBB01EA1E4D14B34961"}, {}, - mojom::ProviderError::kParsingError, - l10n_util::GetStringUTF8(IDS_WALLET_PARSING_ERROR)); - - // Valid registry token DAI is discovered and added. - // Valid registry token WETH is discovered and added (tests insensitivity to - // lower case addresses in provider logs response). - // Valid BAT is not added because it is already a user asset. - // Invalid LilNoun is not added because it is an ERC721. - token_list_json = R"( - { - "0x0d8775f648430679a709e98d2b0cb6250d2887ef": { - "name": "Basic Attention Token", - "logo": "bat.svg", - "erc20": true, - "symbol": "BAT", - "decimals": 18 - }, - "0x6B175474E89094C44Da98b954EedeAC495271d0F": { - "name": "Dai Stablecoin", - "logo": "dai.svg", - "erc20": true, - "symbol": "DAI", - "decimals": 18 - }, - "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2": { - "name": "Wrapped Eth", - "logo": "weth.svg", - "erc20": true, - "symbol": "WETH", - "decimals": 18, - "chainId": "0x1" - }, - "0x4b10701Bfd7BFEdc47d50562b76b436fbB5BdB3B": { - "name": "Lil Nouns", - "logo": "lilnouns.svg", - "erc20": false, - "erc721": true, - "symbol": "LilNouns", - "chainId": "0x1" - } - })"; - ASSERT_TRUE( - ParseTokenList(token_list_json, &token_list_map, mojom::CoinType::ETH)); - blockchain_registry->UpdateTokenList(std::move(token_list_map)); - - // Note: the matching transfer log for WETH uses an all lowercase address - // while the token registry uses checksum address (contains uppercase) - response = R"({ - "jsonrpc":"2.0", - "id":1, - "result":[ - { - "address":"0x6B175474E89094C44Da98b954EedeAC495271d0F", - "blockHash":"0x2961ceb6c16bab72a55f79e394a35f2bf1c62b30446e3537280f7c22c3115e6e", - "blockNumber":"0xd6464c", - "data":"0x00000000000000000000000000000000000000000000000555aff1f0fae8c000", - "logIndex":"0x159", - "removed":false, - "topics":[ - "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", - "0x000000000000000000000000503828976d22510aad0201ac7ec88293211d23da", - "0x000000000000000000000000b4b2802129071b2b9ebb8cbb01ea1e4d14b34961" - ], - "transactionHash":"0x2e652b70966c6a05f4b3e68f20d6540b7a5ab712385464a7ccf62774d39b7066", - "transactionIndex":"0x9f" - }, - { - "address":"0x4b10701Bfd7BFEdc47d50562b76b436fbB5BdB3B", - "blockHash":"0x2961ceb6c16bab72a55f79e394a35f2bf1c62b30446e3537280f7c22c3115e6e", - "blockNumber":"0xd6464c", - "data":"0x00000000000000000000000000000000000000000000000555aff1f0fae8c000", - "logIndex":"0x159", - "removed":false, - "topics":[ - "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", - "0x000000000000000000000000503828976d22510aad0201ac7ec88293211d23da", - "0x000000000000000000000000b4b2802129071b2b9ebb8cbb01ea1e4d14b34961" - ], - "transactionHash":"0x2e652b70966c6a05f4b3e68f20d6540b7a5ab712385464a7ccf62774d39b7066", - "transactionIndex":"0x9f" - }, - { - "address":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "blockHash":"0x2961ceb6c16bab72a55f79e394a35f2bf1c62b30446e3537280f7c22c3115e6e", - "blockNumber":"0xd6464c", - "data":"0x00000000000000000000000000000000000000000000000555aff1f0fae8c000", - "logIndex":"0x159", - "removed":false, - "topics":[ - "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", - "0x000000000000000000000000503828976d22510aad0201ac7ec88293211d23da", - "0x000000000000000000000000b4b2802129071b2b9ebb8cbb01ea1e4d14b34961" - ], - "transactionHash":"0x2e652b70966c6a05f4b3e68f20d6540b7a5ab712385464a7ccf62774d39b7066", - "transactionIndex":"0x9f" - } - ] - })"; - SetInterceptor(expected_network, "eth_getLogs", "", response); - TestDiscoverAssetsInternal(mojom::kMainnetChainId, mojom::CoinType::ETH, - {"0xB4B2802129071b2B9eBb8cBB01EA1E4D14B34961"}, - {"0x6B175474E89094C44Da98b954EedeAC495271d0F", - "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"}, - mojom::ProviderError::kSuccess, ""); - - // Discover assets should not run unless using Infura proxy - std::vector values; - mojom::NetworkInfo chain = GetTestNetworkInfo1("0x1"); - values.push_back(brave_wallet::NetworkInfoToValue(chain)); - UpdateCustomNetworks(prefs(), &values); - TestDiscoverAssetsInternal( - mojom::kMainnetChainId, mojom::CoinType::ETH, - {"0xB4B2802129071b2B9eBb8cBB01EA1E4D14B34961"}, {}, - mojom::ProviderError::kMethodNotSupported, - l10n_util::GetStringUTF8(IDS_WALLET_METHOD_NOT_SUPPORTED_ERROR)); -} - TEST_F(JsonRpcServiceUnitTest, Reset) { std::vector values; mojom::NetworkInfo chain = GetTestNetworkInfo1("0x1"); @@ -5978,4 +5774,80 @@ TEST_F(SnsJsonRpcServiceUnitTest, ResolveHost_IpfsValue) { testing::Mock::VerifyAndClearExpectations(&callback); } +TEST_F(JsonRpcServiceUnitTest, EthGetLogs) { + base::Value::List contract_addresses; + base::Value::List topics; + + // Invalid network ID yields internal error + TestEthGetLogs("0xinvalid", "earliest", "latest", + std::move(contract_addresses), std::move(topics), {}, + mojom::ProviderError::kInternalError, + l10n_util::GetStringUTF8(IDS_WALLET_INTERNAL_ERROR)); + + // Non 200 response yields internal error + SetHTTPRequestTimeoutInterceptor(); + TestEthGetLogs(mojom::kMainnetChainId, "earliest", "latest", + std::move(contract_addresses), std::move(topics), {}, + mojom::ProviderError::kInternalError, + l10n_util::GetStringUTF8(IDS_WALLET_INTERNAL_ERROR)); + + // Invalid response body yields parsing error + SetInvalidJsonInterceptor(); + TestEthGetLogs(mojom::kMainnetChainId, "earliest", "latest", + std::move(contract_addresses), std::move(topics), {}, + mojom::ProviderError::kParsingError, + l10n_util::GetStringUTF8(IDS_WALLET_PARSING_ERROR)); + + // Valid request yields parsed Logs + const std::string response = R"({ + "jsonrpc":"2.0", + "id":1, + "result":[ + { + "address":"0x6B175474E89094C44Da98b954EedeAC495271d0F", + "blockHash":"0x2961ceb6c16bab72a55f79e394a35f2bf1c62b30446e3537280f7c22c3115e6e", + "blockNumber":"0xd6464e", + "data":"0x00000000000000000000000000000000000000000000000555aff1f0fae8c000", + "logIndex":"0x159", + "removed":false, + "topics":[ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000503828976d22510aad0201ac7ec88293211d23da", + "0x000000000000000000000000b4b2802129071b2b9ebb8cbb01ea1e4d14b34961" + ], + "transactionHash":"0x2e652b70966c6a05f4b3e68f20d6540b7a5ab712385464a7ccf62774d39b7066", + "transactionIndex":"0x9f" + } + ] + })"; + + Log expected_log; + expected_log.address = "0x6B175474E89094C44Da98b954EedeAC495271d0F"; + expected_log.block_hash = + "0x2961ceb6c16bab72a55f79e394a35f2bf1c62b30446e3537280f7c22c3115e6e"; + uint256_t expected_block_number = 14042702; + expected_log.block_number = expected_block_number; + expected_log.data = + "0x00000000000000000000000000000000000000000000000555aff1f0fae8c000"; + uint32_t expected_log_index = 345; + expected_log.log_index = expected_log_index; + expected_log.removed = false; + std::vector expected_topics = { + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000503828976d22510aad0201ac7ec88293211d23da", + "0x000000000000000000000000b4b2802129071b2b9ebb8cbb01ea1e4d14b34961"}; + expected_log.topics = std::move(expected_topics); + expected_log.transaction_hash = + "0x2e652b70966c6a05f4b3e68f20d6540b7a5ab712385464a7ccf62774d39b7066"; + uint32_t expected_transaction_index = 159; + expected_log.transaction_index = expected_transaction_index; + std::vector expected_logs; + expected_logs.push_back(std::move(expected_log)); + SetInterceptor(GetNetwork(mojom::kMainnetChainId, mojom::CoinType::ETH), + "eth_getLogs", "", response); + TestEthGetLogs(mojom::kMainnetChainId, "earliest", "latest", + std::move(contract_addresses), std::move(topics), + std::move(expected_logs), mojom::ProviderError::kSuccess, ""); +} + } // namespace brave_wallet diff --git a/components/brave_wallet/browser/keyring_service.cc b/components/brave_wallet/browser/keyring_service.cc index 0db73f4de53..13cfcbc344b 100644 --- a/components/brave_wallet/browser/keyring_service.cc +++ b/components/brave_wallet/browser/keyring_service.cc @@ -731,8 +731,8 @@ void KeyringService::CreateWallet(const std::string& password, if (address) { SetPrefForKeyring(prefs_, kSelectedAccount, base::Value(*address), mojom::kDefaultKeyringId); - json_rpc_service_->DiscoverAssets( - mojom::kMainnetChainId, mojom::CoinType::ETH, {address.value()}); + + NotifyAccountsAdded(mojom::CoinType::ETH, {address.value()}); } } @@ -769,8 +769,7 @@ void KeyringService::RestoreWallet(const std::string& mnemonic, if (address) { SetPrefForKeyring(prefs_, kSelectedAccount, base::Value(*address), mojom::kDefaultKeyringId); - json_rpc_service_->DiscoverAssets( - mojom::kMainnetChainId, mojom::CoinType::ETH, {address.value()}); + NotifyAccountsAdded(mojom::CoinType::ETH, {address.value()}); } } @@ -912,8 +911,7 @@ void KeyringService::AddAccount(const std::string& account_name, if (address) { SetSelectedAccountForCoinSilently(coin, address.value()); SetSelectedCoin(prefs_, coin); - json_rpc_service_->DiscoverAssets(mojom::kMainnetChainId, - mojom::CoinType::ETH, {address.value()}); + NotifyAccountsAdded(coin, {address.value()}); } NotifyAccountsChanged(); @@ -1357,10 +1355,8 @@ absl::optional KeyringService::ImportAccountForKeyring( SetSelectedAccountForCoinSilently(GetCoinForKeyring(keyring_id), address); SetSelectedCoin(prefs_, GetCoinForKeyring(keyring_id)); - json_rpc_service_->DiscoverAssets(mojom::kMainnetChainId, - mojom::CoinType::ETH, {address}); NotifyAccountsChanged(); - + NotifyAccountsAdded(info.coin, {address}); return address; } @@ -1471,10 +1467,8 @@ void KeyringService::AddHardwareAccounts( account_selected = true; } } - - json_rpc_service_->DiscoverAssets(mojom::kMainnetChainId, - mojom::CoinType::ETH, addresses); NotifyAccountsChanged(); + NotifyAccountsAdded(infos[0]->coin, addresses); } void KeyringService::RemoveHardwareAccount( @@ -2163,6 +2157,18 @@ void KeyringService::NotifyAccountsChanged() { } } +void KeyringService::NotifyAccountsAdded( + mojom::CoinType coin, + const std::vector& addresses) { + for (const auto& observer : observers_) { + std::vector addresses_clone; + for (const auto& address : addresses) { + addresses_clone.push_back(address); + } + observer->AccountsAdded(coin, addresses_clone); + } +} + void KeyringService::OnAutoLockPreferenceChanged() { StopAutoLockTimer(); ResetAutoLockTimer(); diff --git a/components/brave_wallet/browser/keyring_service.h b/components/brave_wallet/browser/keyring_service.h index 5caaa5defc1..a204e46790d 100644 --- a/components/brave_wallet/browser/keyring_service.h +++ b/components/brave_wallet/browser/keyring_service.h @@ -41,6 +41,7 @@ class EthereumProviderImplUnitTest; class SolanaProviderImplUnitTest; class FilecoinKeyring; class JsonRpcService; +class AssetDiscoveryManagerUnitTest; // This class is not thread-safe and should have single owner class KeyringService : public KeyedService, public mojom::KeyringService { @@ -177,6 +178,7 @@ class KeyringService : public KeyedService, public mojom::KeyringService { void NotifyWalletBackupComplete() override; void GetKeyringsInfo(const std::vector& keyrings, GetKeyringsInfoCallback callback) override; + mojom::KeyringInfoPtr GetKeyringInfoSync(const std::string& keyring_id); void GetKeyringInfo(const std::string& keyring_id, GetKeyringInfoCallback callback) override; void SetHardwareAccountName(const std::string& address, @@ -297,8 +299,7 @@ class KeyringService : public KeyedService, public mojom::KeyringService { FRIEND_TEST_ALL_PREFIXES(KeyringServiceUnitTest, DefaultSolanaAccountCreated); FRIEND_TEST_ALL_PREFIXES(KeyringServiceUnitTest, DefaultSolanaAccountRestored); - FRIEND_TEST_ALL_PREFIXES(KeyringServiceUnitTest, DiscoverAssets); - + FRIEND_TEST_ALL_PREFIXES(KeyringServiceUnitTest, AccountsAdded); FRIEND_TEST_ALL_PREFIXES(KeyringServiceAccountDiscoveryUnitTest, AccountDiscovery); FRIEND_TEST_ALL_PREFIXES(KeyringServiceAccountDiscoveryUnitTest, @@ -307,6 +308,8 @@ class KeyringService : public KeyedService, public mojom::KeyringService { ManuallyAddAccount); FRIEND_TEST_ALL_PREFIXES(KeyringServiceAccountDiscoveryUnitTest, RestoreWalletTwice); + FRIEND_TEST_ALL_PREFIXES(AssetDiscoveryManagerUnitTest, + KeyringServiceObserver); friend class EthereumProviderImplUnitTest; friend class SolanaProviderImplUnitTest; @@ -314,6 +317,7 @@ class KeyringService : public KeyedService, public mojom::KeyringService { friend class EthTxManagerUnitTest; friend class FilTxManagerUnitTest; friend class KeyringServiceUnitTest; + friend class AssetDiscoveryManagerUnitTest; absl::optional FindImportedFilecoinKeyringId( const std::string& address) const; @@ -338,7 +342,6 @@ class KeyringService : public KeyedService, public mojom::KeyringService { const std::string& account_name); void AddDiscoveryAccountsForKeyring(size_t discovery_account_index, int attempts_left); - mojom::KeyringInfoPtr GetKeyringInfoSync(const std::string& keyring_id); void OnAutoLockFired(); HDKeyring* GetHDKeyringById(const std::string& keyring_id) const; std::vector GetHardwareAccountsSync( @@ -385,6 +388,8 @@ class KeyringService : public KeyedService, public mojom::KeyringService { void MaybeMigratePBKDF2Iterations(const std::string& password); void NotifyAccountsChanged(); + void NotifyAccountsAdded(mojom::CoinType coin, + const std::vector& account_infos); void StopAutoLockTimer(); void ResetAutoLockTimer(); void OnAutoLockPreferenceChanged(); diff --git a/components/brave_wallet/browser/pref_names.cc b/components/brave_wallet/browser/pref_names.cc index 5dbefbb3b2d..054ea7533a1 100644 --- a/components/brave_wallet/browser/pref_names.cc +++ b/components/brave_wallet/browser/pref_names.cc @@ -55,6 +55,10 @@ const char kBraveWalletKeyringEncryptionKeysMigrated[] = "brave.wallet.keyring_encryption_keys_migrated"; const char kBraveWalletLastTransactionSentTimeDict[] = "brave.wallet.last_transaction_sent_time_dict"; +const char kBraveWalletNextAssetDiscoveryFromBlocks[] = + "brave.wallet.next_asset_discovery_from_blocks"; +const char kBraveWalletLastDiscoveredAssetsAt[] = + "brave.wallet.last_discovered_assets_at"; // DEPRECATED const char kBraveWalletSelectedAccount[] = "brave.wallet.selected_account"; diff --git a/components/brave_wallet/browser/pref_names.h b/components/brave_wallet/browser/pref_names.h index 2ec26b964e0..e7f3f1d7318 100644 --- a/components/brave_wallet/browser/pref_names.h +++ b/components/brave_wallet/browser/pref_names.h @@ -45,6 +45,8 @@ extern const char kBraveWalletP3AUsedSecondDay[]; extern const char kBraveWalletP3AActiveWalletDict[]; extern const char kBraveWalletKeyringEncryptionKeysMigrated[]; +extern const char kBraveWalletNextAssetDiscoveryFromBlocks[]; +extern const char kBraveWalletLastDiscoveredAssetsAt[]; extern const char kBraveWalletLastTransactionSentTimeDict[]; diff --git a/components/brave_wallet/browser/solana_provider_impl.h b/components/brave_wallet/browser/solana_provider_impl.h index 7abe430145e..186511e8d6b 100644 --- a/components/brave_wallet/browser/solana_provider_impl.h +++ b/components/brave_wallet/browser/solana_provider_impl.h @@ -128,6 +128,8 @@ class SolanaProviderImpl final : public mojom::SolanaProvider, void Unlocked() override; void BackedUp() override {} void AccountsChanged() override {} + void AccountsAdded(mojom::CoinType coin, + const std::vector& addresses) override {} void AutoLockMinutesChanged() override {} void SelectedAccountChanged(mojom::CoinType coin) override; diff --git a/components/brave_wallet/browser/tx_manager.h b/components/brave_wallet/browser/tx_manager.h index 26490a21864..ccb0b8bd6c3 100644 --- a/components/brave_wallet/browser/tx_manager.h +++ b/components/brave_wallet/browser/tx_manager.h @@ -8,6 +8,7 @@ #include #include +#include #include "brave/components/brave_wallet/browser/tx_state_manager.h" #include "brave/components/brave_wallet/common/brave_wallet.mojom.h" @@ -101,6 +102,8 @@ class TxManager : public TxStateManager::Observer, void Unlocked() override; void BackedUp() override {} void AccountsChanged() override {} + void AccountsAdded(mojom::CoinType coin, + const std::vector& addresses) override {} void AutoLockMinutesChanged() override {} void SelectedAccountChanged(mojom::CoinType coin) override {} diff --git a/components/brave_wallet/common/brave_wallet.mojom b/components/brave_wallet/common/brave_wallet.mojom index d2a5e3d77bf..ca525e9649c 100644 --- a/components/brave_wallet/common/brave_wallet.mojom +++ b/components/brave_wallet/common/brave_wallet.mojom @@ -514,6 +514,9 @@ interface KeyringServiceObserver { // Fired when the accounts list changes AccountsChanged(); + // Fired when accounts are added + AccountsAdded(CoinType coin, array addresses); + // Fired when the autolock setting changes AutoLockMinutesChanged(); @@ -1139,6 +1142,9 @@ interface BraveWalletServiceObserver { // Fired when the list of networks changes OnNetworkListChanged(); + + // Fired when a discovered asset query completes + OnDiscoverAssetsCompleted(array discovered_assets); }; struct SignMessageRequest { @@ -1313,12 +1319,15 @@ interface BraveWalletService { // This is used for UI notifying native when the user approves or // rejects decrypting data for an origin NotifyDecryptRequestProcessed(bool approved, url.mojom.Origin origin); + GetPendingDecryptRequests() => (array requests); IsBase58EncodedSolanaPubkey(string key) => (bool result); Base58Encode(array> addresses) => (array addresses); + DiscoverAssetsOnAllSupportedChains(); + // Resets the keyring and the related preferences Reset(); }; diff --git a/components/brave_wallet_ui/common/wallet_api_proxy.ts b/components/brave_wallet_ui/common/wallet_api_proxy.ts index 617bb689df5..a3d6cff07bf 100644 --- a/components/brave_wallet_ui/common/wallet_api_proxy.ts +++ b/components/brave_wallet_ui/common/wallet_api_proxy.ts @@ -63,6 +63,9 @@ export class WalletApiProxy { accountsChanged: function () { store.dispatch(WalletActions.accountsChanged()) }, + accountsAdded: function () { + // TODO: Handle this event. + }, autoLockMinutesChanged: function () { store.dispatch(WalletActions.autoLockMinutesChanged()) }, @@ -115,6 +118,9 @@ export class WalletApiProxy { }, onNetworkListChanged: function () { store.dispatch(WalletActions.getAllNetworks()) + }, + onDiscoverAssetsCompleted: function (discoveredAssets) { + // TODO: Handle this event. } }) this.braveWalletService.addObserver(braveWalletServiceObserverReceiver.$.bindNewPipeAndPassRemote())