feat(spl): add desktop Solana token (SPL) detail view
What changed, and why it matters
This commit adds a new desktop screen for viewing Solana token (SPL) details in Stack Wallet. It is purely a user-interface feature with no security-sensitive code, no network calls, no cryptographic operations, and no changes to permissions or data handling. There is nothing in the diff that suggests a security vulnerability or fix.
No security action required. Treat as a normal feature commit. Future TODOs in this file should be reviewed when implemented, especially any transaction-list or send/receive integration.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change introduces a single new Dart file, desktop_sol_token_view.dart, implementing a ConsumerStatefulWidget that displays a Solana token wallet page. It uses existing widgets (DesktopScaffold, DesktopAppBar, DesktopWalletSummary, DesktopWalletFeatures, MyWallet, SolTokenIcon) and Riverpod providers. The file contains only UI layout code, placeholder TODO comments, and hardcoded strings such as ‘Token Name’ and ‘WIP’. No input validation, storage, networking, or crypto logic is present.
Changed components
lib/pages_desktop_specific/my_stack_view/wallet_view/desktop_sol_token_view.dartInspect captured patch +226 / −0
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
new file mode 100644
index 0000000..45204e3
--- /dev/null
+++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/desktop_sol_token_view.dart
@@ -0,0 +1,226 @@
+/*
+ * This file is part of Stack Wallet.
+ *
+ * Copyright (c) 2025 Cypher Stack
+ * All Rights Reserved.
+ * The code is distributed under GPLv3 license, see LICENSE file for details.
+ *
+ */
+
+import 'package:event_bus/event_bus.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_svg/svg.dart';
+
+import '../../../pages/send_view/sub_widgets/transaction_fee_selection_sheet.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/text_styles.dart';
+import '../../../wallets/isar/providers/wallet_info_provider.dart';
+import '../../../widgets/coin_ticker_tag.dart';
+import '../../../widgets/custom_buttons/blue_text_button.dart';
+import '../../../widgets/desktop/desktop_app_bar.dart';
+import '../../../widgets/desktop/desktop_scaffold.dart';
+import '../../../widgets/desktop/secondary_button.dart';
+import '../../../widgets/icon_widgets/sol_token_icon.dart';
+import '../../../widgets/rounded_white_container.dart';
+import 'sub_widgets/desktop_wallet_features.dart';
+import 'sub_widgets/desktop_wallet_summary.dart';
+import 'sub_widgets/my_wallet.dart';
+
+/// [eventBus] should only be set during testing.
+class DesktopSolTokenView extends ConsumerStatefulWidget {
+ const DesktopSolTokenView({
+ super.key,
+ required this.walletId,
+ required this.tokenMint,
+ this.eventBus,
+ });
+
+ static const String routeName = "/desktopSolTokenView";
+
+ final String walletId;
+ final String tokenMint;
+ final EventBus? eventBus;
+
+ @override
+ ConsumerState<DesktopSolTokenView> createState() => _DesktopTokenViewState();
+}
+
+class _DesktopTokenViewState extends ConsumerState<DesktopSolTokenView> {
+ static const double sendReceiveColumnWidth = 460;
+
+ late final WalletSyncStatus initialSyncStatus;
+
+ @override
+ void initState() {
+ // TODO: Integrate Solana token refresh status when available.
+ initialSyncStatus = WalletSyncStatus.synced;
+ super.initState();
+ }
+
+ @override
+ void dispose() {
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ debugPrint("BUILD: $runtimeType");
+
+ return DesktopScaffold(
+ appBar: DesktopAppBar(
+ background: Theme.of(context).extension<StackColors>()!.popupBG,
+ leading: Expanded(
+ flex: 3,
+ child: Row(
+ children: [
+ const SizedBox(width: 32),
+ SecondaryButton(
+ padding: const EdgeInsets.only(left: 12, right: 18),
+ buttonHeight: ButtonHeight.s,
+ label: ref.watch(pWalletName(widget.walletId)),
+ icon: SvgPicture.asset(
+ Assets.svg.arrowLeft,
+ width: 18,
+ height: 18,
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.topNavIconPrimary,
+ ),
+ onPressed: () {
+ ref.refresh(feeSheetSessionCacheProvider);
+ Navigator.of(context).pop();
+ },
+ ),
+ const SizedBox(width: 15),
+ ],
+ ),
+ ),
+ center: Expanded(
+ flex: 4,
+ child: Row(
+ children: [
+ SolTokenIcon(mintAddress: widget.tokenMint, size: 32),
+ const SizedBox(width: 12),
+ Text(
+ "Token Name", // TODO: Replace with actual token name from SplToken.
+ style: STextStyles.desktopH3(context),
+ ),
+ const SizedBox(width: 12),
+ CoinTickerTag(
+ ticker: ref.watch(
+ pWalletCoin(widget.walletId).select((s) => s.ticker),
+ ),
+ ),
+ ],
+ ),
+ ),
+ useSpacers: false,
+ isCompactHeight: true,
+ ),
+ body: Padding(
+ padding: const EdgeInsets.all(24),
+ child: Column(
+ children: [
+ RoundedWhiteContainer(
+ padding: const EdgeInsets.all(20),
+ child: Row(
+ children: [
+ SolTokenIcon(mintAddress: widget.tokenMint, size: 40),
+ const SizedBox(width: 10),
+ DesktopWalletSummary(
+ walletId: widget.walletId,
+ isToken: true,
+ initialSyncStatus:
+ ref
+ .watch(pWallets)
+ .getWallet(widget.walletId)
+ .refreshMutex
+ .isLocked
+ ? WalletSyncStatus.syncing
+ : WalletSyncStatus.synced,
+ ),
+ const Spacer(),
+ DesktopWalletFeatures(walletId: widget.walletId),
+ ],
+ ),
+ ),
+ const SizedBox(height: 24),
+ Row(
+ children: [
+ SizedBox(
+ width: sendReceiveColumnWidth,
+ child: Text(
+ "My wallet",
+ style: STextStyles.desktopTextExtraSmall(context).copyWith(
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.textFieldActiveSearchIconLeft,
+ ),
+ ),
+ ),
+ const SizedBox(width: 16),
+ Expanded(
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(
+ "Recent transactions",
+ style: STextStyles.desktopTextExtraSmall(context)
+ .copyWith(
+ color: Theme.of(context)
+ .extension<StackColors>()!
+ .textFieldActiveSearchIconLeft,
+ ),
+ ),
+ CustomTextButton(
+ text: "See all",
+ onTap: () {
+ // TODO: Navigate to all transactions for this token
+ // Navigator.of(context).pushNamed(
+ // AllTransactionsV2View.routeName,
+ // arguments: (
+ // walletId: widget.walletId,
+ // tokenMint: "TODO_TOKEN_MINT",
+ // ),
+ // );
+ },
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(height: 14),
+ Expanded(
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ SizedBox(
+ width: sendReceiveColumnWidth,
+ child: MyWallet(
+ walletId: widget.walletId,
+ contractAddress: widget.tokenMint,
+ ),
+ ),
+ const SizedBox(width: 16),
+ Expanded(
+ child: Center(
+ child: Text(
+ "WIP", // TODO [prio=high]: Implement.
+ style: STextStyles.itemSubtitle(context),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
Why this scored 15/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.