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>
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
5067AssetDiscoveryTask::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+
464637void AssetDiscoveryTask::DiscoverNFTs (
465638 const std::vector<mojom::AccountIdPtr>& accounts,
466639 const std::vector<mojom::ChainIdPtr>& chain_ids,
0 commit comments