refactor create masternode dialog to match app wide look and feel and WIP general masternodes UX upgrades
What changed, and why it matters
This commit is a user-interface refactor for the masternode creation screen in Stack Wallet. It moves the masternode registration form into a reusable widget, updates styling to match the rest of the app, and renames an internal constant. There is no indication of a security fix or vulnerability being addressed.
No security action required; review as normal UI refactor if desired.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change extracts _CreateMasternodeDialog from masternodes_home_view.dart into a new CreateMasternodeView plus a shared RegisterMasternodeForm widget. It updates navigation, button styling, and route registration. The only functional change in firo_wallet.dart is renaming _masterNodeValue to kMasterNodeValue (a Dart public-constant convention) with no value change. Validation logic, balance checks, and the registerMasternode call remain effectively the same.
Changed components
lib/pages/masternodes/create_masternode_view.dartlib/pages/masternodes/masternodes_home_view.dartlib/pages/masternodes/sub_widgets/register_masternode_form.dartlib/route_generator.dartlib/wallets/wallet/impl/firo_wallet.dartInspect captured patch +484 / −290
diff --git a/lib/pages/masternodes/create_masternode_view.dart b/lib/pages/masternodes/create_masternode_view.dart
new file mode 100644
index 0000000..0f54e54
--- /dev/null
+++ b/lib/pages/masternodes/create_masternode_view.dart
@@ -0,0 +1,107 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+
+import '../../themes/stack_colors.dart';
+import '../../utilities/text_styles.dart';
+import '../../utilities/util.dart';
+import '../../widgets/background.dart';
+import '../../widgets/conditional_parent.dart';
+import '../../widgets/custom_buttons/app_bar_icon_button.dart';
+import '../../widgets/desktop/desktop_dialog_close_button.dart';
+import 'sub_widgets/register_masternode_form.dart';
+
+class CreateMasternodeView extends ConsumerStatefulWidget {
+ const CreateMasternodeView({super.key, required this.firoWalletId});
+
+ static const routeName = "/createMasternodeView";
+
+ final String firoWalletId;
+
+ @override
+ ConsumerState<CreateMasternodeView> createState() =>
+ _CreateMasternodeDialogState();
+}
+
+class _CreateMasternodeDialogState extends ConsumerState<CreateMasternodeView> {
+ @override
+ Widget build(BuildContext context) {
+ return ConditionalParent(
+ condition: Util.isDesktop,
+ builder: (child) => SizedBox(
+ width: 660,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Row(
+ mainAxisAlignment: .spaceBetween,
+ children: [
+ Padding(
+ padding: const EdgeInsets.only(left: 32),
+ child: Text(
+ "Create masternode",
+ style: STextStyles.desktopH3(context),
+ ),
+ ),
+ const DesktopDialogCloseButton(),
+ ],
+ ),
+ Flexible(
+ child: Padding(
+ padding: const EdgeInsets.only(left: 32, bottom: 32, right: 32),
+ child: child,
+ ),
+ ),
+ ],
+ ),
+ ),
+ child: ConditionalParent(
+ condition: !Util.isDesktop,
+ builder: (child) => Background(
+ child: Scaffold(
+ backgroundColor: Theme.of(
+ context,
+ ).extension<StackColors>()!.background,
+ appBar: AppBar(
+ backgroundColor: Theme.of(
+ context,
+ ).extension<StackColors>()!.background,
+ leading: AppBarBackButton(
+ onPressed: () async {
+ Navigator.of(context).pop();
+ },
+ ),
+ title: Text(
+ "Create masternode",
+ style: STextStyles.navBarTitle(context),
+ ),
+ ),
+ body: SafeArea(
+ child: LayoutBuilder(
+ builder: (context, constraints) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16),
+ child: SingleChildScrollView(
+ child: ConstrainedBox(
+ constraints: BoxConstraints(
+ minHeight: constraints.maxHeight,
+ ),
+ child: IntrinsicHeight(
+ child: Padding(
+ padding: const EdgeInsets.only(bottom: 16),
+ child: child,
+ ),
+ ),
+ ),
+ ),
+ );
+ },
+ ),
+ ),
+ ),
+ ),
+ child: RegisterMasternodeForm(firoWalletId: widget.firoWalletId),
+ ),
+ );
+ }
+}
diff --git a/lib/pages/masternodes/masternodes_home_view.dart b/lib/pages/masternodes/masternodes_home_view.dart
index e0b5dbe..3179972 100644
--- a/lib/pages/masternodes/masternodes_home_view.dart
+++ b/lib/pages/masternodes/masternodes_home_view.dart
@@ -1,18 +1,19 @@
-import 'package:decimal/decimal.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
+
+import '../../providers/global/wallets_provider.dart';
import '../../themes/stack_colors.dart';
import '../../utilities/assets.dart';
import '../../utilities/text_styles.dart';
import '../../utilities/util.dart';
-import '../../utilities/logger.dart';
+import '../../wallets/wallet/impl/firo_wallet.dart';
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
import '../../widgets/desktop/desktop_app_bar.dart';
import '../../widgets/desktop/desktop_scaffold.dart';
-import '../../widgets/stack_dialog.dart';
-import '../../providers/global/wallets_provider.dart';
-import '../../wallets/wallet/impl/firo_wallet.dart';
+import '../../widgets/desktop/primary_button.dart';
+import '../../widgets/dialogs/s_dialog.dart';
+import 'create_masternode_view.dart';
class MasternodesHomeView extends ConsumerStatefulWidget {
const MasternodesHomeView({super.key, required this.walletId});
@@ -87,18 +88,20 @@ class _MasternodesHomeViewState extends ConsumerState<MasternodesHomeView> {
),
trailing: Padding(
padding: const EdgeInsets.only(right: 24),
- child: ElevatedButton.icon(
- onPressed: _showCreateMasternodeDialog,
- style: ElevatedButton.styleFrom(
- backgroundColor: Theme.of(
- context,
- ).extension<StackColors>()!.buttonBackPrimary,
- foregroundColor: Theme.of(
- context,
- ).extension<StackColors>()!.buttonTextPrimary,
+ child: PrimaryButton(
+ label: "Create Masternode",
+ buttonHeight: .l,
+ horizontalContentPadding: 10,
+ icon: SvgPicture.asset(
+ Assets.svg.circlePlus,
+ colorFilter: ColorFilter.mode(
+ Theme.of(
+ context,
+ ).extension<StackColors>()!.buttonTextPrimary,
+ .srcIn,
+ ),
),
- icon: const Icon(Icons.add),
- label: const Text('Create Masternode'),
+ onPressed: _showDesktopCreateMasternodeDialog,
),
),
)
@@ -114,11 +117,38 @@ class _MasternodesHomeViewState extends ConsumerState<MasternodesHomeView> {
),
actions: [
Padding(
- padding: const EdgeInsets.only(right: 16),
- child: IconButton(
- onPressed: _showCreateMasternodeDialog,
- icon: const Icon(Icons.add),
- tooltip: 'Create Masternode',
+ padding: const EdgeInsets.only(
+ top: 10,
+ bottom: 10,
+ right: 10,
+ ),
+ child: AspectRatio(
+ aspectRatio: 1,
+ child: AppBarIconButton(
+ key: const Key("createNewMasterNodeButton"),
+ size: 36,
+ shadows: const [],
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.background,
+ icon: SvgPicture.asset(
+ Assets.svg.plus,
+ colorFilter: ColorFilter.mode(
+ Theme.of(
+ context,
+ ).extension<StackColors>()!.accentColorDark,
+ .srcIn,
+ ),
+ width: 20,
+ height: 20,
+ ),
+ onPressed: () {
+ Navigator.of(context).pushNamed(
+ CreateMasternodeView.routeName,
+ arguments: widget.walletId,
+ );
+ },
+ ),
),
),
],
@@ -152,19 +182,27 @@ class _MasternodesHomeViewState extends ConsumerState<MasternodesHomeView> {
"No masternodes found",
style: STextStyles.w600_14(context),
),
- const SizedBox(height: 16),
- ElevatedButton.icon(
- onPressed: _showCreateMasternodeDialog,
- style: ElevatedButton.styleFrom(
- backgroundColor: Theme.of(
- context,
- ).extension<StackColors>()!.buttonBackPrimary,
- foregroundColor: Theme.of(
- context,
- ).extension<StackColors>()!.buttonTextPrimary,
- ),
- icon: const Icon(Icons.add),
- label: const Text('Create Your First Masternode'),
+ const SizedBox(height: 24),
+ Row(
+ mainAxisSize: .min,
+ mainAxisAlignment: .center,
+ children: [
+ PrimaryButton(
+ label: "Create Your First Masternode",
+ horizontalContentPadding: 16,
+ buttonHeight: Util.isDesktop ? .l : null,
+ onPressed: () {
+ if (Util.isDesktop) {
+ _showDesktopCreateMasternodeDialog();
+ } else {
+ Navigator.of(context).pushNamed(
+ CreateMasternodeView.routeName,
+ arguments: widget.walletId,
+ );
+ }
+ },
+ ),
+ ],
),
],
),
@@ -451,11 +489,12 @@ class _MasternodesHomeViewState extends ConsumerState<MasternodesHomeView> {
);
}
- void _showCreateMasternodeDialog() {
+ void _showDesktopCreateMasternodeDialog() {
showDialog<void>(
context: context,
barrierDismissible: true,
- builder: (context) => _CreateMasternodeDialog(wallet: _wallet),
+ builder: (context) =>
+ SDialog(child: CreateMasternodeView(firoWalletId: widget.walletId)),
);
}
@@ -468,251 +507,6 @@ class _MasternodesHomeViewState extends ConsumerState<MasternodesHomeView> {
}
}
-class _CreateMasternodeDialog extends StatefulWidget {
- const _CreateMasternodeDialog({required this.wallet});
-
- final FiroWallet wallet;
-
- @override
- State<_CreateMasternodeDialog> createState() =>
- _CreateMasternodeDialogState();
-}
-
-class _CreateMasternodeDialogState extends State<_CreateMasternodeDialog> {
- final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
- final TextEditingController _ipAndPortController = TextEditingController();
- final TextEditingController _operatorPubKeyController =
- TextEditingController();
- final TextEditingController _votingAddressController =
- TextEditingController();
- final TextEditingController _operatorRewardController = TextEditingController(
- text: "0",
- );
- final TextEditingController _payoutAddressController =
- TextEditingController();
- bool _isRegistering = false;
- String? _errorMessage;
-
- @override
- void dispose() {
- _ipAndPortController.dispose();
- _operatorPubKeyController.dispose();
- _votingAddressController.dispose();
- _operatorRewardController.dispose();
- _payoutAddressController.dispose();
- super.dispose();
- }
-
- @override
- Widget build(BuildContext context) {
- final stack = Theme.of(context).extension<StackColors>()!;
- final spendable = widget.wallet.info.cachedBalance.spendable;
- final spendableFiro = spendable.decimal;
- final threshold = Decimal.fromInt(1000);
- final canRegister = spendableFiro >= threshold;
- final availableCount = (spendableFiro ~/ threshold).toInt();
-
- return AlertDialog(
- backgroundColor: stack.popupBG,
- title: const Text('Create Masternode'),
- content: SizedBox(
- width: 500,
- child: Form(
- key: _formKey,
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- if (!canRegister)
- Container(
- width: double.infinity,
- margin: const EdgeInsets.only(bottom: 12),
- padding: const EdgeInsets.all(12),
- decoration: BoxDecoration(
- color: stack.textFieldErrorBG,
- borderRadius: BorderRadius.circular(8),
- ),
- child: Text(
- 'Insufficient funds to register a masternode. You need at least 1000 public FIRO.',
- style: STextStyles.w600_14(
- context,
- ).copyWith(color: stack.textDark),
- ),
- )
- else
- Container(
- width: double.infinity,
- margin: const EdgeInsets.only(bottom: 12),
- padding: const EdgeInsets.all(12),
- decoration: BoxDecoration(
- color: stack.textFieldSuccessBG,
- borderRadius: BorderRadius.circular(8),
- ),
- child: Text(
- 'You can register $availableCount masternode(s).',
- style: STextStyles.w600_14(
- context,
- ).copyWith(color: stack.textDark),
- ),
- ),
- if (_errorMessage != null)
- Container(
- width: double.infinity,
- margin: const EdgeInsets.only(bottom: 12),
- padding: const EdgeInsets.all(12),
- decoration: BoxDecoration(
- color: stack.textFieldErrorBG,
- borderRadius: BorderRadius.circular(8),
- ),
- child: Text(
- 'Registration failed: $_errorMessage',
- style: STextStyles.w600_14(
- context,
- ).copyWith(color: stack.textDark),
- ),
- ),
- TextFormField(
- controller: _ipAndPortController,
- decoration: const InputDecoration(
- labelText: 'IP:Port',
- hintText: '123.45.67.89:8168',
- ),
- validator: (v) {
- if (v == null || v.trim().isEmpty) return 'Required';
- final parts = v.split(':');
- if (parts.length != 2) return 'Format must be ip:port';
- if (int.tryParse(parts[1]) == null) return 'Invalid port';
- return null;
- },
- ),
- const SizedBox(height: 8),
- TextFormField(
- controller: _operatorPubKeyController,
- decoration: const InputDecoration(
- labelText: 'Operator public key (BLS)',
- ),
- validator: (v) =>
- (v == null || v.trim().isEmpty) ? 'Required' : null,
- ),
- const SizedBox(height: 8),
- TextFormField(
- controller: _votingAddressController,
- decoration: const InputDecoration(
- labelText: 'Voting address (optional)',
- hintText: 'Defaults to owner address',
- ),
- ),
- const SizedBox(height: 8),
- TextFormField(
- controller: _operatorRewardController,
- decoration: const InputDecoration(
- labelText: 'Operator reward (%)',
- hintText: '0',
- ),
- keyboardType: TextInputType.number,
- ),
- const SizedBox(height: 8),
- TextFormField(
- controller: _payoutAddressController,
- decoration: const InputDecoration(labelText: 'Payout address'),
- validator: (v) =>
- (v == null || v.trim().isEmpty) ? 'Required' : null,
- ),
- ],
- ),
- ),
- ),
- actions: [
- TextButton(
- onPressed: _isRegistering ? null : () => Navigator.of(context).pop(),
- child: const Text('Cancel'),
- ),
- FilledButton(
- onPressed: _isRegistering || !canRegister
- ? null
- : _registerMasternode,
- style: FilledButton.styleFrom(
- backgroundColor: stack.buttonBackPrimary,
- foregroundColor: stack.buttonTextPrimary,
- ),
- child: _isRegistering
- ? const SizedBox(
- width: 16,
- height: 16,
- child: CircularProgressIndicator(strokeWidth: 2),
- )
- : const Text('Create'),
- ),
- ],
- );
- }
-
- Future<void> _registerMasternode() async {
- setState(() {
- _isRegistering = true;
- _errorMessage = null; // Clear any previous error
- });
-
- try {
- final parts = _ipAndPortController.text.trim().split(':');
- final ip = parts[0];
- final port = int.parse(parts[1]);
- final operatorPubKey = _operatorPubKeyController.text.trim();
- final votingAddress = _votingAddressController.text.trim();
- final operatorReward = _operatorRewardController.text.trim().isNotEmpty
- ? (double.parse(_operatorRewardController.text.trim()) * 100).floor()
- : 0;
- final payoutAddress = _payoutAddressController.text.trim();
-
- final txId = await widget.wallet.registerMasternode(
- ip,
- port,
- operatorPubKey,
- votingAddress,
- operatorReward,
- payoutAddress,
- );
-
- if (!mounted) return;
-
- // Get the parent navigator context before popping
- final navigator = Navigator.of(context, rootNavigator: Util.isDesktop);
- navigator.pop();
-
- Logging.instance.i('Masternode registration submitted: $txId');
-
- // Show success dialog after frame is complete to ensure navigation stack is correct
- if (!mounted) return;
- WidgetsBinding.instance.addPostFrameCallback((_) {
- if (!mounted) return;
- showDialog<void>(
- context: context,
- barrierDismissible: true,
- useRootNavigator: Util.isDesktop,
- builder: (_) => StackOkDialog(
- title: 'Masternode Registration Submitted',
- message:
- 'Masternode registration submitted, your masternode will appear in the list after the tx is confirmed.\n\nTransaction ID: $txId',
- desktopPopRootNavigator: Util.isDesktop,
- ),
- );
- });
- } catch (e, s) {
- Logging.instance.e(
- "Masternode registration failed",
- error: e,
- stackTrace: s,
- );
-
- if (!mounted) return;
-
- setState(() {
- _errorMessage = e.toString();
- _isRegistering = false;
- });
- }
- }
-}
-
class _MasternodeInfoDialog extends StatelessWidget {
const _MasternodeInfoDialog({required this.node});
diff --git a/lib/pages/masternodes/sub_widgets/register_masternode_form.dart b/lib/pages/masternodes/sub_widgets/register_masternode_form.dart
new file mode 100644
index 0000000..22f9638
--- /dev/null
+++ b/lib/pages/masternodes/sub_widgets/register_masternode_form.dart
@@ -0,0 +1,282 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+
+import '../../../providers/global/wallets_provider.dart';
+import '../../../themes/stack_colors.dart';
+import '../../../utilities/amount/amount.dart';
+import '../../../utilities/if_not_already.dart';
+import '../../../utilities/logger.dart';
+import '../../../utilities/show_loading.dart';
+import '../../../utilities/text_styles.dart';
+import '../../../utilities/util.dart';
+import '../../../wallets/isar/providers/wallet_info_provider.dart';
+import '../../../wallets/wallet/impl/firo_wallet.dart';
+import '../../../widgets/desktop/primary_button.dart';
+import '../../../widgets/desktop/secondary_button.dart';
+import '../../../widgets/rounded_container.dart';
+import '../../../widgets/stack_dialog.dart';
+import '../../../widgets/textfields/adaptive_text_field.dart';
+
+class RegisterMasternodeForm extends ConsumerStatefulWidget {
+ const RegisterMasternodeForm({super.key, required this.firoWalletId});
+
+ final String firoWalletId;
+
+ @override
+ ConsumerState<RegisterMasternodeForm> createState() =>
+ _RegisterMasternodeFormState();
+}
+
+class _RegisterMasternodeFormState
+ extends ConsumerState<RegisterMasternodeForm> {
+ late final Amount _masternodeThreshold;
+
+ final _ipAndPortController = TextEditingController();
+ final _operatorPubKeyController = TextEditingController();
+ final _votingAddressController = TextEditingController();
+ final _operatorRewardController = TextEditingController(text: "0");
+ final _payoutAddressController = TextEditingController();
+
+ TextStyle _getStyle(BuildContext context) {
+ return Util.isDesktop
+ ? STextStyles.desktopTextExtraExtraSmall(context).copyWith(
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.textFieldActiveSearchIconRight,
+ )
+ : STextStyles.smallMed12(context);
+ }
+
+ late final VoidCallback _register;
+
+ bool _enableCreateButton = false;
+
+ void _validate() {
+ if (mounted) {
+ setState(() {
+ _enableCreateButton = [
+ _ipAndPortController.text.trim().isNotEmpty,
+ _operatorPubKeyController.text.trim().isNotEmpty,
+ _operatorRewardController.text.trim().isNotEmpty,
+ _payoutAddressController.text.trim().isNotEmpty,
+ ].every((e) => e);
+ });
+ }
+ }
+
+ Future<String> _registerMasternode() async {
+ final parts = _ipAndPortController.text.trim().split(':');
+ final ip = parts[0];
+ final port = int.parse(parts[1]);
+ final operatorPubKey = _operatorPubKeyController.text.trim();
+ final votingAddress = _votingAddressController.text.trim();
+ final operatorReward = _operatorRewardController.text.trim().isNotEmpty
+ ? (double.parse(_operatorRewardController.text.trim()) * 100).floor()
+ : 0;
+ final payoutAddress = _payoutAddressController.text.trim();
+
+ final wallet =
+ ref.read(pWallets).getWallet(widget.firoWalletId) as FiroWallet;
+
+ final txId = await wallet.registerMasternode(
+ ip,
+ port,
+ operatorPubKey,
+ votingAddress,
+ operatorReward,
+ payoutAddress,
+ );
+
+ Logging.instance.i('Masternode registration submitted: $txId');
+
+ return txId;
+ }
+
+ @override
+ void initState() {
+ super.initState();
+ final coin = ref.read(pWalletCoin(widget.firoWalletId));
+ _masternodeThreshold = Amount.fromDecimal(
+ kMasterNodeValue,
+ fractionDigits: coin.fractionDigits,
+ );
+
+ _register = IfNotAlreadyAsync<void>(() async {
+ Exception? ex;
+
+ final txId = await showLoading(
+ whileFuture: _registerMasternode(),
+ context: context,
+ message: "Creating and submitting masternode registration...",
+ delay: const Duration(seconds: 1),
+ onException: (e) => ex = e,
+ );
+
+ if (mounted) {
+ final String title;
+ String message;
+ if (ex != null || txId == null) {
+ message = ex?.toString().trim() ?? "Unknown error: txId=$txId";
+ const exceptionPrefix = "Exception:";
+ while (message.startsWith(exceptionPrefix) &&
+ message.length > exceptionPrefix.length) {
+ message = message.substring(exceptionPrefix.length).trim();
+ }
+ title = "Registration failed";
+ } else {
+ title = "Masternode Registration Submitted";
+ message =
+ "Masternode registration submitted, your masternode will "
+ "appear in the list after the tx is confirmed.\n\nTransaction"
+ " ID: $txId";
+ }
+
+ await showDialog<void>(
+ context: context,
+ builder: (_) => StackOkDialog(
+ title: title,
+ message: message,
+ desktopPopRootNavigator: Util.isDesktop,
+ maxWidth: Util.isDesktop ? 400 : null,
+ ),
+ );
+ }
+ }).execute;
+ }
+
+ @override
+ void dispose() {
+ _ipAndPortController.dispose();
+ _operatorPubKeyController.dispose();
+ _votingAddressController.dispose();
+ _operatorRewardController.dispose();
+ _payoutAddressController.dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final stack = Theme.of(context).extension<StackColors>()!;
+ final spendableFiro = ref.watch(
+ pWalletBalance(widget.firoWalletId).select((s) => s.spendable),
+ );
+ final canRegister = spendableFiro >= _masternodeThreshold;
+ final availableCount = (spendableFiro.raw ~/ _masternodeThreshold.raw)
+ .toInt();
+
+ final infoColor = canRegister
+ ? stack.snackBarTextSuccess
+ : stack.snackBarTextError;
+ final infoColorBG = canRegister
+ ? stack.snackBarBackSuccess
+ : stack.snackBarBackError;
+
+ final infoMessage = canRegister
+ ? "You can register $availableCount masternode(s)."
+ : "Insufficient funds to register a masternode. "
+ "You need at least 1000 public FIRO.";
+
+ return Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Flexible(
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Flexible(
+ child: RoundedContainer(
+ color: infoColorBG,
+ child: Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Text(
+ infoMessage,
+ style: STextStyles.w600_14(
+ context,
+ ).copyWith(color: infoColor),
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+
+ SizedBox(height: Util.isDesktop ? 24 : 16),
+
+ SelectableText("IP:Port", style: _getStyle(context)),
+ SizedBox(height: Util.isDesktop ? 10 : 8),
+ AdaptiveTextField(
+ controller: _ipAndPortController,
+ showPasteClearButton: true,
+ maxLines: 1,
+ onChangedComprehensive: (_) => _validate(),
+ ),
+ SizedBox(height: Util.isDesktop ? 24 : 16),
+
+ SelectableText("Operator public key (BLS)", style: _getStyle(context)),
+ SizedBox(height: Util.isDesktop ? 10 : 8),
+ AdaptiveTextField(
+ controller: _operatorPubKeyController,
+ showPasteClearButton: true,
+ maxLines: 1,
+ onChangedComprehensive: (_) => _validate(),
+ ),
+ SizedBox(height: Util.isDesktop ? 24 : 16),
+
+ SelectableText("Voting address (optional)", style: _getStyle(context)),
+ SizedBox(height: Util.isDesktop ? 10 : 8),
+ AdaptiveTextField(
+ controller: _votingAddressController,
+ showPasteClearButton: true,
+ maxLines: 1,
+ labelText: "Defaults to owner address",
+ onChangedComprehensive: (_) => _validate(),
+ ),
+ SizedBox(height: Util.isDesktop ? 24 : 16),
+
+ SelectableText("Operator reward (%)", style: _getStyle(context)),
+ SizedBox(height: Util.isDesktop ? 10 : 8),
+ AdaptiveTextField(
+ controller: _operatorRewardController,
+ showPasteClearButton: true,
+ maxLines: 1,
+ onChangedComprehensive: (_) => _validate(),
+ ),
+ SizedBox(height: Util.isDesktop ? 24 : 16),
+
+ SelectableText("Payout address", style: _getStyle(context)),
+ SizedBox(height: Util.isDesktop ? 10 : 8),
+ AdaptiveTextField(
+ controller: _payoutAddressController,
+ showPasteClearButton: true,
+ maxLines: 1,
+ onChangedComprehensive: (_) => _validate(),
+ ),
+
+ Util.isDesktop ? const SizedBox(height: 32) : const Spacer(),
+
+ Row(
+ children: [
+ Expanded(
+ child: SecondaryButton(
+ label: "Cancel",
+ onPressed: Navigator.of(context).pop,
+ buttonHeight: Util.isDesktop ? .l : null,
+ ),
+ ),
+ SizedBox(width: Util.isDesktop ? 24 : 16),
+ Expanded(
+ child: PrimaryButton(
+ label: "Create",
+ enabled: _enableCreateButton,
+ onPressed: _enableCreateButton ? _register : null,
+ buttonHeight: Util.isDesktop ? .l : null,
+ ),
+ ),
+ ],
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/route_generator.dart b/lib/route_generator.dart
index 4961b23..483d829 100644
--- a/lib/route_generator.dart
+++ b/lib/route_generator.dart
@@ -29,8 +29,8 @@ import 'models/keys/key_data_interface.dart';
import 'models/keys/view_only_wallet_data.dart';
import 'models/paynym/paynym_account_lite.dart';
import 'models/send_view_auto_fill_data.dart';
-import 'pages/add_wallet_views/add_token_view/add_custom_token_view.dart';
import 'pages/add_wallet_views/add_token_view/add_custom_solana_token_view.dart';
+import 'pages/add_wallet_views/add_token_view/add_custom_token_view.dart';
import 'pages/add_wallet_views/add_token_view/edit_wallet_tokens_view.dart';
import 'pages/add_wallet_views/add_wallet_view/add_wallet_view.dart';
import 'pages/add_wallet_views/create_or_restore_wallet_view/create_or_restore_wallet_view.dart';
@@ -44,8 +44,8 @@ import 'pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_walle
import 'pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart';
import 'pages/add_wallet_views/restore_wallet_view/restore_view_only_wallet_view.dart';
import 'pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart';
-import 'pages/add_wallet_views/select_wallet_for_token_view.dart';
import 'pages/add_wallet_views/select_wallet_for_sol_token_view.dart';
+import 'pages/add_wallet_views/select_wallet_for_token_view.dart';
import 'pages/add_wallet_views/verify_recovery_phrase_view/verify_recovery_phrase_view.dart';
import 'pages/address_book_views/address_book_view.dart';
import 'pages/address_book_views/subviews/add_address_book_entry_view.dart';
@@ -77,6 +77,8 @@ import 'pages/generic/single_field_edit_view.dart';
import 'pages/home_view/home_view.dart';
import 'pages/intro_view.dart';
import 'pages/manage_favorites_view/manage_favorites_view.dart';
+import 'pages/masternodes/create_masternode_view.dart';
+import 'pages/masternodes/masternodes_home_view.dart';
import 'pages/monkey/monkey_view.dart';
import 'pages/namecoin_names/buy_domain_view.dart';
import 'pages/namecoin_names/confirm_name_transaction_view.dart';
@@ -155,6 +157,7 @@ import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_setting
import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/rbf_settings_view.dart';
import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/rename_wallet_view.dart';
import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/spark_info.dart';
+import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/spark_view_key_view.dart';
import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart';
import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/xpub_view.dart';
import 'pages/signing/signing_view.dart';
@@ -163,7 +166,6 @@ import 'pages/spark_names/buy_spark_name_view.dart';
import 'pages/spark_names/confirm_spark_name_transaction_view.dart';
import 'pages/spark_names/spark_names_home_view.dart';
import 'pages/spark_names/sub_widgets/spark_name_details.dart';
-import 'pages/masternodes/masternodes_home_view.dart';
import 'pages/special/firo_rescan_recovery_error_dialog.dart';
import 'pages/stack_privacy_calls.dart';
import 'pages/token_view/my_tokens_view.dart';
@@ -235,7 +237,6 @@ import 'wallets/wallet/wallet.dart';
import 'wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart';
import 'widgets/choose_coin_view.dart';
import 'widgets/frost_scaffold.dart';
-import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/spark_view_key_view.dart';
/*
* This file contains all the routes for the app.
@@ -908,6 +909,16 @@ class RouteGenerator {
}
return _routeError("${settings.name} invalid args: ${args.toString()}");
+ case CreateMasternodeView.routeName:
+ if (args is String) {
+ return getRoute(
+ shouldUseMaterialRoute: useMaterialPageRoute,
+ builder: (_) => CreateMasternodeView(firoWalletId: args),
+ settings: RouteSettings(name: settings.name),
+ );
+ }
+ return _routeError("${settings.name} invalid args: ${args.toString()}");
+
case BuySparkNameView.routeName:
if (args is ({String walletId, String name})) {
return getRoute(
diff --git a/lib/wallets/wallet/impl/firo_wallet.dart b/lib/wallets/wallet/impl/firo_wallet.dart
index 7954a76..ef93f38 100644
--- a/lib/wallets/wallet/impl/firo_wallet.dart
+++ b/lib/wallets/wallet/impl/firo_wallet.dart
@@ -69,7 +69,7 @@ class MasternodeInfo {
});
}
-final _masterNodeValue = Decimal.fromInt(1000); // full value (not sats)
+final kMasterNodeValue = Decimal.fromInt(1000); // full value (not sats)
class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
with
@@ -724,7 +724,7 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
// Fall back to locked in case network call fails
blocked =
Amount.fromDecimal(
- _masterNodeValue,
+ kMasterNodeValue,
fractionDigits: cryptoCurrency.fractionDigits,
).raw ==
BigInt.from(jsonUTXO["value"] as int);
@@ -921,7 +921,7 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
) async {
if (info.cachedBalance.spendable <
Amount.fromDecimal(
- _masterNodeValue,
+ kMasterNodeValue,
fractionDigits: cryptoCurrency.fractionDigits,
)) {
throw Exception(
@@ -1074,7 +1074,7 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
address: collateralAddress.value,
addressType: AddressType.p2pkh,
amount: Amount.fromDecimal(
- _masterNodeValue,
+ kMasterNodeValue,
fractionDigits: cryptoCurrency.fractionDigits,
),
isChange: false,
@@ -1212,7 +1212,7 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
final List<String> r = [];
final rawMasterNodeAmount = Amount.fromDecimal(
- _masterNodeValue,
+ kMasterNodeValue,
fractionDigits: cryptoCurrency.fractionDigits,
).raw.toInt();
Why this scored 12/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.