fix(spl): mobile fee selection ui fix
What changed, and why it matters
This commit fixes two mobile UI/behavior bugs in the Solana token send flow. First, it delays setting the default fee speed until after the screen is built, preventing a possible crash or inconsistent state. Second, it corrects which wallet object is used to estimate fees for non-Ethereum tokens (like Solana tokens), so fee estimates come from the right wallet instead of the currently selected Ethereum token wallet. The commit does not describe these as security issues, but using the wrong wallet for fee estimates could in theory lead to incorrect fees being shown or used.
Treat as a routine bug fix. Review whether the pre-patch behavior could have caused incorrect fee quotes or transaction construction for Solana/SPL token sends, and consider regression testing fee selection across Ethereum and Solana token wallets. No immediate security response is indicated by the available evidence.
Security signals we found
State modification moved out of build/init to prevent runtime errors
Fee estimation now uses correct wallet object for non-EVM tokens
Potential use of wrong wallet object for fee estimation before patch
Evidence from the diff
In sol_token_send_view.dart, the default feeRateTypeMobileStateProvider is now set inside a WidgetsBinding.instance.addPostFrameCallback instead of synchronously during initState. This avoids modifying provider state during widget build. In transaction_fee_selection_sheet.dart, the fee estimation logic for tokens is changed: previously it always used pCurrentTokenWallet (the current Ethereum token wallet); now it checks coin is Ethereum and only uses pCurrentTokenWallet for Ethereum tokens, otherwise it fetches the wallet by walletId from pWallets. The same branching is applied to the FutureBuilder that reads .fees. A Wallet base class import is added to support the type.
Changed components
lib/pages/send_view/sol_token_send_view.dartlib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dartInspect captured patch +30 / −9
diff --git a/lib/pages/send_view/sol_token_send_view.dart b/lib/pages/send_view/sol_token_send_view.dart
index c94528f..11731c3 100644
--- a/lib/pages/send_view/sol_token_send_view.dart
+++ b/lib/pages/send_view/sol_token_send_view.dart
@@ -560,7 +560,10 @@ class _SolTokenSendViewState extends ConsumerState<SolTokenSendView> {
@override
void initState() {
ref.refresh(feeSheetSessionCacheProvider);
- ref.read(feeRateTypeMobileStateProvider.state).state = FeeRateType.slow;
+
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ ref.read(feeRateTypeMobileStateProvider.state).state = FeeRateType.slow;
+ });
_calculateFeesFuture = calculateFees();
_data = widget.autoFillData;
diff --git a/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart b/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart
index 8c165cc..5d586ae 100644
--- a/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart
+++ b/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart
@@ -1,4 +1,4 @@
-/*
+/*
* This file is part of Stack Wallet.
*
* Copyright (c) 2023 Cypher Stack
@@ -28,6 +28,7 @@ import '../../../wallets/isar/providers/eth/current_token_wallet_provider.dart';
import '../../../wallets/isar/providers/wallet_info_provider.dart';
import '../../../wallets/wallet/impl/firo_wallet.dart';
import '../../../wallets/wallet/intermediate/cryptonote_wallet.dart';
+import '../../../wallets/wallet/wallet.dart';
import '../../../wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart';
import '../../../widgets/animated_text.dart';
@@ -115,8 +116,13 @@ class _TransactionFeeSelectionSheetState
.estimateFeeFor(amount, feeRate);
}
} else {
- final tokenWallet = ref.read(pCurrentTokenWallet)!;
- final fee = await tokenWallet.estimateFeeFor(amount, feeRate);
+ final Wallet wallet;
+ if (coin is Ethereum) {
+ wallet = ref.read(pCurrentTokenWallet)!;
+ } else {
+ wallet = ref.read(pWallets).getWallet(walletId);
+ }
+ final fee = await wallet.estimateFeeFor(amount, feeRate);
ref.read(feeSheetSessionCacheProvider).fast[amount] = fee;
}
}
@@ -151,8 +157,13 @@ class _TransactionFeeSelectionSheetState
await wallet.estimateFeeFor(amount, feeRate);
}
} else {
- final tokenWallet = ref.read(pCurrentTokenWallet)!;
- final fee = await tokenWallet.estimateFeeFor(amount, feeRate);
+ final Wallet wallet;
+ if (coin is Ethereum) {
+ wallet = ref.read(pCurrentTokenWallet)!;
+ } else {
+ wallet = ref.read(pWallets).getWallet(walletId);
+ }
+ final fee = await wallet.estimateFeeFor(amount, feeRate);
ref.read(feeSheetSessionCacheProvider).average[amount] = fee;
}
}
@@ -187,8 +198,13 @@ class _TransactionFeeSelectionSheetState
.estimateFeeFor(amount, feeRate);
}
} else {
- final tokenWallet = ref.read(pCurrentTokenWallet)!;
- final fee = await tokenWallet.estimateFeeFor(amount, feeRate);
+ final Wallet wallet;
+ if (coin is Ethereum) {
+ wallet = ref.read(pCurrentTokenWallet)!;
+ } else {
+ wallet = ref.read(pWallets).getWallet(walletId);
+ }
+ final fee = await wallet.estimateFeeFor(amount, feeRate);
ref.read(feeSheetSessionCacheProvider).slow[amount] = fee;
}
}
@@ -269,7 +285,9 @@ class _TransactionFeeSelectionSheetState
const SizedBox(height: 36),
FutureBuilder(
future: widget.isToken
- ? ref.read(pCurrentTokenWallet)!.fees
+ ? (coin is Ethereum
+ ? ref.read(pCurrentTokenWallet)!.fees
+ : wallet.fees)
: wallet.fees,
builder: (context, AsyncSnapshot<FeeObject> snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
Why this scored 32/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.