From 7ca151374fd5be54d508369e5699c39cfad6111c Mon Sep 17 00:00:00 2001 From: oisupov Date: Mon, 2 Oct 2023 18:12:52 +0700 Subject: [PATCH] Implement ZCash account creation & balance resolution Resolves https://github.com/brave/brave-browser/issues/32305 --- browser/brave_wallet/BUILD.gn | 2 + .../zcash_wallet_service_factory.cc | 93 ++++++++ .../zcash_wallet_service_factory.h | 54 +++++ ...browser_context_keyed_service_factories.cc | 6 + .../common_handler/wallet_handler.cc | 4 +- .../ui/webui/brave_wallet/wallet_page_ui.cc | 5 + .../ui/webui/brave_wallet/wallet_page_ui.h | 2 + .../ui/webui/brave_wallet/wallet_panel_ui.cc | 2 + .../ui/webui/brave_wallet/wallet_panel_ui.h | 2 + components/brave_wallet/browser/BUILD.gn | 10 + .../browser/brave_wallet_constants.h | 5 + .../browser/brave_wallet_utils.cc | 95 ++++++++ .../brave_wallet/browser/keyring_service.cc | 88 +++++++- .../brave_wallet/browser/keyring_service.h | 7 + .../zcash/protos/zcash_grpc_data.proto | 24 ++ .../brave_wallet/browser/zcash/zcash_rpc.cc | 207 ++++++++++++++++++ .../brave_wallet/browser/zcash/zcash_rpc.h | 56 +++++ .../browser/zcash/zcash_wallet_service.cc | 133 +++++++++++ .../browser/zcash/zcash_wallet_service.h | 59 +++++ .../brave_wallet/common/brave_wallet.mojom | 13 ++ .../brave_wallet/common/common_utils.cc | 28 +++ components/brave_wallet/common/common_utils.h | 3 + components/brave_wallet/common/switches.cc | 2 + components/brave_wallet/common/switches.h | 6 + .../common/async/base-query-cache.ts | 3 +- .../common/hooks/use-balances-fetcher.tsx | 1 + .../common/selectors/wallet-selectors.ts | 1 + .../common/slices/api.slice.ts | 25 ++- .../common/slices/wallet.slice.ts | 2 + .../common/wallet_api_proxy.ts | 1 + .../add-hardware-account-modal.tsx | 3 +- .../add-imported-account-modal.tsx | 3 +- .../create-account-modal.tsx | 31 ++- components/brave_wallet_ui/constants/types.ts | 19 +- .../brave_wallet_ui/options/asset-options.ts | 3 + .../options/create-account-options.ts | 9 + .../page/wallet_page_api_proxy.ts | 1 + .../panel/wallet_panel_api_proxy.ts | 1 + components/brave_wallet_ui/stories/locale.ts | 1 + .../stories/mock-data/asset-icons/index.ts | 2 + .../mock-data/asset-icons/zec-asset-icon.svg | 1 + .../stories/mock-data/mock-wallet-state.ts | 1 + .../brave_wallet_ui/utils/account-utils.ts | 12 + components/brave_wallet_ui/utils/api-utils.ts | 3 +- components/resources/wallet_strings.grdp | 2 + 45 files changed, 1016 insertions(+), 15 deletions(-) create mode 100644 browser/brave_wallet/zcash_wallet_service_factory.cc create mode 100644 browser/brave_wallet/zcash_wallet_service_factory.h create mode 100644 components/brave_wallet/browser/zcash/protos/zcash_grpc_data.proto create mode 100644 components/brave_wallet/browser/zcash/zcash_rpc.cc create mode 100644 components/brave_wallet/browser/zcash/zcash_rpc.h create mode 100644 components/brave_wallet/browser/zcash/zcash_wallet_service.cc create mode 100644 components/brave_wallet/browser/zcash/zcash_wallet_service.h create mode 100644 components/brave_wallet_ui/stories/mock-data/asset-icons/zec-asset-icon.svg diff --git a/browser/brave_wallet/BUILD.gn b/browser/brave_wallet/BUILD.gn index df0907262b5da..c4910b3a8fdec 100644 --- a/browser/brave_wallet/BUILD.gn +++ b/browser/brave_wallet/BUILD.gn @@ -32,6 +32,8 @@ source_set("brave_wallet") { "swap_service_factory.h", "tx_service_factory.cc", "tx_service_factory.h", + "zcash_wallet_service_factory.cc", + "zcash_wallet_service_factory.h", ] if (enable_ipfs_local_node) { sources += [ diff --git a/browser/brave_wallet/zcash_wallet_service_factory.cc b/browser/brave_wallet/zcash_wallet_service_factory.cc new file mode 100644 index 0000000000000..043e77dc93e9a --- /dev/null +++ b/browser/brave_wallet/zcash_wallet_service_factory.cc @@ -0,0 +1,93 @@ +/* Copyright (c) 2023 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 https://mozilla.org/MPL/2.0/. */ + +#include "brave/browser/brave_wallet/zcash_wallet_service_factory.h" + +#include + +#include "base/no_destructor.h" +#include "brave/browser/brave_wallet/brave_wallet_context_utils.h" +#include "brave/browser/brave_wallet/keyring_service_factory.h" +#include "brave/components/brave_wallet/browser/zcash/zcash_wallet_service.h" +#include "brave/components/brave_wallet/common/common_utils.h" +#include "chrome/browser/profiles/incognito_helpers.h" +#include "components/keyed_service/content/browser_context_dependency_manager.h" +#include "components/user_prefs/user_prefs.h" +#include "content/public/browser/storage_partition.h" +#include "services/network/public/cpp/shared_url_loader_factory.h" + +namespace brave_wallet { + +// static +ZCashWalletServiceFactory* ZCashWalletServiceFactory::GetInstance() { + static base::NoDestructor instance; + return instance.get(); +} + +// static +mojo::PendingRemote +ZCashWalletServiceFactory::GetForContext(content::BrowserContext* context) { + if (!IsAllowedForContext(context)) { + return mojo::PendingRemote(); + } + + if (!IsZCashEnabled()) { + return mojo::PendingRemote(); + } + + return static_cast( + GetInstance()->GetServiceForBrowserContext(context, true)) + ->MakeRemote(); +} + +// static +ZCashWalletService* ZCashWalletServiceFactory::GetServiceForContext( + content::BrowserContext* context) { + if (!IsAllowedForContext(context)) { + return nullptr; + } + if (!IsZCashEnabled()) { + return nullptr; + } + return static_cast( + GetInstance()->GetServiceForBrowserContext(context, true)); +} + +// static +void ZCashWalletServiceFactory::BindForContext( + content::BrowserContext* context, + mojo::PendingReceiver receiver) { + auto* zcash_service = + ZCashWalletServiceFactory::GetServiceForContext(context); + if (zcash_service) { + zcash_service->Bind(std::move(receiver)); + } +} + +ZCashWalletServiceFactory::ZCashWalletServiceFactory() + : BrowserContextKeyedServiceFactory( + "ZCashWalletService", + BrowserContextDependencyManager::GetInstance()) { + DependsOn(KeyringServiceFactory::GetInstance()); +} + +ZCashWalletServiceFactory::~ZCashWalletServiceFactory() = default; + +KeyedService* ZCashWalletServiceFactory::BuildServiceInstanceFor( + content::BrowserContext* context) const { + auto* default_storage_partition = context->GetDefaultStoragePartition(); + auto shared_url_loader_factory = + default_storage_partition->GetURLLoaderFactoryForBrowserProcess(); + return new ZCashWalletService( + KeyringServiceFactory::GetServiceForContext(context), + user_prefs::UserPrefs::Get(context), shared_url_loader_factory); +} + +content::BrowserContext* ZCashWalletServiceFactory::GetBrowserContextToUse( + content::BrowserContext* context) const { + return chrome::GetBrowserContextRedirectedInIncognito(context); +} + +} // namespace brave_wallet diff --git a/browser/brave_wallet/zcash_wallet_service_factory.h b/browser/brave_wallet/zcash_wallet_service_factory.h new file mode 100644 index 0000000000000..bb180912204f7 --- /dev/null +++ b/browser/brave_wallet/zcash_wallet_service_factory.h @@ -0,0 +1,54 @@ +/* Copyright (c) 2023 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 https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_BROWSER_BRAVE_WALLET_ZCASH_WALLET_SERVICE_FACTORY_H_ +#define BRAVE_BROWSER_BRAVE_WALLET_ZCASH_WALLET_SERVICE_FACTORY_H_ + +#include "brave/components/brave_wallet/common/brave_wallet.mojom.h" +#include "components/keyed_service/content/browser_context_keyed_service_factory.h" +#include "components/keyed_service/core/keyed_service.h" +#include "content/public/browser/browser_context.h" +#include "mojo/public/cpp/bindings/pending_receiver.h" +#include "mojo/public/cpp/bindings/pending_remote.h" + +namespace base { +template +class NoDestructor; +} // namespace base + +namespace brave_wallet { + +class ZCashWalletService; + +class ZCashWalletServiceFactory : public BrowserContextKeyedServiceFactory { + public: + ZCashWalletServiceFactory(const ZCashWalletServiceFactory&) = delete; + ZCashWalletServiceFactory& operator=(const ZCashWalletServiceFactory&) = + delete; + + static mojo::PendingRemote GetForContext( + content::BrowserContext* context); + static ZCashWalletService* GetServiceForContext( + content::BrowserContext* context); + static ZCashWalletServiceFactory* GetInstance(); + static void BindForContext( + content::BrowserContext* context, + mojo::PendingReceiver receiver); + + private: + friend base::NoDestructor; + + ZCashWalletServiceFactory(); + ~ZCashWalletServiceFactory() override; + + KeyedService* BuildServiceInstanceFor( + content::BrowserContext* context) const override; + content::BrowserContext* GetBrowserContextToUse( + content::BrowserContext* context) const override; +}; + +} // namespace brave_wallet + +#endif // BRAVE_BROWSER_BRAVE_WALLET_ZCASH_WALLET_SERVICE_FACTORY_H_ diff --git a/browser/browser_context_keyed_service_factories.cc b/browser/browser_context_keyed_service_factories.cc index adb60fd9ead39..f468b3082af00 100644 --- a/browser/browser_context_keyed_service_factories.cc +++ b/browser/browser_context_keyed_service_factories.cc @@ -21,6 +21,7 @@ #include "brave/browser/brave_wallet/simulation_service_factory.h" #include "brave/browser/brave_wallet/swap_service_factory.h" #include "brave/browser/brave_wallet/tx_service_factory.h" +#include "brave/browser/brave_wallet/zcash_wallet_service_factory.h" #include "brave/browser/debounce/debounce_service_factory.h" #include "brave/browser/ephemeral_storage/ephemeral_storage_service_factory.h" #include "brave/browser/ethereum_remote_client/buildflags/buildflags.h" @@ -35,6 +36,7 @@ #include "brave/components/ai_chat/common/buildflags/buildflags.h" #include "brave/components/brave_perf_predictor/browser/named_third_party_registry_factory.h" #include "brave/components/brave_vpn/common/buildflags/buildflags.h" +#include "brave/components/brave_wallet/common/common_utils.h" #include "brave/components/commander/common/buildflags/buildflags.h" #include "brave/components/greaselion/browser/buildflags/buildflags.h" #include "brave/components/ipfs/buildflags/buildflags.h" @@ -150,6 +152,10 @@ void EnsureBrowserContextKeyedServiceFactoriesBuilt() { brave_wallet::TxServiceFactory::GetInstance(); brave_wallet::BraveWalletServiceFactory::GetInstance(); + if (brave_wallet::IsZCashEnabled()) { + brave_wallet::ZCashWalletServiceFactory::GetInstance(); + } + #if !BUILDFLAG(IS_ANDROID) if (base::FeatureList::IsEnabled(commands::features::kBraveCommands)) { commands::AcceleratorServiceFactory::GetInstance(); diff --git a/browser/ui/webui/brave_wallet/common_handler/wallet_handler.cc b/browser/ui/webui/brave_wallet/common_handler/wallet_handler.cc index 8399b25b5adcf..1f8fc1b3478c3 100644 --- a/browser/ui/webui/brave_wallet/common_handler/wallet_handler.cc +++ b/browser/ui/webui/brave_wallet/common_handler/wallet_handler.cc @@ -39,8 +39,8 @@ void WalletHandler::GetWalletInfo(GetWalletInfoCallback callback) { std::move(callback).Run(mojom::WalletInfo::New( default_keyring->is_keyring_created, default_keyring->is_locked, keyring_service_->IsWalletBackedUpSync(), IsFilecoinEnabled(), - IsSolanaEnabled(), IsBitcoinEnabled(), IsNftPinningEnabled(), - IsPanelV2Enabled())); + IsSolanaEnabled(), IsBitcoinEnabled(), IsZCashEnabled(), + IsNftPinningEnabled(), IsPanelV2Enabled())); } } // namespace brave_wallet diff --git a/browser/ui/webui/brave_wallet/wallet_page_ui.cc b/browser/ui/webui/brave_wallet/wallet_page_ui.cc index 24d1cd8cfea4b..94f7a5f029cf5 100644 --- a/browser/ui/webui/brave_wallet/wallet_page_ui.cc +++ b/browser/ui/webui/brave_wallet/wallet_page_ui.cc @@ -18,6 +18,7 @@ #include "brave/browser/brave_wallet/keyring_service_factory.h" #include "brave/browser/brave_wallet/swap_service_factory.h" #include "brave/browser/brave_wallet/tx_service_factory.h" +#include "brave/browser/brave_wallet/zcash_wallet_service_factory.h" #include "brave/browser/ui/webui/brave_wallet/wallet_common_ui.h" #include "brave/browser/ui/webui/navigation_bar_data_provider.h" #include "brave/components/brave_wallet/browser/asset_ratio_service.h" @@ -102,6 +103,8 @@ void WalletPageUI::CreatePageHandler( json_rpc_service_receiver, mojo::PendingReceiver bitcoin_rpc_service_receiver, + mojo::PendingReceiver + zcash_service_receiver, mojo::PendingReceiver swap_service_receiver, mojo::PendingReceiver @@ -140,6 +143,8 @@ void WalletPageUI::CreatePageHandler( profile, std::move(json_rpc_service_receiver)); brave_wallet::BitcoinWalletServiceFactory::BindForContext( profile, std::move(bitcoin_rpc_service_receiver)); + brave_wallet::ZCashWalletServiceFactory::BindForContext( + profile, std::move(zcash_service_receiver)); brave_wallet::SwapServiceFactory::BindForContext( profile, std::move(swap_service_receiver)); brave_wallet::AssetRatioServiceFactory::BindForContext( diff --git a/browser/ui/webui/brave_wallet/wallet_page_ui.h b/browser/ui/webui/brave_wallet/wallet_page_ui.h index a94be6180ff6d..327fdc26ae99e 100644 --- a/browser/ui/webui/brave_wallet/wallet_page_ui.h +++ b/browser/ui/webui/brave_wallet/wallet_page_ui.h @@ -43,6 +43,8 @@ class WalletPageUI : public ui::MojoWebUIController, json_rpc_service, mojo::PendingReceiver bitcoin_rpc_service, + mojo::PendingReceiver + zcash_service, mojo::PendingReceiver swap_service, mojo::PendingReceiver asset_ratio_service, diff --git a/browser/ui/webui/brave_wallet/wallet_panel_ui.cc b/browser/ui/webui/brave_wallet/wallet_panel_ui.cc index 7d60cb5534a5a..b0cb516cb6379 100644 --- a/browser/ui/webui/brave_wallet/wallet_panel_ui.cc +++ b/browser/ui/webui/brave_wallet/wallet_panel_ui.cc @@ -121,6 +121,8 @@ void WalletPanelUI::CreatePanelHandler( json_rpc_service_receiver, mojo::PendingReceiver bitcoin_rpc_service_receiver, + mojo::PendingReceiver + zcash_rpc_service_receiver, mojo::PendingReceiver swap_service_receiver, mojo::PendingReceiver diff --git a/browser/ui/webui/brave_wallet/wallet_panel_ui.h b/browser/ui/webui/brave_wallet/wallet_panel_ui.h index 57c24710b8be0..99011549d9ab4 100644 --- a/browser/ui/webui/brave_wallet/wallet_panel_ui.h +++ b/browser/ui/webui/brave_wallet/wallet_panel_ui.h @@ -52,6 +52,8 @@ class WalletPanelUI : public ui::MojoBubbleWebUIController, json_rpc_service, mojo::PendingReceiver bitcoin_rpc_service, + mojo::PendingReceiver + zcash_service, mojo::PendingReceiver swap_service, mojo::PendingReceiver simulation_service, diff --git a/components/brave_wallet/browser/BUILD.gn b/components/brave_wallet/browser/BUILD.gn index 08d9a207bf7cd..8d26842943e78 100644 --- a/components/brave_wallet/browser/BUILD.gn +++ b/components/brave_wallet/browser/BUILD.gn @@ -6,6 +6,7 @@ import("//brave/build/config.gni") import("//brave/components/ipfs/buildflags/buildflags.gni") import("//build/config/features.gni") +import("//third_party/protobuf/proto_library.gni") import("//tools/json_schema_compiler/json_schema_api.gni") declare_args() { @@ -204,6 +205,10 @@ static_library("browser") { "unstoppable_domains_multichain_calls.h", "wallet_data_files_installer.cc", "wallet_data_files_installer.h", + "zcash/zcash_rpc.cc", + "zcash/zcash_rpc.h", + "zcash/zcash_wallet_service.cc", + "zcash/zcash_wallet_service.h", ] if (enable_ipfs_local_node) { sources += [ @@ -224,6 +229,7 @@ static_library("browser") { ":pref_names", ":transaction", ":utils", + ":zcash_proto", "//base", "//brave/components/brave_component_updater/browser", "//brave/components/brave_stats/browser", @@ -470,6 +476,10 @@ generated_types("generated_swap_responses") { ] } +proto_library("zcash_proto") { + sources = [ "zcash/protos/zcash_grpc_data.proto" ] +} + config("sardine_config") { defines = [ "SARDINE_CLIENT_ID=\"$sardine_client_id\"", diff --git a/components/brave_wallet/browser/brave_wallet_constants.h b/components/brave_wallet/browser/brave_wallet_constants.h index ab771c0791b71..67061a677a44b 100644 --- a/components/brave_wallet/browser/brave_wallet_constants.h +++ b/components/brave_wallet/browser/brave_wallet_constants.h @@ -322,6 +322,8 @@ constexpr webui::LocalizedString kLocalizedStrings[] = { IDS_BRAVE_WALLET_CREATE_ACCOUNT_FILECOIN_DESCRIPTION}, {"braveWalletCreateAccountBitcoinDescription", IDS_BRAVE_WALLET_CREATE_ACCOUNT_BITCOIN_DESCRIPTION}, + {"braveWalletCreateAccountZCashDescription", + IDS_BRAVE_WALLET_CREATE_ACCOUNT_ZCASH_DESCRIPTION}, {"braveWalletFilecoinPrivateKeyProtocol", IDS_BRAVE_WALLET_FILECOIN_PRIVATE_KEY_PROTOCOL}, {"braveWalletAddAccountImport", IDS_BRAVE_WALLET_ADD_ACCOUNT_IMPORT}, @@ -894,6 +896,8 @@ constexpr webui::LocalizedString kLocalizedStrings[] = { IDS_BRAVE_WALLET_FIL_ACCOUNT_DESCRIPTION}, {"braveWalletBTCAccountDescrption", IDS_BRAVE_WALLET_BTC_ACCOUNT_DESCRIPTION}, + {"braveWalletZECAccountDescrption", + IDS_BRAVE_WALLET_ZEC_ACCOUNT_DESCRIPTION}, {"braveWalletShowNetworkLogoOnNftsTitle", IDS_BRAVE_WALLET_SHOW_NETWORK_LOGO_ON_NFTS_TITLE}, {"braveWalletShowNetworkLogoOnNftsDescription", @@ -1398,6 +1402,7 @@ constexpr char kERC721InterfaceId[] = "0x80ac58cd"; constexpr char kERC721MetadataInterfaceId[] = "0x5b5e139f"; constexpr char kBitcoinPrefKey[] = "bitcoin"; +constexpr char kZCashPrefKey[] = "zcash"; constexpr char kEthereumPrefKey[] = "ethereum"; constexpr char kFilecoinPrefKey[] = "filecoin"; constexpr char kSolanaPrefKey[] = "solana"; diff --git a/components/brave_wallet/browser/brave_wallet_utils.cc b/components/brave_wallet/browser/brave_wallet_utils.cc index 38c8cf5ade8bd..49bb1c25c031e 100644 --- a/components/brave_wallet/browser/brave_wallet_utils.cc +++ b/components/brave_wallet/browser/brave_wallet_utils.cc @@ -524,6 +524,26 @@ GURL BitcoinTestnetRpcUrl() { return GURL("https://blockstream.info/testnet/api/"); } +GURL ZCashMainnetRpcUrl() { + auto switch_url = + GURL(base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + switches::kZCashMainnetRpcUrl)); + if (switch_url.is_valid()) { + return switch_url; + } + return GURL("https://mainnet.lightwalletd.com:9067/"); +} + +GURL ZCashTestnetRpcUrl() { + auto switch_url = + GURL(base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + switches::kZCashTestnetRpcUrl)); + if (switch_url.is_valid()) { + return switch_url; + } + return GURL("https://testnet.lightwalletd.com:9067/"); +} + const mojom::NetworkInfo* GetBitcoinMainnet() { const auto coin = mojom::CoinType::BTC; const auto* chain_id = mojom::kBitcoinMainnet; @@ -564,6 +584,56 @@ const mojom::NetworkInfo* GetBitcoinTestnet() { return network_info.get(); } +const mojom::NetworkInfo* GetZCashMainnet() { + const auto coin = mojom::CoinType::ZEC; + const auto* chain_id = mojom::kZCashMainnet; + + static base::NoDestructor network_info( + {chain_id, + "ZCash Mainnet", + {""}, // TODO(cypt4): explorer url + {}, + 0, + {ZCashMainnetRpcUrl()}, + "ZEC", + "ZCash", + 8, + coin, + GetSupportedKeyringsForNetwork(coin, chain_id), + false}); + return network_info.get(); +} + +const mojom::NetworkInfo* GetZCashTestnet() { + const auto coin = mojom::CoinType::ZEC; + const auto* chain_id = mojom::kZCashTestnet; + + static base::NoDestructor network_info( + {chain_id, + "ZCash Testnet", + {""}, // TODO(cypt4): explorer url + {}, + 0, + {ZCashTestnetRpcUrl()}, + "ZEC", + "ZCash", + 8, + coin, + GetSupportedKeyringsForNetwork(coin, chain_id), + false}); + return network_info.get(); +} + +const std::vector& GetKnownZCashNetworks() { + static base::NoDestructor> networks({ + // clang-format off + GetZCashMainnet(), + GetZCashTestnet(), + // clang-format on + }); + return *networks.get(); +} + const std::vector& GetKnownBitcoinNetworks() { static base::NoDestructor> networks({ // clang-format off @@ -732,6 +802,14 @@ mojom::NetworkInfoPtr GetKnownChain(PrefService* prefs, } return nullptr; } + if (coin == mojom::CoinType::ZEC) { + for (const auto* network : GetKnownZCashNetworks()) { + if (base::EqualsCaseInsensitiveASCII(network->chain_id, chain_id)) { + return network->Clone(); + } + } + return nullptr; + } NOTREACHED(); return nullptr; } @@ -871,6 +949,12 @@ bool KnownChainExists(const std::string& chain_id, mojom::CoinType coin) { return true; } } + } else if (coin == mojom::CoinType::ZEC) { + for (const auto* network : GetKnownZCashNetworks()) { + if (base::CompareCaseInsensitiveASCII(network->chain_id, chain_id) == 0) { + return true; + } + } } else { NOTREACHED() << coin; } @@ -1265,6 +1349,13 @@ std::vector GetAllKnownChains(PrefService* prefs, return result; } + if (coin == mojom::CoinType::ZEC) { + for (const auto* network : GetKnownZCashNetworks()) { + result.push_back(network->Clone()); + } + return result; + } + NOTREACHED(); return result; } @@ -1720,6 +1811,8 @@ std::string GetPrefKeyForCoinType(mojom::CoinType coin) { switch (coin) { case mojom::CoinType::BTC: return kBitcoinPrefKey; + case mojom::CoinType::ZEC: + return kZCashPrefKey; case mojom::CoinType::ETH: return kEthereumPrefKey; case mojom::CoinType::FIL: @@ -1740,6 +1833,8 @@ absl::optional GetCoinTypeFromPrefKey(const std::string& key) { return mojom::CoinType::SOL; } else if (key == kBitcoinPrefKey) { return mojom::CoinType::BTC; + } else if (key == kZCashPrefKey) { + return mojom::CoinType::ZEC; } NOTREACHED(); return absl::nullopt; diff --git a/components/brave_wallet/browser/keyring_service.cc b/components/brave_wallet/browser/keyring_service.cc index 594f8e02e2014..bf02378c14e39 100644 --- a/components/brave_wallet/browser/keyring_service.cc +++ b/components/brave_wallet/browser/keyring_service.cc @@ -541,8 +541,9 @@ mojom::AccountInfoPtr MakeAccountInfoForDerivedAccount( const DerivedAccountInfo& derived_account_info, mojom::KeyringId keyring_id) { // Ignore address from prefs for bitcoin. - auto address = - IsBitcoinKeyring(keyring_id) ? "" : derived_account_info.account_address; + auto address = IsBitcoinKeyring(keyring_id) || IsZCashKeyring(keyring_id) + ? "" + : derived_account_info.account_address; return mojom::AccountInfo::New( MakeAccountIdForDerivedAccount(derived_account_info, keyring_id), @@ -792,6 +793,9 @@ void KeyringService::MaybeMigrateSelectedAccountPrefs() { case mojom::CoinType::FIL: wallet_selected = fil_selected.Clone(); break; + case mojom::CoinType::ZEC: + NOTREACHED(); + break; case mojom::CoinType::BTC: NOTREACHED(); break; @@ -1070,6 +1074,11 @@ void KeyringService::CreateWallet(const std::string& mnemonic, CreateKeyring(mojom::kBitcoinKeyring84TestId, mnemonic, password); } + if (IsZCashEnabled()) { + CreateKeyring(mojom::KeyringId::kZCashMainnet, mnemonic, password); + CreateKeyring(mojom::KeyringId::kZCashTestnet, mnemonic, password); + } + ResetAllAccountInfosCache(); std::move(callback).Run(mnemonic); } @@ -1118,6 +1127,11 @@ bool KeyringService::RestoreWalletSync(const std::string& mnemonic, RestoreKeyring(mojom::kBitcoinKeyring84TestId, mnemonic, password, false); } + if (IsZCashEnabled()) { + RestoreKeyring(mojom::KeyringId::kZCashMainnet, mnemonic, password, false); + RestoreKeyring(mojom::KeyringId::kZCashTestnet, mnemonic, password, false); + } + ResetAllAccountInfosCache(); if (keyring) { @@ -1188,10 +1202,16 @@ mojom::AccountInfoPtr KeyringService::AddAccountSync( } } + if (IsZCashKeyring(keyring_id)) { + if (!IsZCashEnabled()) { + return nullptr; + } + } auto* keyring = GetHDKeyringById(keyring_id); if (!keyring) { return nullptr; } + auto account = AddAccountForKeyring(keyring_id, account_name); if (!account) { return nullptr; @@ -1387,6 +1407,15 @@ BitcoinKeyring* KeyringService::GetBitcoinKeyringById( return static_cast(GetHDKeyringById(keyring_id)); } +ZCashKeyring* KeyringService::GetZCashKeyringById( + mojom::KeyringId keyring_id) const { + if (!IsZCashKeyring(keyring_id)) { + return nullptr; + } + + return static_cast(GetHDKeyringById(keyring_id)); +} + void KeyringService::SetSelectedAccountInternal( const mojom::AccountInfo& account_info) { SetSelectedWalletAccountInternal(account_info); @@ -1916,6 +1945,11 @@ void KeyringService::Unlock(const std::string& password, ResumeKeyring(mojom::kBitcoinKeyring84TestId, password); } + if (IsZCashEnabled()) { + ResumeKeyring(mojom::KeyringId::kZCashMainnet, password); + ResumeKeyring(mojom::KeyringId::kZCashTestnet, password); + } + UpdateLastUnlockPref(local_state_); request_unlock_pending_ = false; for (const auto& observer : observers_) { @@ -2520,6 +2554,56 @@ void KeyringService::HasPendingUnlockRequest( std::move(callback).Run(HasPendingUnlockRequest()); } +absl::optional>> +KeyringService::GetZCashAddresses(const mojom::AccountId& account_id) { + CHECK(IsZCashAccount(account_id)); + + auto* zcash_keyring = GetZCashKeyringById(account_id.keyring_id); + if (!zcash_keyring) { + return absl::nullopt; + } + + // TODO(cypt4): store used addresses indexes in prefs. + + // TODO(cypt4): temporarily just return first 5 recieve and 5 change + // addresses. + std::vector> addresses; + for (auto i = 0; i < 5; ++i) { + auto key_id = + mojom::ZCashKeyId::New(account_id.bitcoin_account_index, 0, i); + auto address = zcash_keyring->GetAddress(*key_id); + if (!address) { + return absl::nullopt; + } + addresses.emplace_back(*address, std::move(key_id)); + } + for (auto i = 0; i < 5; ++i) { + auto key_id = + mojom::ZCashKeyId::New(account_id.bitcoin_account_index, 1, i); + auto address = zcash_keyring->GetAddress(*key_id); + if (!address) { + return absl::nullopt; + } + addresses.emplace_back(*address, std::move(key_id)); + } + + return addresses; +} + +absl::optional KeyringService::GetZCashAddress( + const mojom::AccountId& account_id, + const mojom::ZCashKeyId& key_id) { + CHECK(IsZCashAccount(account_id)); + CHECK_EQ(account_id.bitcoin_account_index, key_id.account); + + auto* zcash_keyring = GetZCashKeyringById(account_id.keyring_id); + if (!zcash_keyring) { + return absl::nullopt; + } + + return zcash_keyring->GetAddress(key_id); +} + absl::optional>> KeyringService::GetBitcoinAddresses(const mojom::AccountId& account_id) { CHECK(IsBitcoinAccount(account_id)); diff --git a/components/brave_wallet/browser/keyring_service.h b/components/brave_wallet/browser/keyring_service.h index ed880bd7077cc..a6c0e23e7a11c 100644 --- a/components/brave_wallet/browser/keyring_service.h +++ b/components/brave_wallet/browser/keyring_service.h @@ -43,6 +43,7 @@ class SolanaProviderImplUnitTest; class FilecoinKeyring; class JsonRpcService; class AssetDiscoveryManagerUnitTest; +class ZCashKeyring; // This class is not thread-safe and should have single owner class KeyringService : public KeyedService, public mojom::KeyringService { @@ -220,6 +221,11 @@ class KeyringService : public KeyedService, public mojom::KeyringService { absl::optional>> GetBitcoinAddresses(const mojom::AccountId& account_id); + absl::optional>> + GetZCashAddresses(const mojom::AccountId& account_id); + absl::optional GetZCashAddress( + const mojom::AccountId& account_id, + const mojom::ZCashKeyId& key_id); absl::optional GetBitcoinAddress( const mojom::AccountId& account_id, const mojom::BitcoinKeyId& key_id); @@ -310,6 +316,7 @@ class KeyringService : public KeyedService, public mojom::KeyringService { void OnAutoLockFired(); HDKeyring* GetHDKeyringById(mojom::KeyringId keyring_id) const; BitcoinKeyring* GetBitcoinKeyringById(mojom::KeyringId keyring_id) const; + ZCashKeyring* GetZCashKeyringById(mojom::KeyringId keyring_id) const; std::vector GetHardwareAccountsSync( mojom::KeyringId keyring_id) const; // Address will be returned when success diff --git a/components/brave_wallet/browser/zcash/protos/zcash_grpc_data.proto b/components/brave_wallet/browser/zcash/protos/zcash_grpc_data.proto new file mode 100644 index 0000000000000..b0e83ae2e4f14 --- /dev/null +++ b/components/brave_wallet/browser/zcash/protos/zcash_grpc_data.proto @@ -0,0 +1,24 @@ +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +package zcash; + +message GetAddressUtxosRequest { + repeated string addresses = 1; + uint64 startHeight = 2; + uint32 maxEntries = 3; // zero means unlimited +} + +message ZCashUtxo { + string address = 6; + bytes txid = 1; + int32 index = 2; + bytes script = 3; + int64 valueZat = 4; + uint64 height = 5; +} + +message GetAddressUtxosResponse { + repeated ZCashUtxo addressUtxos = 1; +} diff --git a/components/brave_wallet/browser/zcash/zcash_rpc.cc b/components/brave_wallet/browser/zcash/zcash_rpc.cc new file mode 100644 index 0000000000000..2ad265f4d77f9 --- /dev/null +++ b/components/brave_wallet/browser/zcash/zcash_rpc.cc @@ -0,0 +1,207 @@ +// Copyright (c) 2023 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 https://mozilla.org/MPL/2.0/. + +#include "brave/components/brave_wallet/browser/zcash/zcash_rpc.h" + +#include "base/big_endian.h" +#include "base/functional/bind.h" +#include "brave/components/brave_wallet/browser/brave_wallet_utils.h" +#include "brave/components/brave_wallet/common/brave_wallet.mojom.h" +#include "net/base/load_flags.h" +#include "net/http/http_request_headers.h" +#include "net/http/http_status_code.h" +#include "services/network/public/cpp/resource_request.h" +#include "services/network/public/mojom/url_loader.mojom.h" + +namespace brave_wallet::zcash_rpc { + +namespace { + +net::NetworkTrafficAnnotationTag GetNetworkTrafficAnnotationTag() { + return net::DefineNetworkTrafficAnnotation("zcash_rpc", R"( + semantics { + sender: "ZCash RPC" + description: + "This service is used to communicate with ZCash Lightwalletd nodes " + "on behalf of the user interacting with the native Brave wallet." + trigger: + "Triggered by uses of the native Brave wallet." + data: + "ZCash JSON RPC response bodies." + destination: WEBSITE + } + policy { + cookies_allowed: NO + setting: + "You can enable or disable this feature on chrome://flags." + policy_exception_justification: + "Not implemented." + } + )"); +} + +bool UrlPathEndsWithSlash(const GURL& base_url) { + auto path_piece = base_url.path_piece(); + return !path_piece.empty() && path_piece.back() == '/'; +} + +const GURL MakeGetAddressUtxosURL(const GURL& base_url) { + if (!base_url.is_valid()) { + return GURL(); + } + if (!UrlPathEndsWithSlash(base_url)) { + return GURL(); + } + + GURL::Replacements replacements; + std::string path = + base::StrCat({base_url.path(), + "cash.z.wallet.sdk.rpc.CompactTxStreamer/GetAddressUtxos"}); + replacements.SetPathStr(path); + + return base_url.ReplaceComponents(replacements); +} + +// Prefixes provided serialized protobuf with compression byte and 4 bytes of +// message size. See +// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md +std::string GetPrefixedProtobuf(const std::string& serialized_proto) { + char compression = 0; // 0 means no compression + char buff[4]; // big-endian 4 bytes of message size + base::WriteBigEndian(buff, serialized_proto.size()); + std::string result; + result.append(&compression, 1); + result.append(buff, 4); + result.append(serialized_proto); + return result; +} + +std::string MakeGetAddressUtxosURLParams( + const std::vector& addresses) { + zcash::GetAddressUtxosRequest request; + for (const auto& address : addresses) { + request.add_addresses(address); + } + request.set_maxentries(1); + request.set_startheight(0); + return GetPrefixedProtobuf(request.SerializeAsString()); +} + +std::unique_ptr MakeGRPCLoader( + const GURL& url, + const std::string& body) { + auto request = std::make_unique(); + request->url = url; + + request->load_flags = net::LOAD_DO_NOT_SAVE_COOKIES | net::LOAD_DISABLE_CACHE; + request->credentials_mode = network::mojom::CredentialsMode::kOmit; + request->method = "POST"; + + auto url_loader = network::SimpleURLLoader::Create( + std::move(request), GetNetworkTrafficAnnotationTag()); + url_loader->AttachStringForUpload(body, "application/grpc+proto"); + + url_loader->SetRetryOptions( + 5, network::SimpleURLLoader::RetryMode::RETRY_ON_NETWORK_CHANGE); + url_loader->SetAllowHttpErrorResults(true); + return url_loader; +} + +// Resolves serialized protobuf from length-prefixed string +absl::optional ResolveSerializedMessage( + const std::string& grpc_response_body) { + if (grpc_response_body.size() < 5) { + return absl::nullopt; + } + if (grpc_response_body.c_str()[0] != 0) { + // Compression is not supported yet + return absl::nullopt; + } + uint32_t size = 0; + + uint8_t as_bytes[4]; + const char* size_start = &(grpc_response_body.c_str()[1]); + for (int i = 0; i < 4; i++) { + as_bytes[i] = static_cast(size_start[i]); + } + base::ReadBigEndian(as_bytes, &size); + + if (grpc_response_body.size() != size + 5) { + return absl::nullopt; + } + + return grpc_response_body.substr(5, grpc_response_body.size() - 5); +} + +} // namespace + +ZCashRpc::ZCashRpc( + PrefService* prefs, + scoped_refptr url_loader_factory) + : prefs_(prefs), url_loader_factory_(url_loader_factory) {} + +ZCashRpc::~ZCashRpc() = default; + +void ZCashRpc::GetUtxoList(const std::string& chain_id, + const std::vector& addresses, + ZCashRpc::GetUtxoListCallback callback) { + GURL request_url = MakeGetAddressUtxosURL( + GetNetworkURL(prefs_, chain_id, mojom::CoinType::ZEC)); + + if (!request_url.is_valid()) { + std::move(callback).Run(base::unexpected("Request URL is invalid.")); + return; + } + + auto url_loader = + MakeGRPCLoader(request_url, MakeGetAddressUtxosURLParams(addresses)); + + UrlLoadersList::iterator it = url_loaders_list_.insert( + url_loaders_list_.begin(), std::move(url_loader)); + + (*it)->DownloadToString( + url_loader_factory_.get(), + base::BindOnce(&ZCashRpc::OnGetUtxosResponse, + weak_ptr_factory_.GetWeakPtr(), std::move(callback), it), + 5000); +} + +void ZCashRpc::OnGetUtxosResponse( + ZCashRpc::GetUtxoListCallback callback, + UrlLoadersList::iterator it, + const std::unique_ptr response_body) { + auto current_loader = std::move(*it); + url_loaders_list_.erase(it); + zcash::GetAddressUtxosResponse response; + if (current_loader->NetError()) { + std::move(callback).Run(base::unexpected("Network error")); + return; + } + + if (!response_body) { + std::move(callback).Run(base::unexpected("Response body is empty")); + return; + } + + auto message = ResolveSerializedMessage(*response_body); + if (!message) { + std::move(callback).Run(base::unexpected("Wrong response format")); + return; + } + + if (!response.ParseFromString(message.value())) { + std::move(callback).Run(base::unexpected("Can't parse response")); + return; + } + + std::vector result; + for (const auto& item : response.addressutxos()) { + result.push_back(item); + } + + std::move(callback).Run(result); +} + +} // namespace brave_wallet::zcash_rpc diff --git a/components/brave_wallet/browser/zcash/zcash_rpc.h b/components/brave_wallet/browser/zcash/zcash_rpc.h new file mode 100644 index 0000000000000..4c2ceb6495b44 --- /dev/null +++ b/components/brave_wallet/browser/zcash/zcash_rpc.h @@ -0,0 +1,56 @@ +/* Copyright (c) 2023 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 https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_RPC_H_ +#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_RPC_H_ + +#include +#include +#include +#include +#include + +#include "base/functional/callback.h" +#include "base/memory/weak_ptr.h" +#include "base/types/expected.h" +#include "brave/components/brave_wallet/browser/zcash/protos/zcash_grpc_data.pb.h" +#include "components/prefs/pref_service.h" +#include "services/network/public/cpp/shared_url_loader_factory.h" +#include "services/network/public/cpp/simple_url_loader.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +namespace brave_wallet::zcash_rpc { + +// lightwalletd interface +class ZCashRpc { + public: + using GetUtxoListCallback = base::OnceCallback, std::string>)>; + + explicit ZCashRpc( + PrefService* prefs, + scoped_refptr url_loader_factory); + ~ZCashRpc(); + + void GetUtxoList(const std::string& chain_id, + const std::vector& addresses, + GetUtxoListCallback callback); + + private: + using UrlLoadersList = std::list>; + + void OnGetUtxosResponse(ZCashRpc::GetUtxoListCallback callback, + UrlLoadersList::iterator it, + const std::unique_ptr response_body); + + UrlLoadersList url_loaders_list_; + raw_ptr prefs_; + scoped_refptr url_loader_factory_; + base::WeakPtrFactory weak_ptr_factory_{this}; +}; + +} // namespace brave_wallet::zcash_rpc + +#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_RPC_H_ diff --git a/components/brave_wallet/browser/zcash/zcash_wallet_service.cc b/components/brave_wallet/browser/zcash/zcash_wallet_service.cc new file mode 100644 index 0000000000000..5d1259fef4a8a --- /dev/null +++ b/components/brave_wallet/browser/zcash/zcash_wallet_service.cc @@ -0,0 +1,133 @@ +/* Copyright (c) 2023 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 https://mozilla.org/MPL/2.0/. */ + +#include "brave/components/brave_wallet/browser/zcash/zcash_wallet_service.h" + +#include +#include +#include + +#include "brave/components/brave_wallet/common/common_utils.h" +#include "services/network/public/cpp/shared_url_loader_factory.h" + +namespace brave_wallet { + +class GetTransparentBalanceContext + : public base::RefCountedThreadSafe { + public: + using GetBalanceCallback = mojom::ZCashWalletService::GetBalanceCallback; + + std::set addresses; + std::map balances; + absl::optional error; + GetBalanceCallback callback; + + bool ShouldRespond() { return callback && (error || addresses.empty()); } + + void SetError(const std::string& error_string) { error = error_string; } + + protected: + friend class base::RefCountedThreadSafe; + virtual ~GetTransparentBalanceContext() = default; +}; + +ZCashWalletService::ZCashWalletService( + KeyringService* keyring_service, + PrefService* prefs, + scoped_refptr url_loader_factory) + : keyring_service_(keyring_service), + zcash_rpc_( + std::make_unique(prefs, url_loader_factory)) { + zcash_rpc_ = std::make_unique(prefs, url_loader_factory); +} + +mojo::PendingRemote +ZCashWalletService::MakeRemote() { + mojo::PendingRemote remote; + receivers_.Add(this, remote.InitWithNewPipeAndPassReceiver()); + return remote; +} + +void ZCashWalletService::Bind( + mojo::PendingReceiver receiver) { + receivers_.Add(this, std::move(receiver)); +} + +ZCashWalletService::~ZCashWalletService() = default; + +void ZCashWalletService::GetBalance(const std::string& chain_id, + mojom::AccountIdPtr account_id, + GetBalanceCallback callback) { + if (!IsZCashNetwork(chain_id)) { + // Desktop frontend sometimes does that. + std::move(callback).Run(nullptr, "Invalid bitcoin chain id " + chain_id); + return; + } + + const auto& addresses = keyring_service_->GetZCashAddresses(*account_id); + if (!addresses) { + std::move(callback).Run(nullptr, "Couldn't get balance"); + return; + } + + auto context = base::MakeRefCounted(); + context->callback = std::move(callback); + for (const auto& address : addresses.value()) { + context->addresses.insert(address.first); + } + + for (const auto& address : context->addresses) { + zcash_rpc_->GetUtxoList( + chain_id, {address}, + base::BindOnce(&ZCashWalletService::OnGetUtxosForBalance, + weak_ptr_factory_.GetWeakPtr(), context, address)); + } +} + +void ZCashWalletService::OnGetUtxosForBalance( + scoped_refptr context, + const std::string& address, + base::expected, std::string> result) { + DCHECK(context->addresses.contains(address)); + DCHECK(!context->balances.contains(address)); + + if (!result.has_value()) { + context->SetError(result.error()); + WorkOnGetBalance(std::move(context)); + return; + } + + uint64_t total_amount = 0; + for (const auto& output : result.value()) { + total_amount += output.valuezat(); + } + + context->addresses.erase(address); + context->balances[address] = total_amount; + + WorkOnGetBalance(std::move(context)); +} + +void ZCashWalletService::WorkOnGetBalance( + scoped_refptr context) { + if (!context->ShouldRespond()) { + return; + } + + if (context->error) { + std::move(context->callback).Run(nullptr, std::move(*context->error)); + return; + } + + auto result = mojom::ZCashBalance::New(); + for (auto& balance : context->balances) { + result->total_balance += balance.second; + } + + result->balances.insert(context->balances.begin(), context->balances.end()); + std::move(context->callback).Run(std::move(result), absl::nullopt); +} + +} // namespace brave_wallet diff --git a/components/brave_wallet/browser/zcash/zcash_wallet_service.h b/components/brave_wallet/browser/zcash/zcash_wallet_service.h new file mode 100644 index 0000000000000..fa443eb660fe5 --- /dev/null +++ b/components/brave_wallet/browser/zcash/zcash_wallet_service.h @@ -0,0 +1,59 @@ +/* Copyright (c) 2023 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 https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_WALLET_SERVICE_H_ +#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_WALLET_SERVICE_H_ + +#include +#include +#include + +#include "base/types/expected.h" +#include "brave/components/brave_wallet/browser/keyring_service.h" +#include "brave/components/brave_wallet/browser/keyring_service_observer_base.h" +#include "brave/components/brave_wallet/browser/zcash/protos/zcash_grpc_data.pb.h" +#include "brave/components/brave_wallet/browser/zcash/zcash_rpc.h" +#include "brave/components/brave_wallet/browser/zcash_rpc_responses.h" +#include "brave/components/brave_wallet/common/brave_wallet.mojom.h" +#include "components/keyed_service/core/keyed_service.h" + +namespace brave_wallet { + +class GetTransparentBalanceContext; + +class ZCashWalletService : public KeyedService, + public mojom::ZCashWalletService, + KeyringServiceObserverBase { + public: + ZCashWalletService( + KeyringService* keyring_service, + PrefService* prefs, + scoped_refptr url_loader_factory); + ~ZCashWalletService() override; + + mojo::PendingRemote MakeRemote(); + void Bind(mojo::PendingReceiver receiver); + + void GetBalance(const std::string& chain_id, + mojom::AccountIdPtr account_id, + GetBalanceCallback) override; + + private: + void OnGetUtxosForBalance( + scoped_refptr context, + const std::string& current_address, + base::expected, std::string> result); + void WorkOnGetBalance(scoped_refptr context); + + raw_ptr keyring_service_; + scoped_refptr url_loader_factory_; + mojo::ReceiverSet receivers_; + std::unique_ptr zcash_rpc_; + base::WeakPtrFactory weak_ptr_factory_{this}; +}; + +} // namespace brave_wallet + +#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_ZCASH_ZCASH_WALLET_SERVICE_H_ diff --git a/components/brave_wallet/common/brave_wallet.mojom b/components/brave_wallet/common/brave_wallet.mojom index 2444c44f51777..3b973d222f635 100644 --- a/components/brave_wallet/common/brave_wallet.mojom +++ b/components/brave_wallet/common/brave_wallet.mojom @@ -337,6 +337,7 @@ interface PanelHandlerFactory { pending_receiver wallet_handler, pending_receiver json_rpc_service, pending_receiver bitcoin_wallet_service, + pending_receiver zcash_wallet_service, pending_receiver swap_service, pending_receiver simulation_service, pending_receiver asset_ratio_service, @@ -363,6 +364,7 @@ interface PageHandlerFactory { pending_receiver wallet_handler, pending_receiver json_rpc_service, pending_receiver bitcoin_wallet_service, + pending_receiver zcash_wallet_service, pending_receiver swap_service, pending_receiver asset_ratio_service, pending_receiver keyring_service, @@ -714,6 +716,7 @@ struct WalletInfo { bool is_filecoin_enabled; bool is_solana_enabled; bool is_bitcoin_enabled; + bool is_z_cash_enabled; bool is_nft_pinning_feature_enabled; bool is_panel_v2_feature_enabled; }; @@ -1110,6 +1113,7 @@ const string kBaseMainnetChainId = "0x2105"; // https://github.com/satoshilabs/slips/blob/master/slip-0044.md enum CoinType { BTC = 0, + ZEC = 133, ETH = 60, FIL = 461, SOL = 501 @@ -1386,6 +1390,11 @@ struct BitcoinBalance { map balances; // address -> balance of that address(sum of all related utxos) }; +struct ZCashBalance { + uint64 total_balance; + map balances; // address -> balance of that address(sum of all related utxos) +}; + interface BitcoinWalletService { // Fetches BitcoinBalance struct for all addresses associated with account_id. // Now includes both confirmed and mempool utxos. @@ -1394,6 +1403,10 @@ interface BitcoinWalletService { SendTo(string network_id, AccountId account_id, string address_to, uint64 amount, uint64 fee) => (string txid, string error); }; +interface ZCashWalletService { + GetBalance(string network_id, AccountId account_id) => (ZCashBalance? balance, string? error_message); +}; + enum TransactionStatus { Unapproved = 0, Approved = 1, diff --git a/components/brave_wallet/common/common_utils.cc b/components/brave_wallet/common/common_utils.cc index f2e683c3549a4..fba12147b67c3 100644 --- a/components/brave_wallet/common/common_utils.cc +++ b/components/brave_wallet/common/common_utils.cc @@ -84,6 +84,11 @@ bool IsBitcoinKeyring(mojom::KeyringId keyring_id) { keyring_id == mojom::KeyringId::kBitcoin84Testnet; } +bool IsZCashKeyring(mojom::KeyringId keyring_id) { + return keyring_id == mojom::KeyringId::kZCashMainnet || + keyring_id == mojom::KeyringId::kZCashTestnet; +} + bool IsBitcoinMainnetKeyring(mojom::KeyringId keyring_id) { return keyring_id == mojom::KeyringId::kBitcoin84; } @@ -97,6 +102,11 @@ bool IsBitcoinNetwork(const std::string& network_id) { network_id == mojom::kBitcoinTestnet; } +bool IsZCashNetwork(const std::string& network_id) { + return network_id == mojom::kZCashMainnet || + network_id == mojom::kZCashTestnet; +} + bool IsValidBitcoinNetworkKeyringPair(const std::string& network_id, mojom::KeyringId keyring_id) { if (!IsBitcoinKeyring(keyring_id) || !IsBitcoinNetwork(network_id)) { @@ -118,6 +128,12 @@ bool IsBitcoinAccount(const mojom::AccountId& account_id) { account_id.kind == mojom::AccountKind::kDerived; } +bool IsZCashAccount(const mojom::AccountId& account_id) { + return account_id.coin == mojom::CoinType::ZEC && + IsZCashKeyring(account_id.keyring_id) && + account_id.kind == mojom::AccountKind::kDerived; +} + mojom::KeyringId GetFilecoinKeyringId(const std::string& network) { if (network == mojom::kFilecoinMainnet) { return mojom::KeyringId::kFilecoin; @@ -146,6 +162,8 @@ mojom::CoinType GetCoinForKeyring(mojom::KeyringId keyring_id) { return mojom::CoinType::SOL; } else if (IsBitcoinKeyring(keyring_id)) { return mojom::CoinType::BTC; + } else if (IsZCashKeyring(keyring_id)) { + return mojom::CoinType::ZEC; } DCHECK_EQ(keyring_id, mojom::KeyringId::kDefault); @@ -174,6 +192,10 @@ std::vector GetSupportedKeyrings() { ids.push_back(mojom::KeyringId::kBitcoin84); ids.push_back(mojom::KeyringId::kBitcoin84Testnet); } + if (IsZCashEnabled()) { + ids.push_back(mojom::KeyringId::kZCashMainnet); + ids.push_back(mojom::KeyringId::kZCashTestnet); + } DCHECK_GT(ids.size(), 0u); return ids; @@ -204,6 +226,12 @@ std::vector GetSupportedKeyringsForNetwork( } else { return {mojom::KeyringId::kBitcoin84Testnet}; } + case mojom::CoinType::ZEC: + if (chain_id == mojom::kZCashMainnet) { + return {mojom::KeyringId::kZCashMainnet}; + } else { + return {mojom::KeyringId::kZCashTestnet}; + } default: NOTREACHED_NORETURN(); } diff --git a/components/brave_wallet/common/common_utils.h b/components/brave_wallet/common/common_utils.h index cc5796204f568..9c5aad3834d62 100644 --- a/components/brave_wallet/common/common_utils.h +++ b/components/brave_wallet/common/common_utils.h @@ -37,12 +37,15 @@ bool IsAllowed(PrefService* prefs); bool IsFilecoinKeyringId(mojom::KeyringId keyring_id); bool IsBitcoinKeyring(mojom::KeyringId keyring_id); +bool IsZCashKeyring(mojom::KeyringId keyring_id); bool IsBitcoinMainnetKeyring(mojom::KeyringId keyring_id); bool IsBitcoinTestnetKeyring(mojom::KeyringId keyring_id); bool IsBitcoinNetwork(const std::string& network_id); +bool IsZCashNetwork(const std::string& network_id); bool IsValidBitcoinNetworkKeyringPair(const std::string& network_id, mojom::KeyringId keyring_id); bool IsBitcoinAccount(const mojom::AccountId& account_id); +bool IsZCashAccount(const mojom::AccountId& account_id); mojom::KeyringId GetFilecoinKeyringId(const std::string& network); diff --git a/components/brave_wallet/common/switches.cc b/components/brave_wallet/common/switches.cc index a3c88fa8f00eb..54aa27dcc0f97 100644 --- a/components/brave_wallet/common/switches.cc +++ b/components/brave_wallet/common/switches.cc @@ -12,6 +12,8 @@ const char kDevWalletPassword[] = "dev-wallet-password"; const char kBitcoinMainnetRpcUrl[] = "bitcoin-mainnet-rpc-url"; const char kBitcoinTestnetRpcUrl[] = "bitcoin-testnet-rpc-url"; const char kAssetRatioDevUrl[] = "asset-ratio-dev-url"; +const char kZCashMainnetRpcUrl[] = "zcash-mainnet-rpc-url"; +const char kZCashTestnetRpcUrl[] = "zcash-testnet-rpc-url"; } // namespace switches } // namespace brave_wallet diff --git a/components/brave_wallet/common/switches.h b/components/brave_wallet/common/switches.h index ea19ea281ca88..e96f13ca30d55 100644 --- a/components/brave_wallet/common/switches.h +++ b/components/brave_wallet/common/switches.h @@ -21,6 +21,12 @@ extern const char kBitcoinTestnetRpcUrl[]; // Ratios service dev URL extern const char kAssetRatioDevUrl[]; +// ZCash rpc mainnet endpoint. +extern const char kZCashMainnetRpcUrl[]; + +// ZCash rpc testnet endpoint. +extern const char kZCashTestnetRpcUrl[]; + } // namespace switches } // namespace brave_wallet diff --git a/components/brave_wallet_ui/common/async/base-query-cache.ts b/components/brave_wallet_ui/common/async/base-query-cache.ts index 8744aa28deb49..1a54a22eff2d7 100644 --- a/components/brave_wallet_ui/common/async/base-query-cache.ts +++ b/components/brave_wallet_ui/common/async/base-query-cache.ts @@ -126,7 +126,7 @@ export class BaseQueryCache { const { jsonRpcService } = apiProxyFetcher() // network type flags - const { isFilecoinEnabled, isSolanaEnabled, isBitcoinEnabled } = + const { isFilecoinEnabled, isSolanaEnabled, isBitcoinEnabled, isZCashEnabled } = await this.getWalletInfo() // Get all networks @@ -136,6 +136,7 @@ export class BaseQueryCache { (coin === BraveWallet.CoinType.FIL && isFilecoinEnabled) || (coin === BraveWallet.CoinType.SOL && isSolanaEnabled) || (coin === BraveWallet.CoinType.BTC && isBitcoinEnabled) || + (coin === BraveWallet.CoinType.ZEC && isZCashEnabled) || coin === BraveWallet.CoinType.ETH ) }) diff --git a/components/brave_wallet_ui/common/hooks/use-balances-fetcher.tsx b/components/brave_wallet_ui/common/hooks/use-balances-fetcher.tsx index 7b43d6804d74a..8a18c47951b82 100644 --- a/components/brave_wallet_ui/common/hooks/use-balances-fetcher.tsx +++ b/components/brave_wallet_ui/common/hooks/use-balances-fetcher.tsx @@ -27,6 +27,7 @@ const coinTypesMapping = { [BraveWallet.CoinType.ETH]: CoinTypes.ETH, [BraveWallet.CoinType.FIL]: CoinTypes.FIL, [BraveWallet.CoinType.BTC]: CoinTypes.BTC, + [BraveWallet.CoinType.ZEC]: CoinTypes.ZEC, } export const useBalancesFetcher = (arg: Arg | typeof skipToken) => { diff --git a/components/brave_wallet_ui/common/selectors/wallet-selectors.ts b/components/brave_wallet_ui/common/selectors/wallet-selectors.ts index 80ce861199fed..c9a9dd5337446 100644 --- a/components/brave_wallet_ui/common/selectors/wallet-selectors.ts +++ b/components/brave_wallet_ui/common/selectors/wallet-selectors.ts @@ -18,6 +18,7 @@ export const hasInitialized = ({ wallet }: State) => wallet.hasInitialized export const isFilecoinEnabled = ({ wallet }: State) => wallet.isFilecoinEnabled export const isSolanaEnabled = ({ wallet }: State) => wallet.isSolanaEnabled export const isBitcoinEnabled = ({ wallet }: State) => wallet.isBitcoinEnabled +export const isZCashEnabled = ({ wallet }: State) => wallet.isZCashEnabled export const isLoadingCoinMarketData = ({ wallet }: State) => wallet.isLoadingCoinMarketData export const isMetaMaskInstalled = ({ wallet }: State) => wallet.isMetaMaskInstalled export const isWalletBackedUp = ({ wallet }: State) => wallet.isWalletBackedUp diff --git a/components/brave_wallet_ui/common/slices/api.slice.ts b/components/brave_wallet_ui/common/slices/api.slice.ts index 726aa4716e08c..c0ef48a03e724 100644 --- a/components/brave_wallet_ui/common/slices/api.slice.ts +++ b/components/brave_wallet_ui/common/slices/api.slice.ts @@ -139,6 +139,7 @@ type GetTokenBalancesForAddressAndChainIdArg = { | typeof CoinTypes.ETH | typeof CoinTypes.FIL | typeof CoinTypes.BTC + | typeof CoinTypes.ZEC } type GetTokenBalancesForChainIdArg = | GetSPLTokenBalancesForAddressAndChainIdArg @@ -152,6 +153,7 @@ type GetTokenBalancesRegistryArg = { | typeof CoinTypes.SOL | typeof CoinTypes.FIL | typeof CoinTypes.BTC + | typeof CoinTypes.ZEC } type GetHardwareAccountDiscoveryBalanceArg = { @@ -836,7 +838,7 @@ export function createWalletApi () { extraOptions, baseQuery ) => { - const { jsonRpcService, bitcoinWalletService } = + const { jsonRpcService, bitcoinWalletService, zcashWalletService } = baseQuery(undefined).data // apiProxy // Native asset balances @@ -926,6 +928,27 @@ export function createWalletApi () { data: Amount.normalize(balance.totalBalance.toString()) } } + + case BraveWallet.CoinType.ZEC: { + const { balance, errorMessage } = + await zcashWalletService.getBalance( + token.chainId, + accountId + ) + + + if (errorMessage || balance === null) { + console.log(`getBalance error: ${errorMessage}`) + return { + error: errorMessage || 'Unknown error' + } + } + + return { + data: Amount.normalize(balance.totalBalance.toString()) + } + } + default: { assertNotReached(`Unknown coin ${accountId.coin}`) } diff --git a/components/brave_wallet_ui/common/slices/wallet.slice.ts b/components/brave_wallet_ui/common/slices/wallet.slice.ts index 4d403a67efdd2..2dfb1b8581387 100644 --- a/components/brave_wallet_ui/common/slices/wallet.slice.ts +++ b/components/brave_wallet_ui/common/slices/wallet.slice.ts @@ -57,6 +57,7 @@ const defaultState: WalletState = { isFilecoinEnabled: false, isSolanaEnabled: false, isBitcoinEnabled: false, + isZCashEnabled: false, isWalletCreated: false, isWalletLocked: true, favoriteApps: [], @@ -285,6 +286,7 @@ export const createWalletSlice = (initialState: WalletState = defaultState) => { state.isFilecoinEnabled = payload.walletInfo.isFilecoinEnabled state.isSolanaEnabled = payload.walletInfo.isSolanaEnabled state.isBitcoinEnabled = payload.walletInfo.isBitcoinEnabled + state.isZCashEnabled = payload.walletInfo.isZCashEnabled state.isWalletLocked = payload.walletInfo.isWalletLocked state.isWalletBackedUp = payload.walletInfo.isWalletBackedUp state.isNftPinningFeatureEnabled = diff --git a/components/brave_wallet_ui/common/wallet_api_proxy.ts b/components/brave_wallet_ui/common/wallet_api_proxy.ts index 5a0c4746e54c9..ba30dddd445a0 100644 --- a/components/brave_wallet_ui/common/wallet_api_proxy.ts +++ b/components/brave_wallet_ui/common/wallet_api_proxy.ts @@ -9,6 +9,7 @@ export class WalletApiProxy { walletHandler = new BraveWallet.WalletHandlerRemote() jsonRpcService = new BraveWallet.JsonRpcServiceRemote() bitcoinWalletService = new BraveWallet.BitcoinWalletServiceRemote() + zcashWalletService = new BraveWallet.ZCashWalletServiceRemote() swapService = new BraveWallet.SwapServiceRemote() simulationService = new BraveWallet.SimulationServiceRemote() assetRatioService = new BraveWallet.AssetRatioServiceRemote() diff --git a/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/add-hardware-account-modal.tsx b/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/add-hardware-account-modal.tsx index f6bee2714f184..3f68ed845a191 100644 --- a/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/add-hardware-account-modal.tsx +++ b/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/add-hardware-account-modal.tsx @@ -63,7 +63,8 @@ export const AddHardwareAccountModal = ({ onSelectAccountType }: Props) => { return CreateAccountOptions({ isFilecoinEnabled, isSolanaEnabled, - isBitcoinEnabled: false // No bitcoin hardware accounts by now. + isBitcoinEnabled: false, // No bitcoin hardware accounts by now. + isZCashEnabled: false // No zcash hardware accounts by now. }) }, [isFilecoinEnabled, isSolanaEnabled]) diff --git a/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/add-imported-account-modal.tsx b/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/add-imported-account-modal.tsx index 32980a200f2ce..4e4f608b89d11 100644 --- a/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/add-imported-account-modal.tsx +++ b/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/add-imported-account-modal.tsx @@ -96,7 +96,8 @@ export const ImportAccountModal = () => { return CreateAccountOptions({ isFilecoinEnabled, isSolanaEnabled, - isBitcoinEnabled: false // No bitcoin imported accounts by now. + isBitcoinEnabled: false, // No bitcoin imported accounts by now. + isZCashEnabled: false // No zcash imported accounts by now. }) }, [isFilecoinEnabled, isSolanaEnabled]) diff --git a/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/create-account-modal.tsx b/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/create-account-modal.tsx index 275bf6f060553..b34134fea6cae 100644 --- a/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/create-account-modal.tsx +++ b/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/create-account-modal.tsx @@ -25,7 +25,10 @@ import { FilecoinNetworkLocaleMapping, FilecoinNetworkTypes, ImportAccountErrorType, - WalletRoutes + WalletRoutes, + ZCashNetwork, + ZCashNetworkLocaleMapping, + ZCashNetworkTypes } from '../../../../constants/types' // actions @@ -69,21 +72,24 @@ export const CreateAccountModal = () => { const isFilecoinEnabled = useSafeWalletSelector(WalletSelectors.isFilecoinEnabled) const isSolanaEnabled = useSafeWalletSelector(WalletSelectors.isSolanaEnabled) const isBitcoinEnabled = useSafeWalletSelector(WalletSelectors.isBitcoinEnabled) + const isZCashEnabled = useSafeWalletSelector(WalletSelectors.isZCashEnabled) const accounts = useUnsafeWalletSelector(WalletSelectors.accounts) // state const [accountName, setAccountName] = React.useState('') const [filecoinNetwork, setFilecoinNetwork] = React.useState(BraveWallet.FILECOIN_MAINNET) const [bitcoinNetwork, setBitcoinNetwork] = React.useState(BraveWallet.BITCOIN_TESTNET) + const [zcashNetwork, setZCashNetwork] = React.useState(BraveWallet.Z_CASH_MAINNET) // memos const createAccountOptions = React.useMemo(() => { return CreateAccountOptions({ isFilecoinEnabled, isSolanaEnabled, - isBitcoinEnabled + isBitcoinEnabled, + isZCashEnabled }) - }, [isFilecoinEnabled, isSolanaEnabled, isBitcoinEnabled]) + }, [isFilecoinEnabled, isSolanaEnabled, isBitcoinEnabled, isZCashEnabled]) const selectedAccountType = React.useMemo(() => { return createAccountOptions.find((option) => { @@ -107,6 +113,8 @@ export const CreateAccountModal = () => { filecoinNetwork) || (selectedAccountType.coin === BraveWallet.CoinType.BTC && bitcoinNetwork) || + (selectedAccountType.coin === BraveWallet.CoinType.ZEC && + zcashNetwork) || undefined return keyringIdForNewAccount(selectedAccountType.coin, network) @@ -135,6 +143,10 @@ export const CreateAccountModal = () => { setBitcoinNetwork(network) }, []) + const onChangeZCashNetwork = React.useCallback((network: ZCashNetwork) => { + setZCashNetwork(network) + }, []) + const onClickCreateAccount = React.useCallback(() => { if (!selectedAccountType) { return @@ -210,6 +222,19 @@ export const CreateAccountModal = () => { } + {selectedAccountType?.coin === BraveWallet.CoinType.ZEC && + + + + } { return isStorybook ? FILECOINIconUrl : 'chrome://erc-token-images/fil.png' case 'BTC': return isStorybook ? BTCIconUrl : 'chrome://erc-token-images/btc.png' + case 'ZEC': + return isStorybook ? ZECIconUrl : 'chrome://erc-token-images/zec.png' } return '' diff --git a/components/brave_wallet_ui/options/create-account-options.ts b/components/brave_wallet_ui/options/create-account-options.ts index 9fb137c925f01..2c7b75cd476a5 100644 --- a/components/brave_wallet_ui/options/create-account-options.ts +++ b/components/brave_wallet_ui/options/create-account-options.ts @@ -10,6 +10,7 @@ export const CreateAccountOptions = (options: { isFilecoinEnabled: boolean isSolanaEnabled: boolean isBitcoinEnabled: boolean + isZCashEnabled: boolean }): CreateAccountOptionsType[] => { let accounts = [ { @@ -43,5 +44,13 @@ export const CreateAccountOptions = (options: { icon: getNetworkLogo(BraveWallet.BITCOIN_MAINNET, 'BTC') }) } + if (options.isZCashEnabled) { + accounts.push({ + description: getLocale('braveWalletCreateAccountZCashDescription'), + name: 'ZCash', + coin: BraveWallet.CoinType.ZEC, + icon: getNetworkLogo(BraveWallet.Z_CASH_MAINNET, 'ZEC') + }) + } return accounts } diff --git a/components/brave_wallet_ui/page/wallet_page_api_proxy.ts b/components/brave_wallet_ui/page/wallet_page_api_proxy.ts index a330ebfffcf85..13d580f8ab7fa 100644 --- a/components/brave_wallet_ui/page/wallet_page_api_proxy.ts +++ b/components/brave_wallet_ui/page/wallet_page_api_proxy.ts @@ -21,6 +21,7 @@ export class WalletPageApiProxy extends WalletApiProxy { this.walletHandler.$.bindNewPipeAndPassReceiver(), this.jsonRpcService.$.bindNewPipeAndPassReceiver(), this.bitcoinWalletService.$.bindNewPipeAndPassReceiver(), + this.zcashWalletService.$.bindNewPipeAndPassReceiver(), this.swapService.$.bindNewPipeAndPassReceiver(), this.assetRatioService.$.bindNewPipeAndPassReceiver(), this.keyringService.$.bindNewPipeAndPassReceiver(), diff --git a/components/brave_wallet_ui/panel/wallet_panel_api_proxy.ts b/components/brave_wallet_ui/panel/wallet_panel_api_proxy.ts index af0d6a82ce676..948659501a8f3 100644 --- a/components/brave_wallet_ui/panel/wallet_panel_api_proxy.ts +++ b/components/brave_wallet_ui/panel/wallet_panel_api_proxy.ts @@ -20,6 +20,7 @@ export class WalletPanelApiProxy extends WalletApiProxy { this.walletHandler.$.bindNewPipeAndPassReceiver(), this.jsonRpcService.$.bindNewPipeAndPassReceiver(), this.bitcoinWalletService.$.bindNewPipeAndPassReceiver(), + this.zcashWalletService.$.bindNewPipeAndPassReceiver(), this.swapService.$.bindNewPipeAndPassReceiver(), this.simulationService.$.bindNewPipeAndPassReceiver(), this.assetRatioService.$.bindNewPipeAndPassReceiver(), diff --git a/components/brave_wallet_ui/stories/locale.ts b/components/brave_wallet_ui/stories/locale.ts index aaa05097c93fb..637d22468d6a3 100644 --- a/components/brave_wallet_ui/stories/locale.ts +++ b/components/brave_wallet_ui/stories/locale.ts @@ -818,6 +818,7 @@ provideStrings({ braveWalletSOLAccountDescrption: 'Solana + SVM Chains', braveWalletFILAccountDescrption: 'Filecoin', braveWalletBTCAccountDescrption: 'Bitcoin', + braveWalletZECAccountDescrption: 'ZCash', braveWalletShowNetworkLogoOnNftsTitle: 'Network Logo', braveWalletShowNetworkLogoOnNftsDescription: 'Show network logo on NFTs', braveWalletShowSpamNftsTitle: 'Spam NFTs', diff --git a/components/brave_wallet_ui/stories/mock-data/asset-icons/index.ts b/components/brave_wallet_ui/stories/mock-data/asset-icons/index.ts index bd7dcae30c257..044975f1700c4 100644 --- a/components/brave_wallet_ui/stories/mock-data/asset-icons/index.ts +++ b/components/brave_wallet_ui/stories/mock-data/asset-icons/index.ts @@ -6,6 +6,7 @@ import BATIconUrl from './bat-asset-icon.svg' import ALGOIconUrl from './algo-asset-icon.svg' import BNBIconUrl from './bnb-asset-icon.png' import BTCIconUrl from './btc-asset-icon.svg' +import ZECIconUrl from './zec-asset-icon.svg' import ETHIconUrl from './eth-asset-icon.svg' import ZRXIconUrl from './zrx-asset-icon.svg' import SOLIconUrl from './sol-asset-icon.svg' @@ -20,6 +21,7 @@ export { BATIconUrl, BNBIconUrl, BTCIconUrl, + ZECIconUrl, ETHIconUrl, FILECOINIconUrl, MATICIconUrl, diff --git a/components/brave_wallet_ui/stories/mock-data/asset-icons/zec-asset-icon.svg b/components/brave_wallet_ui/stories/mock-data/asset-icons/zec-asset-icon.svg new file mode 100644 index 0000000000000..12587d47fdcd1 --- /dev/null +++ b/components/brave_wallet_ui/stories/mock-data/asset-icons/zec-asset-icon.svg @@ -0,0 +1 @@ +z \ No newline at end of file diff --git a/components/brave_wallet_ui/stories/mock-data/mock-wallet-state.ts b/components/brave_wallet_ui/stories/mock-data/mock-wallet-state.ts index 865c40fc7eee1..0384f59af18b9 100644 --- a/components/brave_wallet_ui/stories/mock-data/mock-wallet-state.ts +++ b/components/brave_wallet_ui/stories/mock-data/mock-wallet-state.ts @@ -137,6 +137,7 @@ export const mockWalletState: WalletState = { isMetaMaskInstalled: false, isSolanaEnabled: false, isBitcoinEnabled: false, + isZCashEnabled: false, solFeeEstimates: { fee: 0.000005 * LAMPORTS_PER_SOL as unknown as bigint }, diff --git a/components/brave_wallet_ui/utils/account-utils.ts b/components/brave_wallet_ui/utils/account-utils.ts index a5df25266d20e..0dcc1ddba1683 100644 --- a/components/brave_wallet_ui/utils/account-utils.ts +++ b/components/brave_wallet_ui/utils/account-utils.ts @@ -159,6 +159,16 @@ export const keyringIdForNewAccount = ( } } + if (coin === BraveWallet.CoinType.ZEC) { + if (chainId === BraveWallet.Z_CASH_MAINNET) { + return BraveWallet.KeyringId.kZCashMainnet + } + if (chainId === BraveWallet.Z_CASH_TESTNET) { + return BraveWallet.KeyringId.kZCashTestnet + } + } + + assertNotReached(`Unknown coin ${coin} and chainId ${chainId}`) } @@ -172,6 +182,8 @@ export const getAccountTypeDescription = (coin: BraveWallet.CoinType) => { return getLocale('braveWalletFILAccountDescrption') case BraveWallet.CoinType.BTC: return getLocale('braveWalletBTCAccountDescrption') + case BraveWallet.CoinType.ZEC: + return getLocale('braveWalletZECAccountDescrption') default: assertNotReached(`Unknown coin ${coin}`) } diff --git a/components/brave_wallet_ui/utils/api-utils.ts b/components/brave_wallet_ui/utils/api-utils.ts index fe3bf55fa05bb..cf694bac0970e 100644 --- a/components/brave_wallet_ui/utils/api-utils.ts +++ b/components/brave_wallet_ui/utils/api-utils.ts @@ -55,7 +55,7 @@ export function handleEndpointError( export async function getEnabledCoinTypes(api: WalletApiProxy) { const { - isFilecoinEnabled, isSolanaEnabled, isBitcoinEnabled + isFilecoinEnabled, isSolanaEnabled, isBitcoinEnabled, isZCashEnabled } = (await api.walletHandler.getWalletInfo()).walletInfo // Get All Networks @@ -66,6 +66,7 @@ export async function getEnabledCoinTypes(api: WalletApiProxy) { (coin === BraveWallet.CoinType.FIL && isFilecoinEnabled) || (coin === BraveWallet.CoinType.SOL && isSolanaEnabled) || (coin === BraveWallet.CoinType.BTC && isBitcoinEnabled) || + (coin === BraveWallet.CoinType.ZEC && isZCashEnabled) || coin === BraveWallet.CoinType.ETH ) }) diff --git a/components/resources/wallet_strings.grdp b/components/resources/wallet_strings.grdp index 6008dbe92b226..4cf8e2b7c245e 100644 --- a/components/resources/wallet_strings.grdp +++ b/components/resources/wallet_strings.grdp @@ -213,6 +213,7 @@ Supports SPL compatible assets on the Solana blockchain Store FIL asset Store BTC assets + Store ZEC assets Private key BLS12-381$1 Import Import from hardware wallet @@ -614,6 +615,7 @@ Solana + SVM Chains Filecoin Bitcoin + ZCash Network Logo Show network logo on NFTs Spam NFTs