hide/disable coin control view for salvium as the underlying library doesn't fully support it
What changed, and why it matters
This commit hides the 'coin control' feature for Salvium wallets in the Stack Wallet app. Coin control lets users manually choose which coins (transaction outputs) to spend. The change blocks Salvium wallets from using that feature because the underlying library doesn't fully support it. It is a compatibility/bug-avoidance change rather than a fix for an active security flaw, but disabling the unsupported path reduces the chance of users accidentally creating broken or mis-handled transactions.
Treat as a routine feature-compatibility patch. If Salvium coin control is re-enabled later, require explicit testing of the underlying library's UTXO handling and transaction construction. No immediate security response is indicated by this diff alone.
Security signals we found
Feature disabled for a specific coin due to incomplete library support
UI gating prevents user selection of UTXOs for Salvium
Transaction-building paths skip passing selected UTXOs for Salvium
No input validation, parsing, or cryptographic changes present
Evidence from the diff
The patch adds wallet is! SalviumWallet guards at every UI and transaction-building site that previously gated coin control on wallet is CoinControlInterface. Affected files include mobile and desktop send views, the wallet view, and FROST multisig send. The guards prevent coin-control UTXO selection from being shown or passed into transaction data for Salvium wallets. The commit message states the underlying library does not fully support coin control for Salvium. There is no cryptographic or network-layer change; it is purely a UI/flow exclusion.
Changed components
lib/pages/send_view/send_view.dartlib/pages/send_view/frost_ms/frost_send_view.dartlib/pages/wallet_view/wallet_view.dartlib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dartlib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_features.dartSalvium wallet coin-control integrationInspect captured patch +116 / −105
diff --git a/lib/pages/send_view/frost_ms/frost_send_view.dart b/lib/pages/send_view/frost_ms/frost_send_view.dart
index 4b10141..59bdc84 100644
--- a/lib/pages/send_view/frost_ms/frost_send_view.dart
+++ b/lib/pages/send_view/frost_ms/frost_send_view.dart
@@ -33,6 +33,7 @@ import '../../../wallets/crypto_currency/crypto_currency.dart';
import '../../../wallets/isar/providers/wallet_info_provider.dart';
import '../../../wallets/models/tx_data.dart';
import '../../../wallets/wallet/impl/bitcoin_frost_wallet.dart';
+import '../../../wallets/wallet/impl/salvium_wallet.dart';
import '../../../wallets/wallet/wallet_mixin_interfaces/coin_control_interface.dart';
import '../../../widgets/background.dart';
import '../../../widgets/conditional_parent.dart';
@@ -164,10 +165,9 @@ class _FrostSendViewState extends ConsumerState<FrostSendView> {
child: Text(
"Ok",
style: STextStyles.button(context).copyWith(
- color:
- Theme.of(
- context,
- ).extension<StackColors>()!.accentColorDark,
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.accentColorDark,
),
),
onPressed: () {
@@ -231,6 +231,7 @@ class _FrostSendViewState extends ConsumerState<FrostSendView> {
final showCoinControl =
wallet is CoinControlInterface &&
+ wallet is! SalviumWallet &&
ref.watch(
prefsChangeNotifierProvider.select(
(value) => value.enableCoinControl,
@@ -242,59 +243,56 @@ class _FrostSendViewState extends ConsumerState<FrostSendView> {
return ConditionalParent(
condition: !Util.isDesktop,
- builder:
- (child) => Background(
- child: Scaffold(
- backgroundColor:
- Theme.of(context).extension<StackColors>()!.background,
- appBar: AppBar(
- leading: AppBarBackButton(
- onPressed: () async {
- if (FocusScope.of(context).hasFocus) {
- FocusScope.of(context).unfocus();
- await Future<void>.delayed(
- const Duration(milliseconds: 50),
- );
- }
- if (context.mounted) {
- Navigator.of(context).pop();
- }
- },
- ),
- title: Text(
- "Send ${coin.ticker}",
- style: STextStyles.navBarTitle(context),
- ),
- ),
- body: SafeArea(
- child: LayoutBuilder(
- builder: (builderContext, constraints) {
- return SingleChildScrollView(
- child: ConstrainedBox(
- constraints: BoxConstraints(
- // subtract top and bottom padding set in parent
- minHeight: constraints.maxHeight,
- ),
- child: IntrinsicHeight(
- child: Padding(
- padding: const EdgeInsets.symmetric(horizontal: 16),
- child: child,
- ),
- ),
+ builder: (child) => Background(
+ child: Scaffold(
+ backgroundColor: Theme.of(
+ context,
+ ).extension<StackColors>()!.background,
+ appBar: AppBar(
+ leading: AppBarBackButton(
+ onPressed: () async {
+ if (FocusScope.of(context).hasFocus) {
+ FocusScope.of(context).unfocus();
+ await Future<void>.delayed(const Duration(milliseconds: 50));
+ }
+ if (context.mounted) {
+ Navigator.of(context).pop();
+ }
+ },
+ ),
+ title: Text(
+ "Send ${coin.ticker}",
+ style: STextStyles.navBarTitle(context),
+ ),
+ ),
+ body: SafeArea(
+ child: LayoutBuilder(
+ builder: (builderContext, constraints) {
+ return SingleChildScrollView(
+ child: ConstrainedBox(
+ constraints: BoxConstraints(
+ // subtract top and bottom padding set in parent
+ minHeight: constraints.maxHeight,
+ ),
+ child: IntrinsicHeight(
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16),
+ child: child,
),
- );
- },
- ),
- ),
+ ),
+ ),
+ );
+ },
),
),
+ ),
+ ),
child: ConditionalParent(
condition: Util.isDesktop,
- builder:
- (child) => Padding(
- padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
- child: child,
- ),
+ builder: (child) => Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
+ child: child,
+ ),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
@@ -375,11 +373,10 @@ class _FrostSendViewState extends ConsumerState<FrostSendView> {
for (int i = 0; i < recipientWidgetIndexes.length; i++)
ConditionalParent(
condition: recipientWidgetIndexes.length > 1,
- builder:
- (child) => Padding(
- padding: const EdgeInsets.only(top: 8),
- child: child,
- ),
+ builder: (child) => Padding(
+ padding: const EdgeInsets.only(top: 8),
+ child: child,
+ ),
child: Recipient(
key: Key("recipientKey_${recipientWidgetIndexes[i]}"),
index: recipientWidgetIndexes[i],
@@ -388,21 +385,21 @@ class _FrostSendViewState extends ConsumerState<FrostSendView> {
onChanged: () {
_validateRecipientFormStates();
},
- remove:
- i == 0 && recipientWidgetIndexes.length == 1
- ? null
- : () {
- ref
- .read(
- pRecipient(
- recipientWidgetIndexes[i],
- ).notifier,
- )
- .state = null;
- recipientWidgetIndexes.removeAt(i);
- setState(() {});
- _validateRecipientFormStates();
- },
+ remove: i == 0 && recipientWidgetIndexes.length == 1
+ ? null
+ : () {
+ ref
+ .read(
+ pRecipient(
+ recipientWidgetIndexes[i],
+ ).notifier,
+ )
+ .state =
+ null;
+ recipientWidgetIndexes.removeAt(i);
+ setState(() {});
+ _validateRecipientFormStates();
+ },
addAnotherRecipientTapped: () {
// used for tracking recipient forms
_greatestWidgetIndex++;
@@ -443,17 +440,15 @@ class _FrostSendViewState extends ConsumerState<FrostSendView> {
Text(
"Coin control",
style: STextStyles.w500_14(context).copyWith(
- color:
- Theme.of(
- context,
- ).extension<StackColors>()!.textSubtitle1,
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.textSubtitle1,
),
),
CustomTextButton(
- text:
- selectedUTXOs.isEmpty
- ? "Select coins"
- : "Selected coins (${selectedUTXOs.length})",
+ text: selectedUTXOs.isEmpty
+ ? "Select coins"
+ : "Selected coins (${selectedUTXOs.length})",
onTap: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
@@ -506,32 +501,32 @@ class _FrostSendViewState extends ConsumerState<FrostSendView> {
focusNode: _noteFocusNode,
style: STextStyles.field(context),
onChanged: (_) => setState(() {}),
- decoration: standardInputDecoration(
- "Type something...",
- _noteFocusNode,
- context,
- ).copyWith(
- suffixIcon:
- noteController.text.isNotEmpty
+ decoration:
+ standardInputDecoration(
+ "Type something...",
+ _noteFocusNode,
+ context,
+ ).copyWith(
+ suffixIcon: noteController.text.isNotEmpty
? Padding(
- padding: const EdgeInsets.only(right: 0),
- child: UnconstrainedBox(
- child: Row(
- children: [
- TextFieldIconButton(
- child: const XIcon(),
- onTap: () async {
- setState(() {
- noteController.text = "";
- });
- },
- ),
- ],
+ padding: const EdgeInsets.only(right: 0),
+ child: UnconstrainedBox(
+ child: Row(
+ children: [
+ TextFieldIconButton(
+ child: const XIcon(),
+ onTap: () async {
+ setState(() {
+ noteController.text = "";
+ });
+ },
+ ),
+ ],
+ ),
),
- ),
- )
+ )
: null,
- ),
+ ),
),
),
const SizedBox(height: 12),
diff --git a/lib/pages/send_view/send_view.dart b/lib/pages/send_view/send_view.dart
index 94b5663..a2dd4f4 100644
--- a/lib/pages/send_view/send_view.dart
+++ b/lib/pages/send_view/send_view.dart
@@ -57,6 +57,7 @@ import '../../wallets/models/tx_data.dart';
import '../../wallets/wallet/impl/epiccash_wallet.dart';
import '../../wallets/wallet/impl/firo_wallet.dart';
import '../../wallets/wallet/impl/mimblewimblecoin_wallet.dart';
+import '../../wallets/wallet/impl/salvium_wallet.dart';
import '../../wallets/wallet/intermediate/cryptonote_wallet.dart';
import '../../wallets/wallet/wallet_mixin_interfaces/coin_control_interface.dart';
import '../../wallets/wallet/wallet_mixin_interfaces/mweb_interface.dart';
@@ -813,7 +814,9 @@ class _SendViewState extends ConsumerState<SendView> {
.enableCoinControl;
if (coin is! Ethereum &&
- !(wallet is CoinControlInterface && coinControlEnabled) ||
+ !(wallet is CoinControlInterface &&
+ wallet is! SalviumWallet &&
+ coinControlEnabled) ||
(wallet is CoinControlInterface &&
coinControlEnabled &&
selectedUTXOs.isEmpty)) {
@@ -915,6 +918,7 @@ class _SendViewState extends ConsumerState<SendView> {
feeRateType: feeRate,
utxos:
(wallet is CoinControlInterface &&
+ wallet is! SalviumWallet &&
coinControlEnabled &&
selectedUTXOs.isNotEmpty)
? selectedUTXOs
@@ -1037,6 +1041,7 @@ class _SendViewState extends ConsumerState<SendView> {
ethEIP1559Fee: ethFee,
utxos:
(wallet is CoinControlInterface &&
+ wallet is! SalviumWallet &&
coinControlEnabled &&
selectedUTXOs.isNotEmpty)
? selectedUTXOs
@@ -1405,6 +1410,7 @@ class _SendViewState extends ConsumerState<SendView> {
),
) &&
ref.watch(pWallets).getWallet(walletId) is CoinControlInterface &&
+ ref.watch(pWallets).getWallet(walletId) is! SalviumWallet &&
(showPrivateBalance ? balType == BalanceType.public : true);
final isExchangeAddress = ref.watch(pIsExchangeAddress);
diff --git a/lib/pages/wallet_view/wallet_view.dart b/lib/pages/wallet_view/wallet_view.dart
index 83a4d6e..0dc8d17 100644
--- a/lib/pages/wallet_view/wallet_view.dart
+++ b/lib/pages/wallet_view/wallet_view.dart
@@ -54,6 +54,7 @@ import '../../wallets/wallet/impl/epiccash_wallet.dart';
import '../../wallets/wallet/impl/firo_wallet.dart';
import '../../wallets/wallet/impl/mimblewimblecoin_wallet.dart';
import '../../wallets/wallet/impl/namecoin_wallet.dart';
+import '../../wallets/wallet/impl/salvium_wallet.dart';
import '../../wallets/wallet/intermediate/cryptonote_wallet.dart';
import '../../wallets/wallet/intermediate/lib_salvium_wallet.dart';
import '../../wallets/wallet/wallet_mixin_interfaces/cash_fusion_interface.dart';
@@ -1172,6 +1173,7 @@ class _WalletViewState extends ConsumerState<WalletView> {
},
),
if (wallet is CoinControlInterface &&
+ wallet is! SalviumWallet &&
ref.watch(
prefsChangeNotifierProvider.select(
(value) => value.enableCoinControl,
diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart
index b8dc85f..efc02f6 100644
--- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart
+++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart
@@ -56,6 +56,7 @@ import '../../../../wallets/models/tx_data.dart';
import '../../../../wallets/wallet/impl/epiccash_wallet.dart';
import '../../../../wallets/wallet/impl/firo_wallet.dart';
import '../../../../wallets/wallet/impl/mimblewimblecoin_wallet.dart';
+import '../../../../wallets/wallet/impl/salvium_wallet.dart';
import '../../../../wallets/wallet/wallet_mixin_interfaces/coin_control_interface.dart';
import '../../../../wallets/wallet/wallet_mixin_interfaces/mweb_interface.dart';
import '../../../../wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart';
@@ -458,7 +459,9 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
.read(prefsChangeNotifierProvider)
.enableCoinControl;
- if (!(wallet is CoinControlInterface && coinControlEnabled) ||
+ if (!(wallet is CoinControlInterface &&
+ wallet is! SalviumWallet &&
+ coinControlEnabled) ||
(coinControlEnabled && ref.read(desktopUseUTXOs).isEmpty)) {
// confirm send all
if (amount == availableBalance) {
@@ -597,6 +600,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
feeRateType: feeRate,
utxos:
(wallet is CoinControlInterface &&
+ wallet is! SalviumWallet &&
coinControlEnabled &&
ref.read(pDesktopUseUTXOs).isNotEmpty)
? ref.read(pDesktopUseUTXOs)
@@ -724,6 +728,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
: null,
utxos:
(wallet is CoinControlInterface &&
+ wallet is! SalviumWallet &&
coinControlEnabled &&
ref.read(pDesktopUseUTXOs).isNotEmpty)
? ref.read(pDesktopUseUTXOs)
@@ -1351,6 +1356,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
),
) &&
ref.watch(pWallets).getWallet(walletId) is CoinControlInterface &&
+ ref.watch(pWallets).getWallet(walletId) is! SalviumWallet &&
(showPrivateBalance ? balType == BalanceType.public : true);
return Column(
diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_features.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_features.dart
index a9458bf..ca0a2ae 100644
--- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_features.dart
+++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_features.dart
@@ -44,6 +44,7 @@ import '../../../../wallets/crypto_currency/coins/firo.dart';
import '../../../../wallets/wallet/impl/bitcoin_wallet.dart';
import '../../../../wallets/wallet/impl/firo_wallet.dart';
import '../../../../wallets/wallet/impl/namecoin_wallet.dart';
+import '../../../../wallets/wallet/impl/salvium_wallet.dart';
import '../../../../wallets/wallet/intermediate/cryptonote_wallet.dart';
import '../../../../wallets/wallet/intermediate/lib_salvium_wallet.dart';
import '../../../../wallets/wallet/wallet.dart' show Wallet;
@@ -561,6 +562,7 @@ class _DesktopWalletFeaturesState extends ConsumerState<DesktopWalletFeatures> {
prefsChangeNotifierProvider.select((value) => value.enableExchange),
),
(wallet is CoinControlInterface &&
+ wallet is! SalviumWallet &&
ref.watch(
prefsChangeNotifierProvider.select(
(value) => value.enableCoinControl,
Why this scored 21/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.