Skip to content

Commit 179b709

Browse files
committed
add gated Polkadot asset discovery
1 parent fd85296 commit 179b709

13 files changed

Lines changed: 636 additions & 19 deletions

browser/about_flags.cc

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,16 @@ const flags_ui::FeatureEntry::FeatureVariation kZCashFeatureVariations[] = {
155155
{"- Shielded support disabled", kZCashShieldedTransactionsDisabled,
156156
nullptr},
157157
{"- Shielded support enabled", kZCashShieldedTransactionsEnabled, nullptr}};
158+
159+
const flags_ui::FeatureEntry::FeatureParam kPolkadotAssetDiscoveryDisabled[] = {
160+
{"polkadot_asset_discovery", "false"}};
161+
162+
const flags_ui::FeatureEntry::FeatureParam kPolkadotAssetDiscoveryEnabled[] = {
163+
{"polkadot_asset_discovery", "true"}};
164+
165+
const flags_ui::FeatureEntry::FeatureVariation kPolkadotFeatureVariations[] = {
166+
{"- Asset discovery disabled", kPolkadotAssetDiscoveryDisabled, nullptr},
167+
{"- Asset discovery enabled", kPolkadotAssetDiscoveryEnabled, nullptr}};
158168
#endif // BUILDFLAG(ENABLE_BRAVE_WALLET)
159169

160170
#if defined(TOOLKIT_VIEWS)
@@ -237,8 +247,10 @@ const char* const kBraveSyncImplLink[1] = {"https://github.com/brave/go-sync"};
237247
"Enable Brave Wallet Polkadot support", \
238248
"Polkadot support for native Brave Wallet", \
239249
kOsDesktop, \
240-
FEATURE_VALUE_TYPE( \
241-
brave_wallet::features::kBraveWalletPolkadotFeature), \
250+
FEATURE_WITH_PARAMS_VALUE_TYPE( \
251+
brave_wallet::features::kBraveWalletPolkadotFeature, \
252+
kPolkadotFeatureVariations, \
253+
"BraveWalletPolkadot"), \
242254
}, \
243255
{ \
244256
"brave-wallet-bitcoin", \

browser/brave_wallet/asset_discovery_task_unittest.cc

Lines changed: 376 additions & 10 deletions
Large diffs are not rendered by default.

components/brave_wallet/browser/asset_discovery_manager.cc

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include <algorithm>
99

10+
#include "base/containers/extend.h"
1011
#include "base/no_destructor.h"
1112
#include "brave/components/brave_wallet/browser/brave_wallet_constants.h"
1213
#include "brave/components/brave_wallet/browser/brave_wallet_service.h"
@@ -108,6 +109,12 @@ AssetDiscoveryManager::GetFungibleSupportedChains() {
108109
supported_chains.push_back(
109110
mojom::ChainId::New(mojom::CoinType::SOL, mojom::kSolanaMainnet));
110111

112+
if (IsPolkadotAssetDiscoveryEnabled()) {
113+
base::Extend(supported_chains, std::array{mojom::kPolkadotMainnetAssetHub,
114+
mojom::kPolkadotTestnetAssetHub,
115+
mojom::kPolkadotPaseoAssetHub});
116+
}
117+
111118
return supported_chains;
112119
}
113120

@@ -165,11 +172,17 @@ void AssetDiscoveryManager::FinishTask() {
165172

166173
void AssetDiscoveryManager::AccountsAdded(
167174
std::vector<mojom::AccountInfoPtr> added_accounts) {
175+
if (!auto_discovery_enabled_) {
176+
return;
177+
}
178+
168179
std::vector<mojom::AccountIdPtr> accounts;
169180
for (const auto& account : added_accounts) {
170181
auto& account_id = account->account_id;
171182
if (account->account_id->coin == mojom::CoinType::ETH ||
172-
account->account_id->coin == mojom::CoinType::SOL) {
183+
account->account_id->coin == mojom::CoinType::SOL ||
184+
(account->account_id->coin == mojom::CoinType::DOT &&
185+
IsPolkadotAssetDiscoveryEnabled())) {
173186
accounts.push_back(account_id.Clone());
174187
}
175188
}

components/brave_wallet/browser/asset_discovery_manager.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ class AssetDiscoveryManager : public KeyringServiceObserverBase {
6363

6464
size_t GetQueueSizeForTesting() { return queue_.size(); }
6565

66+
// Stops account creation from kicking off a discovery run. Explicit
67+
// DiscoverAssetsOnAllSupportedChains() calls are unaffected.
68+
void SetAutoDiscoveryEnabledForTesting(bool enabled) {
69+
auto_discovery_enabled_ = enabled;
70+
}
71+
6672
private:
6773
friend class AssetDiscoveryManagerUnitTest;
6874
FRIEND_TEST_ALL_PREFIXES(AssetDiscoveryManagerUnitTest,
@@ -83,6 +89,7 @@ class AssetDiscoveryManager : public KeyringServiceObserverBase {
8389
raw_ref<KeyringService> keyring_service_;
8490
raw_ref<SimpleHashClient> simple_hash_client_;
8591
raw_ptr<PrefService> prefs_;
92+
bool auto_discovery_enabled_ = true;
8693
mojo::Receiver<brave_wallet::mojom::KeyringServiceObserver>
8794
keyring_service_observer_receiver_{this};
8895
base::WeakPtrFactory<AssetDiscoveryManager> weak_ptr_factory_;

components/brave_wallet/browser/asset_discovery_task.cc

Lines changed: 176 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include "brave/components/brave_wallet/browser/asset_discovery_task.h"
77

8+
#include <algorithm>
89
#include <map>
910
#include <optional>
1011
#include <string_view>
@@ -14,11 +15,15 @@
1415
#include "base/base64.h"
1516
#include "base/check.h"
1617
#include "base/containers/span_reader.h"
18+
#include "base/strings/string_number_conversions.h"
1719
#include "brave/components/brave_wallet/browser/blockchain_registry.h"
1820
#include "brave/components/brave_wallet/browser/brave_wallet_constants.h"
1921
#include "brave/components/brave_wallet/browser/brave_wallet_service.h"
2022
#include "brave/components/brave_wallet/browser/brave_wallet_utils.h"
2123
#include "brave/components/brave_wallet/browser/json_rpc_service.h"
24+
#include "brave/components/brave_wallet/browser/network_manager.h"
25+
#include "brave/components/brave_wallet/browser/polkadot/polkadot_utils.h"
26+
#include "brave/components/brave_wallet/browser/polkadot/polkadot_wallet_service.h"
2227
#include "brave/components/brave_wallet/browser/pref_names.h"
2328
#include "brave/components/brave_wallet/common/brave_wallet_constants.h"
2429
#include "brave/components/brave_wallet/common/common_utils.h"
@@ -45,6 +50,18 @@ std::pair<std::vector<T>, std::vector<T>> SplitByCoin(
4550
return result;
4651
}
4752

53+
template <class T>
54+
std::vector<T> FilterByCoin(const std::vector<T>& items, mojom::CoinType coin) {
55+
std::vector<T> result;
56+
for (const auto& item : items) {
57+
if (item->coin == coin) {
58+
result.push_back(item.Clone());
59+
}
60+
}
61+
62+
return result;
63+
}
64+
4865
} // namespace
4966

5067
AssetDiscoveryTask::AssetDiscoveryTask(APIRequestHelper& api_request_helper,
@@ -102,12 +119,21 @@ void AssetDiscoveryTask::DiscoverAssets(
102119

103120
bool use_ankr_discovery =
104121
IsAnkrBalancesEnabled() && !ankr_evm_chain_ids.empty();
122+
bool discover_polkadot_assets = IsPolkadotAssetDiscoveryEnabled();
105123

106-
// Concurrently discover ETH ERC20s on our registry, Solana tokens on our
107-
// Registry and NFTs on both platforms, then merge the results
124+
// Concurrently discover ETH ERC20s on our registry, Solana tokens and
125+
// Polkadot assets on our registry, and NFTs on both platforms, then merge
126+
// the results
127+
size_t discovery_count = 3; // SPL tokens, EVM registry tokens and NFTs.
128+
if (use_ankr_discovery) {
129+
++discovery_count;
130+
}
131+
if (discover_polkadot_assets) {
132+
++discovery_count;
133+
}
108134
const auto barrier_callback =
109135
base::BarrierCallback<std::vector<mojom::BlockchainTokenPtr>>(
110-
use_ankr_discovery ? 4 : 3,
136+
discovery_count,
111137
base::BindOnce(&AssetDiscoveryTask::MergeDiscoveredAssets,
112138
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
113139
// Currently SPL tokens are only discovered on Solana Mainnet.
@@ -122,6 +148,13 @@ void AssetDiscoveryTask::DiscoverAssets(
122148
DiscoverERC20sFromRegistry(eth_accounts, evm_chain_ids, barrier_callback);
123149
}
124150

151+
if (discover_polkadot_assets) {
152+
DiscoverPolkadotAssetsFromRegistry(
153+
FilterByCoin(accounts, mojom::CoinType::DOT),
154+
FilterByCoin(fungible_chain_ids, mojom::CoinType::DOT),
155+
barrier_callback);
156+
}
157+
125158
DiscoverNFTs(accounts, non_fungible_chain_ids, barrier_callback);
126159
}
127160

@@ -461,6 +494,146 @@ void AssetDiscoveryTask::OnGetSolanaTokenRegistry(
461494
std::move(callback).Run(std::move(discovered_tokens));
462495
}
463496

497+
void AssetDiscoveryTask::DiscoverPolkadotAssetsFromRegistry(
498+
base::span<const mojom::AccountIdPtr> accounts,
499+
base::span<const mojom::ChainIdPtr> chain_ids,
500+
DiscoverAssetsCompletedCallback callback) {
501+
if (accounts.empty() || chain_ids.empty() ||
502+
!wallet_service_->GetPolkadotWalletService()) {
503+
std::move(callback).Run({});
504+
return;
505+
}
506+
507+
// Polkadot keyrings are chain scoped, so a testnet account has nothing to
508+
// find on mainnet Asset Hub and vice versa.
509+
size_t balance_query_count = 0;
510+
std::vector<std::pair<std::string, std::vector<mojom::AccountIdPtr>>>
511+
accounts_per_chain;
512+
for (const auto& chain_id : chain_ids) {
513+
auto network = wallet_service_->network_manager()->GetChain(
514+
chain_id->chain_id, mojom::CoinType::DOT);
515+
if (!network) {
516+
continue;
517+
}
518+
519+
std::vector<mojom::AccountIdPtr> chain_accounts;
520+
for (const auto& account_id : accounts) {
521+
if (std::ranges::contains(network->supported_keyrings,
522+
account_id->keyring_id)) {
523+
chain_accounts.push_back(account_id.Clone());
524+
}
525+
}
526+
527+
if (chain_accounts.empty()) {
528+
continue;
529+
}
530+
531+
// We performa a balance query per account.
532+
balance_query_count += chain_accounts.size();
533+
accounts_per_chain.emplace_back(chain_id->chain_id,
534+
std::move(chain_accounts));
535+
}
536+
537+
if (balance_query_count == 0) {
538+
std::move(callback).Run({});
539+
return;
540+
}
541+
542+
// Each chain fans out into one balance query per account on that chain.
543+
const auto barrier_callback =
544+
base::BarrierCallback<std::vector<mojom::BlockchainTokenPtr>>(
545+
balance_query_count,
546+
base::BindOnce(&AssetDiscoveryTask::MergeDiscoveredPolkadotAssets,
547+
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
548+
549+
for (auto& [chain_id, chain_accounts] : accounts_per_chain) {
550+
BlockchainRegistry::GetInstance()->GetAllTokens(
551+
chain_id, mojom::CoinType::DOT,
552+
base::BindOnce(&AssetDiscoveryTask::OnGetPolkadotTokenRegistry,
553+
weak_ptr_factory_.GetWeakPtr(), barrier_callback,
554+
std::move(chain_accounts), chain_id));
555+
}
556+
}
557+
558+
void AssetDiscoveryTask::OnGetPolkadotTokenRegistry(
559+
base::RepeatingCallback<void(std::vector<mojom::BlockchainTokenPtr>)>
560+
barrier_callback,
561+
std::vector<mojom::AccountIdPtr> accounts,
562+
const std::string& chain_id,
563+
std::vector<mojom::BlockchainTokenPtr> registry_tokens) {
564+
// The registry carries the pallet_assets asset id as the contract address.
565+
// `candidates` is kept positionally aligned with `asset_ids` so the balances
566+
// can be zipped back onto the tokens they belong to.
567+
std::vector<mojom::BlockchainTokenPtr> candidates;
568+
std::vector<uint32_t> asset_ids;
569+
for (auto& token : registry_tokens) {
570+
uint32_t asset_id = 0;
571+
if (!base::StringToUint(token->contract_address, &asset_id)) {
572+
continue;
573+
}
574+
asset_ids.push_back(asset_id);
575+
candidates.push_back(std::move(token));
576+
}
577+
578+
auto* polkadot_wallet_service = wallet_service_->GetPolkadotWalletService();
579+
if (asset_ids.empty() || !polkadot_wallet_service) {
580+
for (size_t i = 0; i < accounts.size(); ++i) {
581+
barrier_callback.Run({});
582+
}
583+
return;
584+
}
585+
586+
for (const auto& account_id : accounts) {
587+
polkadot_wallet_service->GetAssetAccountBalances(
588+
account_id.Clone(), asset_ids, chain_id,
589+
base::BindOnce(&AssetDiscoveryTask::OnGetPolkadotAssetAccountBalances,
590+
weak_ptr_factory_.GetWeakPtr(), barrier_callback,
591+
CloneVector(candidates)));
592+
}
593+
}
594+
595+
void AssetDiscoveryTask::OnGetPolkadotAssetAccountBalances(
596+
base::OnceCallback<void(std::vector<mojom::BlockchainTokenPtr>)>
597+
barrier_callback,
598+
std::vector<mojom::BlockchainTokenPtr> candidates,
599+
std::vector<mojom::PolkadotAssetAccountInfoPtr> asset_accounts,
600+
const std::optional<std::string>& error_message) {
601+
// Balances are returned positionally zipped with the asset ids we asked for,
602+
// so a size mismatch leaves us unable to tell which asset a balance is for.
603+
if (error_message || asset_accounts.size() != candidates.size()) {
604+
std::move(barrier_callback).Run({});
605+
return;
606+
}
607+
608+
std::vector<mojom::BlockchainTokenPtr> discovered_tokens;
609+
for (size_t i = 0; i < asset_accounts.size(); ++i) {
610+
if (MojomToUint128(asset_accounts[i]->balance) == 0) {
611+
continue;
612+
}
613+
discovered_tokens.push_back(std::move(candidates[i]));
614+
}
615+
616+
std::move(barrier_callback).Run(std::move(discovered_tokens));
617+
}
618+
619+
void AssetDiscoveryTask::MergeDiscoveredPolkadotAssets(
620+
DiscoverAssetsCompletedCallback callback,
621+
const std::vector<std::vector<mojom::BlockchainTokenPtr>>&
622+
discovered_assets) {
623+
std::vector<mojom::BlockchainTokenPtr> discovered_tokens;
624+
for (const auto& tokens : discovered_assets) {
625+
for (const auto& token : tokens) {
626+
// Several accounts can hold the same asset; AddUserAsset deduplicates.
627+
if (!AddUserAsset(prefs_, token.Clone())) {
628+
continue;
629+
}
630+
discovered_tokens.push_back(token.Clone());
631+
}
632+
}
633+
634+
std::move(callback).Run(std::move(discovered_tokens));
635+
}
636+
464637
void AssetDiscoveryTask::DiscoverNFTs(
465638
const std::vector<mojom::AccountIdPtr>& accounts,
466639
const std::vector<mojom::ChainIdPtr>& chain_ids,

components/brave_wallet/browser/asset_discovery_task.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,30 @@ class AssetDiscoveryTask {
120120
const base::flat_set<std::string>& discovered_contract_addresses,
121121
std::vector<mojom::BlockchainTokenPtr> sol_token_registry);
122122

123+
// Polkadot assets live in pallet_assets on the Asset Hub parachains and are
124+
// keyed by a numeric asset id, which the registry carries as the token's
125+
// contract address.
126+
void DiscoverPolkadotAssetsFromRegistry(
127+
base::span<const mojom::AccountIdPtr> accounts,
128+
base::span<const mojom::ChainIdPtr> chain_ids,
129+
DiscoverAssetsCompletedCallback callback);
130+
void OnGetPolkadotTokenRegistry(
131+
base::RepeatingCallback<void(std::vector<mojom::BlockchainTokenPtr>)>
132+
barrier_callback,
133+
std::vector<mojom::AccountIdPtr> accounts,
134+
const std::string& chain_id,
135+
std::vector<mojom::BlockchainTokenPtr> registry_tokens);
136+
void OnGetPolkadotAssetAccountBalances(
137+
base::OnceCallback<void(std::vector<mojom::BlockchainTokenPtr>)>
138+
barrier_callback,
139+
std::vector<mojom::BlockchainTokenPtr> candidates,
140+
std::vector<mojom::PolkadotAssetAccountInfoPtr> asset_accounts,
141+
const std::optional<std::string>& error_message);
142+
void MergeDiscoveredPolkadotAssets(
143+
DiscoverAssetsCompletedCallback callback,
144+
const std::vector<std::vector<mojom::BlockchainTokenPtr>>&
145+
discovered_assets);
146+
123147
void DiscoverNFTs(const std::vector<mojom::AccountIdPtr>& accounts,
124148
const std::vector<mojom::ChainIdPtr>& chain_ids,
125149
DiscoverAssetsCompletedCallback callback);

components/brave_wallet/browser/blockchain_list_parser.cc

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -257,9 +257,12 @@ bool ParseTokenList(const std::string& json, TokenListMap* token_list_map) {
257257
}
258258

259259
// Determine coin type based on chain_id
260-
mojom::CoinType coin = chain_id == mojom::kSolanaMainnet
261-
? mojom::CoinType::SOL
262-
: mojom::CoinType::ETH;
260+
mojom::CoinType coin = mojom::CoinType::ETH;
261+
if (chain_id == mojom::kSolanaMainnet) {
262+
coin = mojom::CoinType::SOL;
263+
} else if (chain_id == mojom::kPolkadotMainnetAssetHub) {
264+
coin = mojom::CoinType::DOT;
265+
}
263266

264267
// Iterate through tokens in this chain
265268
for (const auto token_pair : *tokens_dict) {

components/brave_wallet/browser/brave_wallet_service.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,9 @@ class BraveWalletService : public KeyedService,
370370
AccountDiscoveryManager* account_discovery_manager() {
371371
return account_discovery_manager_.get();
372372
}
373+
AssetDiscoveryManager* asset_discovery_manager() {
374+
return asset_discovery_manager_.get();
375+
}
373376

374377
// Might return nullptr.
375378
BitcoinWalletService* GetBitcoinWalletService();

components/brave_wallet/browser/brave_wallet_utils.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,12 @@ std::optional<std::string> GetUserAssetAddress(const std::string& address,
105105
}
106106
}
107107

108+
if (coin == mojom::CoinType::DOT) {
109+
// Polkadot assets are keyed by a numeric asset id, which acts as the
110+
// contract address.
111+
return address;
112+
}
113+
108114
return std::nullopt;
109115
}
110116

0 commit comments

Comments
 (0)