What changed, and why it matters
This commit tidies up how token icons are displayed in the Stack Wallet app. It switches Ethereum and Solana fallback icons from app-asset files to local files, adds a loading spinner while network icons load, and replaces a silent error with a logged error. There is no obvious security vulnerability here; it is primarily a UI cleanup.
No security action required. Treat as routine UI/maintenance cleanup. If desired, verify that `coinIconProvider` returns only expected local file paths and that network SVG URLs are loaded over HTTPS/trusted CDN as part of normal app hardening, but this commit does not introduce that risk.
Security signals we found
No security-relevant code paths modified
No input validation, parsing, or trust-boundary changes
Error handling changed from silent swallow to logging (minor observability improvement)
File path used for local SVG fallback remains derived from existing provider
Evidence from the diff
The patch modifies two Flutter widget files (eth_token_icon.dart and sol_token_icon.dart). It imports dart:io and LoadingIndicator, changes SvgPicture.asset to SvgPicture.file for fallback icons, and adds a placeholderBuilder to network SVG loaders. It also removes a private _buildSolanaIcon() helper and changes a silent catch block to log exceptions via Logging.instance.e. The constructor argument for Ethereum and Solana is shortened from CryptoCurrencyNetwork.main to .main, which is a syntactic cleanup assuming the enum is imported in scope. No network, cryptographic, or storage security changes are evident.
Changed components
lib/widgets/icon_widgets/eth_token_icon.dartlib/widgets/icon_widgets/sol_token_icon.dartInspect captured patch +24 / −36
diff --git a/lib/widgets/icon_widgets/eth_token_icon.dart b/lib/widgets/icon_widgets/eth_token_icon.dart
index 0b0104f..8564742 100644
--- a/lib/widgets/icon_widgets/eth_token_icon.dart
+++ b/lib/widgets/icon_widgets/eth_token_icon.dart
@@ -8,6 +8,8 @@
*
*/
+import 'dart:io';
+
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
@@ -18,6 +20,7 @@ import '../../services/exchange/change_now/change_now_exchange.dart';
import '../../services/exchange/exchange_data_loading_service.dart';
import '../../themes/coin_icon_provider.dart';
import '../../wallets/crypto_currency/crypto_currency.dart';
+import '../loading_indicator.dart';
class EthTokenIcon extends ConsumerStatefulWidget {
const EthTokenIcon({
@@ -41,18 +44,14 @@ class _EthTokenIconState extends ConsumerState<EthTokenIcon> {
super.initState();
ExchangeDataLoadingService.instance.isar.then((isar) async {
- final currency =
- await isar.currencies
- .where()
- .exchangeNameEqualTo(ChangeNowExchange.exchangeName)
- .filter()
- .tokenContractEqualTo(
- widget.contractAddress,
- caseSensitive: false,
- )
- .and()
- .imageIsNotEmpty()
- .findFirst();
+ final currency = await isar.currencies
+ .where()
+ .exchangeNameEqualTo(ChangeNowExchange.exchangeName)
+ .filter()
+ .tokenContractEqualTo(widget.contractAddress, caseSensitive: false)
+ .and()
+ .imageIsNotEmpty()
+ .findFirst();
if (mounted) {
WidgetsBinding.instance.addPostFrameCallback((_) {
@@ -69,8 +68,8 @@ class _EthTokenIconState extends ConsumerState<EthTokenIcon> {
@override
Widget build(BuildContext context) {
if (imageUrl == null || imageUrl!.isEmpty) {
- return SvgPicture.asset(
- ref.watch(coinIconProvider(Ethereum(CryptoCurrencyNetwork.main))),
+ return SvgPicture.file(
+ File(ref.watch(coinIconProvider(Ethereum(.main)))),
width: widget.size,
height: widget.size,
);
@@ -79,6 +78,7 @@ class _EthTokenIconState extends ConsumerState<EthTokenIcon> {
imageUrl!,
width: widget.size,
height: widget.size,
+ placeholderBuilder: (_) => const LoadingIndicator(),
);
}
}
diff --git a/lib/widgets/icon_widgets/sol_token_icon.dart b/lib/widgets/icon_widgets/sol_token_icon.dart
index b17708b..dc01d17 100644
--- a/lib/widgets/icon_widgets/sol_token_icon.dart
+++ b/lib/widgets/icon_widgets/sol_token_icon.dart
@@ -18,7 +18,9 @@ import '../../models/isar/exchange_cache/currency.dart';
import '../../services/exchange/change_now/change_now_exchange.dart';
import '../../services/exchange/exchange_data_loading_service.dart';
import '../../themes/coin_icon_provider.dart';
+import '../../utilities/logger.dart';
import '../../wallets/crypto_currency/crypto_currency.dart';
+import '../loading_indicator.dart';
/// Token icon widget for Solana SPL tokens.
///
@@ -30,7 +32,6 @@ class SolTokenIcon extends ConsumerStatefulWidget {
/// The SPL token mint address.
final String mintAddress;
- /// Size of the icon in pixels.
final double size;
@override
@@ -67,13 +68,8 @@ class _SolTokenIconState extends ConsumerState<SolTokenIcon> {
}
});
}
- } catch (e) {
- // Silently fail - we'll use fallback icon.
- if (mounted) {
- setState(() {
- imageUrl = null;
- });
- }
+ } catch (e, s) {
+ Logging.instance.e("", error: e, stackTrace: s);
}
}
@@ -81,27 +77,19 @@ class _SolTokenIconState extends ConsumerState<SolTokenIcon> {
Widget build(BuildContext context) {
if (imageUrl == null || imageUrl!.isEmpty) {
// Fallback to Solana coin icon from theme.
- return _buildSolanaIcon();
+ return SvgPicture.file(
+ File(ref.watch(coinIconProvider(Solana(.main)))),
+ width: widget.size,
+ height: widget.size,
+ );
} else {
// Display token icon from network.
return SvgPicture.network(
imageUrl!,
width: widget.size,
height: widget.size,
- placeholderBuilder: (context) {
- return _buildSolanaIcon();
- },
+ placeholderBuilder: (_) => const LoadingIndicator(),
);
}
}
-
- /// Build a Solana icon from the theme assets using file path, not asset bundle.
- Widget _buildSolanaIcon() {
- final assetPath = ref.watch(coinIconProvider(Solana(CryptoCurrencyNetwork.main)));
- return SvgPicture.file(
- File(assetPath),
- width: widget.size,
- height: widget.size,
- );
- }
}
Why this scored 18/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.