Merge branch 'staging' into codex/rsfiro-app-config
What changed, and why it matters
This commit is a routine merge that improves how the Stack Wallet app displays Firo masternode status. Previously, a masternode was shown as simply 'ACTIVE' or 'REVOKED' based only on whether it had been revoked. Now it can also show 'BANNED' when the masternode has been banned for misbehavior (a 'poseBanHeight' value other than -1). The colors in the UI were also updated so banned nodes appear orange instead of green or red. There is no indication this fixes a security vulnerability; it is a user-interface accuracy improvement.
No security action required; treat as normal UI/UX improvement. If reviewing for completeness, verify that MasternodeStatus covers all expected states returned by the Firo RPC and that the poseBanHeight sentinel value (-1) matches the backend.
Security signals we found
No security-relevant code paths modified
UI-only display change for masternode state
No input validation, serialization, or authentication changes
Evidence from the diff
The merge adds a MasternodeStatus enum (active/banned/revoked) to firo_wallet.dart and a computed status getter on MasternodeInfo. Logic now returns banned when poseBanHeight != -1, revoked when revocationReason != 0, and active otherwise. The masternode list card replaces the binary isActive/revocationReason == 0 check with the new status, adding an orange badge for banned nodes and using status.label for display text. pretty() output also uses status.label. No cryptographic, network, or permission changes are present.
Changed components
lib/pages/masternodes/sub_widgets/masternodes_list.dartlib/wallets/wallet/impl/firo_wallet.dartInspect captured patch +30 / −13
### lib/pages/masternodes/sub_widgets/masternodes_list.dart
@@ -68,16 +68,15 @@ class _MasternodeCard extends StatelessWidget {
),
);
} else {
- await Navigator.of(
- context,
- ).pushNamed(MasternodeDetailsView.routeName, arguments: node);
+ await Navigator.of(context)
+ .pushNamed(MasternodeDetailsView.routeName, arguments: node);
}
}
@override
Widget build(BuildContext context) {
final stack = Theme.of(context).extension<StackColors>()!;
- final isActive = node.revocationReason == 0;
+ final status = node.status;
return RoundedWhiteContainer(
padding: const EdgeInsets.all(16),
@@ -97,9 +96,8 @@ class _MasternodeCard extends StatelessWidget {
const SizedBox(height: 2),
Text(
"Last paid height: ${node.lastPaidHeight}",
- style: STextStyles.baseXS(
- context,
- ).copyWith(color: stack.textSubtitle1),
+ style: STextStyles.baseXS(context)
+ .copyWith(color: stack.textSubtitle1),
),
],
),
@@ -108,14 +106,17 @@ class _MasternodeCard extends StatelessWidget {
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
- color: isActive ? stack.accentColorGreen : stack.accentColorRed,
+ color: switch (status) {
+ MasternodeStatus.active => stack.accentColorGreen,
+ MasternodeStatus.banned => stack.accentColorOrange,
+ MasternodeStatus.revoked => stack.accentColorRed,
+ },
borderRadius: BorderRadius.circular(8),
),
child: Text(
- isActive ? "ACTIVE" : "REVOKED",
- style: STextStyles.w600_12(
- context,
- ).copyWith(color: stack.textWhite),
+ status.label,
+ style: STextStyles.w600_12(context)
+ .copyWith(color: stack.textWhite),
),
),
],
### lib/wallets/wallet/impl/firo_wallet.dart
@@ -32,6 +32,16 @@ import '../wallet_mixin_interfaces/extended_keys_interface.dart';
import '../wallet_mixin_interfaces/spark_interface.dart';
import 'firo_transaction_type.dart';
+enum MasternodeStatus {
+ active("ACTIVE"),
+ banned("BANNED"),
+ revoked("REVOKED");
+
+ const MasternodeStatus(this.label);
+
+ final String label;
+}
+
class MasternodeInfo {
final String proTxHash;
final String collateralHash;
@@ -71,11 +81,17 @@ class MasternodeInfo {
required this.pubKeyOperator,
});
+ MasternodeStatus get status {
+ if (revocationReason != 0) return MasternodeStatus.revoked;
+ if (poseBanHeight != -1) return MasternodeStatus.banned;
+ return MasternodeStatus.active;
+ }
+
Map<String, String> pretty() {
return {
"ProTx Hash": proTxHash,
"IP:Port": "$serviceAddr:$servicePort",
- "Status": revocationReason == 0 ? "Active" : "Revoked",
+ "Status": status.label,
"Registered Height": registeredHeight.toString(),
"Last Paid Height": lastPaidHeight.toString(),
"Payout Address": payoutAddress,Why this scored 20/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.