refactor(spl): align DefaultSplTokens usage w/ DefaultTokens
What changed, and why it matters
This commit is a code cleanup that changes how the Stack Wallet app loads Solana token information. Previously, the app looked up tokens in a hard-coded default list and then in the local database. Now it relies primarily on the local database. The change removes duplicated lookup logic and moves wallet initialization to happen when a user selects a token rather than when the token view screen opens. There is no direct evidence in the commit that this fixes a security vulnerability; it appears to be a refactoring to make Solana token handling consistent with how other tokens are handled.
Treat this as a routine refactoring commit. If reviewing for security, verify that the new database-only token lookup path correctly handles unknown or malicious token mint addresses, and that removing the force-unwrap in desktop_wallet_summary.dart does not introduce null-dereference crashes elsewhere. No immediate security patch action is indicated by the diff alone.
Security signals we found
Removal of duplicated token lookup logic between in-memory defaults and database
Centralization of Solana token wallet initialization before navigation
Removal of a force-unwrap (!) on a provider value in desktop_wallet_summary.dart
No explicit security claim, CVE, or advisory language in commit message or diff
Evidence from the diff
The commit refactors Solana SPL token handling to align with the existing DefaultTokens pattern. It removes DefaultSplTokens.list lookups from sol_token_view.dart, desktop_sol_token_view.dart, wallets_overview.dart, and solana_token_contract_details_view.dart. Token wallet initialization is moved from SolTokenView/DesktopSolTokenView.initState into SolTokenSelectItem._onPressed, which now creates a SolanaTokenWallet, calls init(), and refreshes before navigation. The wallets_overview.dart change stops pre-seeding default SPL tokens into the database and only lists tokens already stored there. desktop_wallet_summary.dart removes a non-null assertion on pCurrentSolanaTokenWallet. These are architectural/UX changes; no cryptographic, input-validation, or network-security fixes are visible in the diff.
Changed components
lib/pages/token_view/sol_token_view.dartlib/pages/token_view/solana_token_contract_details_view.dartlib/pages/token_view/sub_widgets/sol_token_select_item.dartlib/pages/wallets_view/wallets_overview.dartlib/pages_desktop_specific/my_stack_view/wallet_view/desktop_sol_token_view.dartlib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dartInspect captured patch +85 / −189
diff --git a/lib/pages/token_view/sol_token_view.dart b/lib/pages/token_view/sol_token_view.dart
index 242a6cd..3253595 100644
--- a/lib/pages/token_view/sol_token_view.dart
+++ b/lib/pages/token_view/sol_token_view.dart
@@ -13,18 +13,13 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:tuple/tuple.dart';
-import '../../models/isar/models/isar_models.dart';
-import '../../providers/db/main_db_provider.dart';
-import '../../providers/providers.dart';
import '../../services/event_bus/events/global/wallet_sync_status_changed_event.dart';
import '../../themes/stack_colors.dart';
import '../../utilities/assets.dart';
import '../../utilities/constants.dart';
-import '../../utilities/default_spl_tokens.dart';
import '../../utilities/text_styles.dart';
import '../../wallets/isar/providers/solana/current_sol_token_wallet_provider.dart';
import '../../wallets/isar/providers/solana/solana_wallet_provider.dart';
-import '../../wallets/wallet/impl/sub_wallets/solana_token_wallet.dart';
import '../../widgets/background.dart';
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
import '../../widgets/custom_buttons/blue_text_button.dart';
@@ -65,75 +60,9 @@ class _SolTokenViewState extends ConsumerState<SolTokenView> {
? WalletSyncStatus.syncing
: WalletSyncStatus.synced;
- // Initialize the Solana token wallet provider with mock data.
- //
- // This sets up the pCurrentSolanaTokenWallet provider so that
- // SolanaTokenSummary can access the token wallet information.
- WidgetsBinding.instance.addPostFrameCallback((_) {
- if (mounted) {
- _initializeSolanaTokenWallet();
- }
- });
-
super.initState();
}
- /// Initialize the Solana token wallet for this token view.
- ///
- /// Creates a SolanaTokenWallet with token data from DefaultSplTokens or the database.
- /// First looks in DefaultSplTokens, then checks the database for custom tokens.
- /// Sets it as the current token wallet in the provider so that UI widgets can access it.
- ///
- /// If the token is not found anywhere, sets the token wallet to null
- /// so the UI can display an error message.
- void _initializeSolanaTokenWallet() {
- SplToken? tokenInfo;
-
- // First try to find in default tokens.
- try {
- tokenInfo = DefaultSplTokens.list.firstWhere(
- (token) => token.address == widget.tokenMint,
- );
- } catch (e) {
- // Token not found in DefaultSplTokens, try database for custom tokens.
- tokenInfo = null;
- }
-
- // If not found in defaults, try database for custom tokens.
- if (tokenInfo == null) {
- try {
- final db = ref.read(mainDBProvider);
- tokenInfo = db.getSplTokenSync(widget.tokenMint);
- } catch (e) {
- tokenInfo = null;
- }
- }
-
- if (tokenInfo == null) {
- ref.read(solanaTokenServiceStateProvider.state).state = null;
- debugPrint(
- 'ERROR: Token not found in DefaultSplTokens or database: ${widget.tokenMint}',
- );
- return;
- }
-
- // Get the parent Solana wallet.
- final parentWallet = ref.read(pSolanaWallet(widget.walletId));
-
- if (parentWallet == null) {
- ref.read(solanaTokenServiceStateProvider.state).state = null;
- debugPrint('ERROR: Wallet is not a SolanaWallet: ${widget.walletId}');
- return;
- }
-
- final solanaTokenWallet = SolanaTokenWallet(parentWallet, tokenInfo);
-
- ref.read(solanaTokenServiceStateProvider.state).state = solanaTokenWallet;
-
- // Fetch the token balance when the wallet is opened.
- solanaTokenWallet.updateBalance();
- }
-
@override
void dispose() {
super.dispose();
diff --git a/lib/pages/token_view/solana_token_contract_details_view.dart b/lib/pages/token_view/solana_token_contract_details_view.dart
index c0b7405..eadc8c6 100644
--- a/lib/pages/token_view/solana_token_contract_details_view.dart
+++ b/lib/pages/token_view/solana_token_contract_details_view.dart
@@ -14,7 +14,6 @@ import 'package:isar_community/isar.dart';
import '../../db/isar/main_db.dart';
import '../../models/isar/models/isar_models.dart';
import '../../themes/stack_colors.dart';
-import '../../utilities/default_spl_tokens.dart';
import '../../utilities/text_styles.dart';
import '../../utilities/util.dart';
import '../../widgets/background.dart';
@@ -48,32 +47,10 @@ class _SolanaTokenContractDetailsViewState
@override
void initState() {
- // Try to find the token in the database first.
- final dbToken = MainDB.instance.isar.splTokens
+ token = MainDB.instance.isar.splTokens
.where()
.addressEqualTo(widget.tokenMint)
- .findFirstSync();
-
- if (dbToken != null) {
- token = dbToken;
- } else {
- // If not in database, try to find it in default tokens.
- try {
- token = DefaultSplTokens.list.firstWhere(
- (t) => t.address == widget.tokenMint,
- );
- } catch (e) {
- // Token not found, create a placeholder.
- //
- // Might want to just throw here instead.
- token = SplToken(
- address: widget.tokenMint,
- name: 'Unknown Token',
- symbol: 'UNKNOWN',
- decimals: 0,
- );
- }
- }
+ .findFirstSync()!;
super.initState();
}
diff --git a/lib/pages/token_view/sub_widgets/sol_token_select_item.dart b/lib/pages/token_view/sub_widgets/sol_token_select_item.dart
index b7772a6..50a5179 100644
--- a/lib/pages/token_view/sub_widgets/sol_token_select_item.dart
+++ b/lib/pages/token_view/sub_widgets/sol_token_select_item.dart
@@ -7,6 +7,8 @@
*
*/
+import 'dart:async';
+
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -15,9 +17,15 @@ import '../../../pages_desktop_specific/my_stack_view/wallet_view/desktop_sol_to
import '../../../providers/providers.dart';
import '../../../themes/stack_colors.dart';
import '../../../utilities/constants.dart';
+import '../../../utilities/show_loading.dart';
import '../../../utilities/text_styles.dart';
import '../../../utilities/util.dart';
+import '../../../wallets/isar/providers/solana/current_sol_token_wallet_provider.dart';
import '../../../wallets/isar/providers/solana/sol_token_balance_provider.dart';
+import '../../../wallets/wallet/impl/solana_wallet.dart';
+import '../../../wallets/wallet/impl/sub_wallets/solana_token_wallet.dart';
+import '../../../widgets/desktop/primary_button.dart';
+import '../../../widgets/dialogs/basic_dialog.dart';
import '../../../widgets/icon_widgets/sol_token_icon.dart';
import '../../../widgets/rounded_white_container.dart';
import '../sol_token_view.dart';
@@ -39,9 +47,80 @@ class SolTokenSelectItem extends ConsumerStatefulWidget {
class _SolTokenSelectItemState extends ConsumerState<SolTokenSelectItem> {
final bool isDesktop = Util.isDesktop;
+ Future<bool> _loadTokenWallet(BuildContext context, WidgetRef ref) async {
+ try {
+ await ref.read(pCurrentSolanaTokenWallet)!.init();
+ return true;
+ } catch (_) {
+ await showDialog<void>(
+ barrierDismissible: false,
+ context: context,
+ builder: (context) => BasicDialog(
+ title: "Failed to load token data",
+ desktopHeight: double.infinity,
+ desktopWidth: 450,
+ rightButton: PrimaryButton(
+ label: "OK",
+ onPressed: () {
+ Navigator.of(context).pop();
+ if (!isDesktop) {
+ Navigator.of(context).pop();
+ }
+ },
+ ),
+ ),
+ );
+ return false;
+ }
+ }
+
void _onPressed() async {
- // TODO [prio=high]: Implement Solana token wallet setup and navigation.
+ final old = ref.read(solanaTokenServiceStateProvider);
+ // exit previous if there is one
+ unawaited(old?.exit());
+
+ // Get the parent Solana wallet.
+ final solanaWallet =
+ ref.read(pWallets).getWallet(widget.walletId) as SolanaWallet?;
+ if (solanaWallet == null) {
+ if (mounted) {
+ await showDialog<void>(
+ barrierDismissible: false,
+ context: context,
+ builder: (context) => BasicDialog(
+ title: "Error: Parent Solana wallet not found",
+ desktopHeight: double.infinity,
+ desktopWidth: 450,
+ rightButton: PrimaryButton(
+ label: "OK",
+ onPressed: () {
+ Navigator.of(context).pop();
+ },
+ ),
+ ),
+ );
+ }
+ return;
+ }
+
+ ref.read(solanaTokenServiceStateProvider.state).state = SolanaTokenWallet(
+ solanaWallet,
+ widget.token,
+ );
+
+ final success = await showLoading<bool>(
+ whileFuture: _loadTokenWallet(context, ref),
+ context: context,
+ rootNavigator: isDesktop,
+ message: "Loading ${widget.token.name}",
+ );
+
+ if (!success!) {
+ return;
+ }
+
if (mounted) {
+ unawaited(ref.read(pCurrentSolanaTokenWallet)!.refresh());
await Navigator.of(context).pushNamed(
isDesktop ? DesktopSolTokenView.routeName : SolTokenView.routeName,
arguments: (
diff --git a/lib/pages/wallets_view/wallets_overview.dart b/lib/pages/wallets_view/wallets_overview.dart
index 3878cfb..675838d 100644
--- a/lib/pages/wallets_view/wallets_overview.dart
+++ b/lib/pages/wallets_view/wallets_overview.dart
@@ -8,8 +8,6 @@
*
*/
-import 'dart:async';
-
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
@@ -26,7 +24,6 @@ import '../../services/event_bus/global_event_bus.dart';
import '../../themes/stack_colors.dart';
import '../../utilities/assets.dart';
import '../../utilities/constants.dart';
-import '../../utilities/default_spl_tokens.dart';
import '../../utilities/text_styles.dart';
import '../../utilities/util.dart';
import '../../wallets/crypto_currency/crypto_currency.dart';
@@ -159,16 +156,6 @@ class _EthWalletsOverviewState extends ConsumerState<WalletsOverview> {
);
}
} else if (widget.coin is Solana) {
- // Ensure default Solana tokens are loaded into database.
- final dbProvider = ref.read(mainDBProvider);
- for (final defaultToken in DefaultSplTokens.list) {
- final existingToken = dbProvider.getSplTokenSync(defaultToken.address);
- if (existingToken == null) {
- // Token not in database, add it asynchronously.
- unawaited(dbProvider.putSplToken(defaultToken));
- }
- }
-
for (final data in walletsData) {
final List<Contract> contracts = [];
final tokenMintAddresses = ref.read(
@@ -177,23 +164,11 @@ class _EthWalletsOverviewState extends ConsumerState<WalletsOverview> {
// fetch each token
for (final tokenAddress in tokenMintAddresses) {
- final token = dbProvider.getSplTokenSync(tokenAddress);
+ final token = ref.read(mainDBProvider).getSplTokenSync(tokenAddress);
- // add it to list if it exists in DB or in default tokens
+ // add it to list if it exists in DB
if (token != null) {
contracts.add(token);
- } else {
- // Try to find in default tokens.
- try {
- final defaultToken = DefaultSplTokens.list.firstWhere(
- (t) => t.address == tokenAddress,
- );
- contracts.add(defaultToken);
- } catch (_) {
- // Token not found anywhere.
- //
- // Might want to throw here or something.
- }
}
}
diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/desktop_sol_token_view.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/desktop_sol_token_view.dart
index ce19784..12dab0b 100644
--- a/lib/pages_desktop_specific/my_stack_view/wallet_view/desktop_sol_token_view.dart
+++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/desktop_sol_token_view.dart
@@ -13,21 +13,16 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:tuple/tuple.dart';
-import '../../../models/isar/models/isar_models.dart';
import '../../../pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart';
import '../../../pages/token_view/solana_token_contract_details_view.dart';
import '../../../pages/token_view/sub_widgets/token_transaction_list_widget_sol.dart';
-import '../../../providers/db/main_db_provider.dart';
-import '../../../providers/providers.dart';
import '../../../services/event_bus/events/global/wallet_sync_status_changed_event.dart';
import '../../../themes/stack_colors.dart';
import '../../../utilities/assets.dart';
-import '../../../utilities/default_spl_tokens.dart';
import '../../../utilities/text_styles.dart';
import '../../../wallets/isar/providers/solana/current_sol_token_wallet_provider.dart';
import '../../../wallets/isar/providers/solana/solana_wallet_provider.dart';
import '../../../wallets/isar/providers/wallet_info_provider.dart';
-import '../../../wallets/wallet/impl/sub_wallets/solana_token_wallet.dart';
import '../../../widgets/coin_ticker_tag.dart';
import '../../../widgets/custom_buttons/blue_text_button.dart';
import '../../../widgets/desktop/desktop_app_bar.dart';
@@ -65,10 +60,6 @@ class _DesktopTokenViewState extends ConsumerState<DesktopSolTokenView> {
@override
void initState() {
- // Initialize the Solana token wallet.
- WidgetsBinding.instance.addPostFrameCallback((_) {
- _initializeSolanaTokenWallet();
- });
// Get the initial sync status from the Solana wallet's refresh mutex.
final solanaWallet = ref.read(pSolanaWallet(widget.walletId));
initialSyncStatus = solanaWallet?.refreshMutex.isLocked ?? false
@@ -77,61 +68,6 @@ class _DesktopTokenViewState extends ConsumerState<DesktopSolTokenView> {
super.initState();
}
- /// Initialize the Solana token wallet.
- ///
- /// Creates a SolanaTokenWallet with token data from DefaultSplTokens or the database.
- /// First looks in DefaultSplTokens, then checks the database for custom tokens.
- /// Sets it as the current token wallet in the provider so that UI widgets can access it.
- ///
- /// If the token is not found anywhere, sets the token wallet to null
- /// so the UI can display an error message.
- void _initializeSolanaTokenWallet() {
- // First try to find in default tokens
- SplToken? tokenInfo;
- try {
- tokenInfo = DefaultSplTokens.list.firstWhere(
- (token) => token.address == widget.tokenMint,
- );
- } catch (e) {
- // Token not found in DefaultSplTokens, try database for custom tokens
- tokenInfo = null;
- }
-
- // If not found in defaults, try database for custom tokens
- if (tokenInfo == null) {
- try {
- final db = ref.read(mainDBProvider);
- tokenInfo = db.getSplTokenSync(widget.tokenMint);
- } catch (e) {
- tokenInfo = null;
- }
- }
-
- if (tokenInfo == null) {
- ref.read(solanaTokenServiceStateProvider.state).state = null;
- debugPrint(
- 'ERROR: Token not found in DefaultSplTokens or database: ${widget.tokenMint}',
- );
- return;
- }
-
- // Get the parent Solana wallet.
- final parentWallet = ref.read(pSolanaWallet(widget.walletId));
-
- if (parentWallet == null) {
- ref.read(solanaTokenServiceStateProvider.state).state = null;
- debugPrint('ERROR: Wallet is not a SolanaWallet: ${widget.walletId}');
- return;
- }
-
- final solanaTokenWallet = SolanaTokenWallet(parentWallet, tokenInfo);
-
- ref.read(solanaTokenServiceStateProvider.state).state = solanaTokenWallet;
-
- // Fetch the token balance when the wallet is opened
- solanaTokenWallet.updateBalance();
- }
-
@override
void dispose() {
super.dispose();
diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart
index 99c671d..b538eb4 100644
--- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart
+++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart
@@ -99,7 +99,7 @@ class _WDesktopWalletSummaryState extends ConsumerState<DesktopWalletSummary> {
tokenContract = null;
// this cannot be null if coin is sol and isToken is true.
// if it is null, then there is a bug somewhere else.
- solanaTokenWallet = ref.watch(pCurrentSolanaTokenWallet)!;
+ solanaTokenWallet = ref.watch(pCurrentSolanaTokenWallet);
break;
default:
Why this scored 26/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.