ui(spl): desktop sol token wallet nav
What changed, and why it matters
This commit adds desktop user-interface support for Solana SPL tokens in Stack Wallet. It lets users view and navigate to Solana token wallets the same way they already could for Ethereum tokens. The changes are almost entirely UI wiring and data-model generalization; there is no obvious security vulnerability in the diff, but the code does add new database lookups and token-loading paths that could later harbor bugs if the underlying token data is malformed or untrusted.
No immediate security action required. As a defensive follow-up, reviewers should verify that DefaultSplTokens.list values are trustworthy and immutable, that getSplTokenSync and putSplToken handle malformed records safely, and that the Solana token wallet initialization path does not bypass existing transaction signing safeguards. Consider surfacing errors from the unawaited default-token seeding instead of silently ignoring them.
Security signals we found
New token-loading code path added for Solana
Fire-and-forget async database write for default token seeding
Null-safety checks added for missing token/contract metadata
No changes to signing, key storage, network transport, or permissions
Evidence from the diff
The patch generalizes the wallet-overview and wallet-card widgets from Ethereum-only ERC-20 contracts to a shared Contract abstraction, then adds a Solana-specific branch. Key changes: (1) WalletsOverview now seeds default SPL tokens into the local Isar database on first access and builds token lists for Solana wallets. (2) A new DesktopExpandingSolanaWalletCard renders Solana token rows. (3) SimpleWalletCard gains _loadSolanaTokenWallet and Solana navigation logic. (4) WalletInfoRowBalance and WalletInfoRow learn to read SPL token metadata and balances. No cryptographic, networking, or permission changes are visible. The only mildly notable pattern is the use of unawaited(dbProvider.putSplToken(defaultToken)) to seed defaults, which is fire-and-forget; a failure there is silently swallowed, but it only affects local UI state, not funds or consensus.
Changed components
lib/pages/wallets_view/wallets_overview.dartlib/pages_desktop_specific/my_stack_view/dialogs/desktop_expanding_solana_wallet_card.dartlib/widgets/wallet_card.dartlib/widgets/wallet_info_row/sub_widgets/wallet_info_row_balance.dartlib/widgets/wallet_info_row/wallet_info_row.dartInspect captured patch +466 / −61
diff --git a/lib/pages/wallets_view/wallets_overview.dart b/lib/pages/wallets_view/wallets_overview.dart
index d0cc2d1..3878cfb 100644
--- a/lib/pages/wallets_view/wallets_overview.dart
+++ b/lib/pages/wallets_view/wallets_overview.dart
@@ -8,6 +8,8 @@
*
*/
+import 'dart:async';
+
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
@@ -15,7 +17,8 @@ import 'package:isar_community/isar.dart';
import '../../app_config.dart';
import '../../models/add_wallet_list_entity/sub_classes/coin_entity.dart';
-import '../../models/isar/models/ethereum/eth_contract.dart';
+import '../../models/isar/models/contract.dart';
+import '../../pages_desktop_specific/my_stack_view/dialogs/desktop_expanding_solana_wallet_card.dart';
import '../../pages_desktop_specific/my_stack_view/dialogs/desktop_expanding_wallet_card.dart';
import '../../providers/providers.dart';
import '../../services/event_bus/events/wallet_added_event.dart';
@@ -23,6 +26,7 @@ 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';
@@ -59,7 +63,7 @@ class WalletsOverview extends ConsumerStatefulWidget {
ConsumerState<WalletsOverview> createState() => _EthWalletsOverviewState();
}
-typedef WalletListItemData = ({Wallet wallet, List<EthContract> contracts});
+typedef WalletListItemData = ({Wallet wallet, List<Contract> contracts});
class _EthWalletsOverviewState extends ConsumerState<WalletsOverview> {
final isDesktop = Util.isDesktop;
@@ -99,15 +103,13 @@ class _EthWalletsOverviewState extends ConsumerState<WalletsOverview> {
term,
);
- final List<EthContract> contracts = [];
+ final List<Contract> contracts = [];
for (final contract in entry.value.contracts) {
if (_elementContains(contract.name, term)) {
contracts.add(contract);
} else if (_elementContains(contract.symbol, term)) {
contracts.add(contract);
- } else if (_elementContains(contract.type.name, term)) {
- contracts.add(contract);
} else if (_elementContains(contract.address, term)) {
contracts.add(contract);
}
@@ -133,7 +135,7 @@ class _EthWalletsOverviewState extends ConsumerState<WalletsOverview> {
if (widget.coin is Ethereum) {
for (final data in walletsData) {
- final List<EthContract> contracts = [];
+ final List<Contract> contracts = [];
final contractAddresses = ref.read(
pWalletTokenAddresses(data.walletId),
);
@@ -150,6 +152,51 @@ class _EthWalletsOverviewState extends ConsumerState<WalletsOverview> {
}
}
+ // add tuple to list
+ wallets[data.walletId] = (
+ wallet: ref.read(pWallets).getWallet(data.walletId),
+ contracts: contracts,
+ );
+ }
+ } 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(
+ pWalletTokenAddresses(data.walletId),
+ );
+
+ // fetch each token
+ for (final tokenAddress in tokenMintAddresses) {
+ final token = dbProvider.getSplTokenSync(tokenAddress);
+
+ // add it to list if it exists in DB or in default tokens
+ 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.
+ }
+ }
+ }
+
// add tuple to list
wallets[data.walletId] = (
wallet: ref.read(pWallets).getWallet(data.walletId),
@@ -319,13 +366,23 @@ class _EthWalletsOverviewState extends ConsumerState<WalletsOverview> {
if (wallet.cryptoCurrency.hasTokenSupport) {
if (isDesktop) {
- return DesktopExpandingWalletCard(
- key: Key(
- "${wallet.walletId}_${entry.contracts.map((e) => e.address).join()}",
- ),
- data: entry,
- navigatorState: widget.navigatorState!,
- );
+ if (wallet.cryptoCurrency is Solana) {
+ return DesktopExpandingSolanaWalletCard(
+ key: Key(
+ "${wallet.walletId}_${entry.contracts.map((e) => e.address).join()}",
+ ),
+ data: entry,
+ navigatorState: widget.navigatorState!,
+ );
+ } else {
+ return DesktopExpandingWalletCard(
+ key: Key(
+ "${wallet.walletId}_${entry.contracts.map((e) => e.address).join()}",
+ ),
+ data: entry,
+ navigatorState: widget.navigatorState!,
+ );
+ }
} else {
return MasterWalletCard(
key: Key(wallet.walletId),
diff --git a/lib/pages_desktop_specific/my_stack_view/dialogs/desktop_expanding_solana_wallet_card.dart b/lib/pages_desktop_specific/my_stack_view/dialogs/desktop_expanding_solana_wallet_card.dart
new file mode 100644
index 0000000..db0afd6
--- /dev/null
+++ b/lib/pages_desktop_specific/my_stack_view/dialogs/desktop_expanding_solana_wallet_card.dart
@@ -0,0 +1,201 @@
+/*
+ * 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.
+ * Generated by Cypher Stack on 2025-11-20
+ *
+ */
+
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+import 'package:flutter_svg/svg.dart';
+
+import '../../../pages/wallets_view/wallets_overview.dart';
+import '../../../themes/stack_colors.dart';
+import '../../../utilities/assets.dart';
+import '../../../utilities/constants.dart';
+import '../../../utilities/text_styles.dart';
+import '../../../widgets/animated_widgets/rotate_icon.dart';
+import '../../../widgets/expandable.dart';
+import '../../../widgets/rounded_white_container.dart';
+import '../../../widgets/wallet_card.dart';
+import '../../../widgets/wallet_info_row/sub_widgets/wallet_info_row_balance.dart';
+import '../../../widgets/wallet_info_row/sub_widgets/wallet_info_row_coin_icon.dart';
+
+class DesktopExpandingSolanaWalletCard extends StatefulWidget {
+ const DesktopExpandingSolanaWalletCard({
+ super.key,
+ required this.data,
+ required this.navigatorState,
+ });
+
+ final WalletListItemData data;
+ final NavigatorState navigatorState;
+
+ @override
+ State<DesktopExpandingSolanaWalletCard> createState() =>
+ _DesktopExpandingSolanaWalletCardState();
+}
+
+class _DesktopExpandingSolanaWalletCardState
+ extends State<DesktopExpandingSolanaWalletCard> {
+ final expandableController = ExpandableController();
+ final rotateIconController = RotateIconController();
+ final List<String> tokenMintAddresses = [];
+
+ @override
+ void initState() {
+ if (widget.data.wallet.cryptoCurrency.hasTokenSupport) {
+ tokenMintAddresses.addAll(
+ widget.data.contracts.map((e) => e.address),
+ );
+ }
+
+ super.initState();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return RoundedWhiteContainer(
+ padding: EdgeInsets.zero,
+ borderColor: Theme.of(context).extension<StackColors>()!.backgroundAppBar,
+ child: Expandable(
+ initialState: widget.data.wallet.cryptoCurrency.hasTokenSupport
+ ? ExpandableState.expanded
+ : ExpandableState.collapsed,
+ controller: expandableController,
+ onExpandWillChange: (toState) {
+ if (toState == ExpandableState.expanded) {
+ rotateIconController.forward?.call();
+ } else {
+ rotateIconController.reverse?.call();
+ }
+ },
+ header: Padding(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 20,
+ vertical: 14,
+ ),
+ child: Row(
+ children: [
+ Expanded(
+ child: Row(
+ children: [
+ Expanded(
+ flex: 2,
+ child: Row(
+ children: [
+ WalletInfoCoinIcon(
+ coin: widget.data.wallet.info.coin,
+ ),
+ const SizedBox(
+ width: 12,
+ ),
+ Text(
+ widget.data.wallet.info.name,
+ style: STextStyles.desktopTextExtraSmall(context)
+ .copyWith(
+ color: Theme.of(context)
+ .extension<StackColors>()!
+ .textDark,
+ ),
+ ),
+ ],
+ ),
+ ),
+ Expanded(
+ flex: 4,
+ child: WalletInfoRowBalance(
+ walletId: widget.data.wallet.walletId,
+ ),
+ ),
+ ],
+ ),
+ ),
+ MaterialButton(
+ padding: const EdgeInsets.all(5),
+ materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ minWidth: 32,
+ height: 32,
+ color: Theme.of(context)
+ .extension<StackColors>()!
+ .textFieldDefaultBG,
+ elevation: 0,
+ hoverElevation: 0,
+ disabledElevation: 0,
+ highlightElevation: 0,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(
+ Constants.size.circularBorderRadius,
+ ),
+ ),
+ onPressed: () {
+ if (expandableController.state == ExpandableState.collapsed) {
+ rotateIconController.forward?.call();
+ } else {
+ rotateIconController.reverse?.call();
+ }
+ expandableController.toggle?.call();
+ },
+ child: RotateIcon(
+ controller: rotateIconController,
+ icon: RotatedBox(
+ quarterTurns: 2,
+ child: SvgPicture.asset(
+ Assets.svg.chevronDown,
+ width: 14,
+ ),
+ ),
+ curve: Curves.easeInOut,
+ ),
+ ),
+ ],
+ ),
+ ),
+ body: ListView(
+ shrinkWrap: true,
+ primary: false,
+ children: [
+ Container(
+ width: double.infinity,
+ height: 1,
+ color:
+ Theme.of(context).extension<StackColors>()!.backgroundAppBar,
+ ),
+ Padding(
+ padding: const EdgeInsets.only(
+ left: 32,
+ right: 14,
+ top: 14,
+ bottom: 14,
+ ),
+ child: SimpleWalletCard(
+ walletId: widget.data.wallet.walletId,
+ popPrevious: true,
+ desktopNavigatorState: widget.navigatorState,
+ ),
+ ),
+ ...tokenMintAddresses.map(
+ (e) => Padding(
+ padding: const EdgeInsets.only(
+ left: 32,
+ right: 14,
+ top: 14,
+ bottom: 14,
+ ),
+ child: SimpleWalletCard(
+ walletId: widget.data.wallet.walletId,
+ contractAddress: e,
+ popPrevious: true,
+ desktopNavigatorState: widget.navigatorState,
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/widgets/wallet_card.dart b/lib/widgets/wallet_card.dart
index ed49e5e..d545f3d 100644
--- a/lib/widgets/wallet_card.dart
+++ b/lib/widgets/wallet_card.dart
@@ -14,6 +14,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../models/isar/models/ethereum/eth_contract.dart';
+import '../models/isar/models/solana/spl_token.dart';
import '../pages/token_view/token_view.dart';
import '../pages/wallet_view/wallet_view.dart';
import '../pages_desktop_specific/my_stack_view/wallet_view/desktop_token_view.dart';
@@ -25,9 +26,13 @@ import '../utilities/logger.dart';
import '../utilities/show_loading.dart';
import '../utilities/show_node_tor_settings_mismatch.dart';
import '../utilities/util.dart';
+import '../wallets/crypto_currency/coins/solana.dart';
import '../wallets/isar/providers/eth/current_token_wallet_provider.dart';
+import '../wallets/isar/providers/solana/current_sol_token_wallet_provider.dart';
import '../wallets/wallet/impl/ethereum_wallet.dart';
+import '../wallets/wallet/impl/solana_wallet.dart';
import '../wallets/wallet/impl/sub_wallets/eth_token_wallet.dart';
+import '../wallets/wallet/impl/sub_wallets/solana_token_wallet.dart';
import '../wallets/wallet/intermediate/external_wallet.dart';
import '../wallets/wallet/wallet.dart';
import 'conditional_parent.dart';
@@ -50,7 +55,7 @@ class SimpleWalletCard extends ConsumerWidget {
final bool popPrevious;
final NavigatorState? desktopNavigatorState;
- Future<bool> _loadTokenWallet(
+ Future<bool> _loadEthTokenWallet(
BuildContext context,
WidgetRef ref,
Wallet wallet,
@@ -91,6 +96,47 @@ class SimpleWalletCard extends ConsumerWidget {
}
}
+ Future<bool> _loadSolanaTokenWallet(
+ BuildContext context,
+ WidgetRef ref,
+ Wallet wallet,
+ SplToken token,
+ ) async {
+ final old = ref.read(solanaTokenServiceStateProvider);
+ // exit previous if there is one
+ unawaited(old?.exit());
+ ref.read(solanaTokenServiceStateProvider.state).state = SolanaTokenWallet(
+ wallet as SolanaWallet,
+ token,
+ );
+
+ 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();
+ Navigator.of(context).pop();
+ if (desktopNavigatorState == null) {
+ Navigator.of(context).pop();
+ }
+ },
+ ),
+ ),
+ );
+ return false;
+ }
+ }
+
void _openWallet(BuildContext context, WidgetRef ref) async {
final nav = Navigator.of(context);
@@ -141,40 +187,90 @@ class SimpleWalletCard extends ConsumerWidget {
}
if (contractAddress != null) {
- final contract =
- ref.read(mainDBProvider).getEthContractSync(contractAddress!)!;
-
- final success = await showLoading<bool>(
- whileFuture: _loadTokenWallet(
- desktopNavigatorState?.context ?? context,
- ref,
- wallet,
- contract,
- ),
- context: desktopNavigatorState?.context ?? context,
- opaqueBG: true,
- message: "Loading ${contract.name}",
- rootNavigator: Util.isDesktop,
- );
+ if (wallet.cryptoCurrency is Solana) {
+ // Handle Solana token.
+ final token = ref.read(mainDBProvider).getSplTokenSync(contractAddress!);
- if (!success!) {
- // TODO: show error dialog here?
- Logging.instance.e(
- "Failed to load token wallet for $contract",
- );
- return;
- }
+ if (token == null) {
+ Logging.instance.e(
+ "Failed to find Solana token with address: $contractAddress",
+ );
+ return;
+ }
- if (desktopNavigatorState != null) {
- await desktopNavigatorState!.pushNamed(
- DesktopTokenView.routeName,
- arguments: walletId,
+ final success = await showLoading<bool>(
+ whileFuture: _loadSolanaTokenWallet(
+ desktopNavigatorState?.context ?? context,
+ ref,
+ wallet,
+ token,
+ ),
+ context: desktopNavigatorState?.context ?? context,
+ opaqueBG: true,
+ message: "Loading ${token.name}",
+ rootNavigator: Util.isDesktop,
);
+
+ if (!success!) {
+ Logging.instance.e(
+ "Failed to load token wallet for $token",
+ );
+ return;
+ }
+
+ if (desktopNavigatorState != null) {
+ await desktopNavigatorState!.pushNamed(
+ DesktopTokenView.routeName,
+ arguments: walletId,
+ );
+ } else {
+ await nav.pushNamed(
+ TokenView.routeName,
+ arguments: (walletId: walletId, popPrevious: !Util.isDesktop),
+ );
+ }
} else {
- await nav.pushNamed(
- TokenView.routeName,
- arguments: (walletId: walletId, popPrevious: !Util.isDesktop),
+ // Handle Ethereum token (default).
+ final contract = ref.read(mainDBProvider).getEthContractSync(contractAddress!);
+
+ if (contract == null) {
+ Logging.instance.e(
+ "Failed to find Ethereum contract with address: $contractAddress",
+ );
+ return;
+ }
+
+ final success = await showLoading<bool>(
+ whileFuture: _loadEthTokenWallet(
+ desktopNavigatorState?.context ?? context,
+ ref,
+ wallet,
+ contract,
+ ),
+ context: desktopNavigatorState?.context ?? context,
+ opaqueBG: true,
+ message: "Loading ${contract.name}",
+ rootNavigator: Util.isDesktop,
);
+
+ if (!success!) {
+ Logging.instance.e(
+ "Failed to load token wallet for $contract",
+ );
+ return;
+ }
+
+ if (desktopNavigatorState != null) {
+ await desktopNavigatorState!.pushNamed(
+ DesktopTokenView.routeName,
+ arguments: walletId,
+ );
+ } else {
+ await nav.pushNamed(
+ TokenView.routeName,
+ arguments: (walletId: walletId, popPrevious: !Util.isDesktop),
+ );
+ }
}
}
}
diff --git a/lib/widgets/wallet_info_row/sub_widgets/wallet_info_row_balance.dart b/lib/widgets/wallet_info_row/sub_widgets/wallet_info_row_balance.dart
index e88737d..e8c1c59 100644
--- a/lib/widgets/wallet_info_row/sub_widgets/wallet_info_row_balance.dart
+++ b/lib/widgets/wallet_info_row/sub_widgets/wallet_info_row_balance.dart
@@ -12,12 +12,15 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../db/isar/main_db.dart';
import '../../../models/isar/models/ethereum/eth_contract.dart';
+import '../../../models/isar/models/solana/spl_token.dart';
import '../../../themes/stack_colors.dart';
import '../../../utilities/amount/amount.dart';
import '../../../utilities/amount/amount_formatter.dart';
import '../../../utilities/text_styles.dart';
import '../../../utilities/util.dart';
+import '../../../wallets/crypto_currency/coins/solana.dart';
import '../../../wallets/isar/providers/eth/token_balance_provider.dart';
+import '../../../wallets/isar/providers/solana/sol_token_balance_provider.dart';
import '../../../wallets/isar/providers/wallet_info_provider.dart';
class WalletInfoRowBalance extends ConsumerWidget {
@@ -36,21 +39,49 @@ class WalletInfoRowBalance extends ConsumerWidget {
final Amount totalBalance;
EthContract? contract;
+ SplToken? splToken;
+
if (contractAddress == null) {
totalBalance = info.cachedBalance.total +
info.cachedBalanceSecondary.total +
info.cachedBalanceTertiary.total;
contract = null;
+ splToken = null;
} else {
- contract = MainDB.instance.getEthContractSync(contractAddress!)!;
- totalBalance = ref
- .watch(
- pTokenBalance(
- (walletId: walletId, contractAddress: contractAddress!),
- ),
- )
- .total;
+ // Check if it's a Solana wallet.
+ if (info.coin is Solana) {
+ splToken = MainDB.instance.getSplTokenSync(contractAddress!);
+ if (splToken != null) {
+ final solanaTokenInfo = ref
+ .watch(
+ pSolanaTokenWalletInfo(
+ (walletId: walletId, tokenMint: contractAddress!),
+ ),
+ );
+ totalBalance = solanaTokenInfo.getCachedBalance().total;
+ } else {
+ // Token not yet in database, show zero balance.
+ totalBalance = Amount(rawValue: BigInt.zero, fractionDigits: 0);
+ }
+ contract = null;
+ } else {
+ // Ethereum token.
+ contract = MainDB.instance.getEthContractSync(contractAddress!);
+ if (contract != null) {
+ totalBalance = ref
+ .watch(
+ pTokenBalance(
+ (walletId: walletId, contractAddress: contractAddress!),
+ ),
+ )
+ .total;
+ } else {
+ // Contract not yet in database, show zero balance.
+ totalBalance = Amount(rawValue: BigInt.zero, fractionDigits: 0);
+ }
+ splToken = null;
+ }
}
return Text(
diff --git a/lib/widgets/wallet_info_row/wallet_info_row.dart b/lib/widgets/wallet_info_row/wallet_info_row.dart
index 69aa906..5ea2add 100644
--- a/lib/widgets/wallet_info_row/wallet_info_row.dart
+++ b/lib/widgets/wallet_info_row/wallet_info_row.dart
@@ -11,11 +11,14 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
+import '../../models/isar/models/contract.dart';
import '../../models/isar/models/ethereum/eth_contract.dart';
+import '../../models/isar/models/solana/spl_token.dart';
import '../../providers/providers.dart';
import '../../themes/stack_colors.dart';
import '../../utilities/text_styles.dart';
import '../../utilities/util.dart';
+import '../../wallets/crypto_currency/coins/solana.dart';
import '../../wallets/isar/providers/wallet_info_provider.dart';
import '../coin_ticker_tag.dart';
import '../custom_buttons/blue_text_button.dart';
@@ -39,14 +42,31 @@ class WalletInfoRow extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final wallet = ref.watch(pWallets).getWallet(walletId);
+ final walletInfo = ref.watch(pWalletInfo(walletId));
+
+ Contract? contract;
+ String? contractName;
- EthContract? contract;
if (contractAddress != null) {
- contract = ref.watch(
- mainDBProvider.select(
- (value) => value.getEthContractSync(contractAddress!),
- ),
- );
+ if (walletInfo.coin is Solana) {
+ // Solana token.
+ final splToken = ref.watch(
+ mainDBProvider.select(
+ (value) => value.getSplTokenSync(contractAddress!),
+ ),
+ );
+ contract = splToken;
+ contractName = splToken?.name;
+ } else {
+ // Ethereum token.
+ final ethContract = ref.watch(
+ mainDBProvider.select(
+ (value) => value.getEthContractSync(contractAddress!),
+ ),
+ );
+ contract = ethContract;
+ contractName = ethContract?.name;
+ }
}
if (Util.isDesktop) {
@@ -65,11 +85,11 @@ class WalletInfoRow extends ConsumerWidget {
contractAddress: contractAddress,
),
const SizedBox(width: 12),
- contract != null
+ contractName != null
? Row(
children: [
Text(
- contract.name,
+ contractName!,
style:
STextStyles.desktopTextExtraSmall(
context,
@@ -138,11 +158,11 @@ class WalletInfoRow extends ConsumerWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- if (contract != null)
+ if (contractName != null)
Row(
children: [
Text(
- contract.name,
+ contractName!,
style: STextStyles.titleBold12(context),
),
const SizedBox(width: 4),
Why this scored 19/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.