Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions lib/pages/masternodes/create_masternode_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,18 @@ class CreateMasternodeView extends ConsumerStatefulWidget {
const CreateMasternodeView({
super.key,
required this.firoWalletId,
required this.collateralTxid,
required this.collateralVout,
required this.collateralAddress,
this.popTxidOnSuccess = true,
});

static const routeName = "/createMasternodeView";

final String firoWalletId;
final String collateralTxid;
final int collateralVout;
final String collateralAddress;
final bool popTxidOnSuccess;

@override
Expand Down Expand Up @@ -107,6 +113,9 @@ class _CreateMasternodeDialogState extends ConsumerState<CreateMasternodeView> {
),
child: RegisterMasternodeForm(
firoWalletId: widget.firoWalletId,
collateralTxid: widget.collateralTxid,
collateralVout: widget.collateralVout,
collateralAddress: widget.collateralAddress,
onRegistrationSuccess: (txid) {
if (widget.popTxidOnSuccess && mounted) {
Navigator.of(context, rootNavigator: Util.isDesktop).pop(txid);
Expand Down
246 changes: 217 additions & 29 deletions lib/pages/masternodes/masternodes_home_view.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';

import 'package:isar_community/isar.dart';
import '../../providers/global/wallets_provider.dart';
import '../../themes/stack_colors.dart';
import '../../utilities/amount/amount.dart';
import '../../utilities/assets.dart';
import '../../utilities/logger.dart';
import '../../utilities/text_styles.dart';
import '../../utilities/util.dart';
import '../../wallets/isar/models/wallet_info.dart';
import '../../wallets/wallet/impl/firo_wallet.dart';
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
import '../../widgets/desktop/desktop_app_bar.dart';
Expand All @@ -32,17 +36,211 @@ class MasternodesHomeView extends ConsumerStatefulWidget {
_MasternodesHomeViewState();
}

class _MasternodesHomeViewState extends ConsumerState<MasternodesHomeView> {
class _MasternodesHomeViewState extends ConsumerState<MasternodesHomeView>
{
late Future<List<MasternodeInfo>> _masternodesFuture;
bool _hasPromptedForCollateral = false;
bool _isCheckingForCollateral = false;

Future<void> _showDesktopCreateMasternodeDialog() async {
final txid = await showDialog<Object>(
context: context,
barrierDismissible: true,
builder: (context) =>
SDialog(child: CreateMasternodeView(firoWalletId: widget.walletId)),
Set<String> _dismissedCollateral(FiroWallet wallet) {
final raw = wallet.info.otherData[WalletInfoKeys.firoMasternodeCollateralDismissed];
if (raw is! List) {
return {};
}
return raw.whereType<String>().toSet();
}

Future<void> _persistDismissedCollateral(
FiroWallet wallet,
String txid,
int vout,
) async {
final set = _dismissedCollateral(wallet);
set.add("$txid:$vout");
await wallet.info.updateOtherData(
newEntries: {
WalletInfoKeys.firoMasternodeCollateralDismissed: set.toList(),
},
isar: wallet.mainDB.isar,
);
_handleSuccessTxid(txid);
}

Future<({String txid, int vout, String address})?> _findCollateralUtxo()
async {
final wallet =
ref.read(pWallets).getWallet(widget.walletId) as FiroWallet;
final utxos = await wallet.mainDB.getUTXOs(widget.walletId).findAll();
final currentChainHeight = await wallet.chainHeight;
final masternodeRaw = Amount.fromDecimal(
kMasterNodeValue,
fractionDigits: wallet.cryptoCurrency.fractionDigits,
).raw.toInt();

for (final utxo in utxos) {
if (utxo.value == masternodeRaw &&
!utxo.isBlocked &&
utxo.used != true &&
utxo.isConfirmed(
currentChainHeight,
wallet.cryptoCurrency.minConfirms,
wallet.cryptoCurrency.minCoinbaseConfirms,
) &&
utxo.address != null) {
return (txid: utxo.txid, vout: utxo.vout, address: utxo.address!);
}
}
return null;
}

Future<void> _createMasternode() async {
final collateral = await _findCollateralUtxo();
if (!mounted) {
return;
}

if (collateral == null) {
await showDialog<void>(
context: context,
builder: (_) => StackOkDialog(
title: "No collateral found",
message:
"A masternode needs one confirmed, unblocked transparent "
"UTXO of exactly 1000 FIRO.\n\n"
"Total balance above 1000 FIRO is not enough if no single "
"1000 output exists. Also ensure fee is not subtracted from "
"the recipient amount when sending to yourself.",
desktopPopRootNavigator: Util.isDesktop,
maxWidth: Util.isDesktop ? 400 : null,
),
);
return;
}

if (Util.isDesktop) {
final txid = await showDialog<Object>(
context: context,
barrierDismissible: true,
builder: (context) => SDialog(
child: CreateMasternodeView(
firoWalletId: widget.walletId,
collateralTxid: collateral.txid,
collateralVout: collateral.vout,
collateralAddress: collateral.address,
),
),
);
_handleSuccessTxid(txid);
} else {
final txid = await Navigator.of(context).pushNamed(
CreateMasternodeView.routeName,
arguments: {
'walletId': widget.walletId,
'collateralTxid': collateral.txid,
'collateralVout': collateral.vout,
'collateralAddress': collateral.address,
},
);
_handleSuccessTxid(txid);
}
}

Future<void> _maybePromptForExistingCollateral() async {
if (_hasPromptedForCollateral || _isCheckingForCollateral || !mounted) {
return;
}
_isCheckingForCollateral = true;

try {
final collateral = await _findCollateralUtxo();
if (collateral == null || !mounted) {
return;
}

final wallet = ref.read(pWallets).getWallet(widget.walletId) as FiroWallet;
final dismissed = _dismissedCollateral(wallet);
final collateralKey = "${collateral.txid}:${collateral.vout}";
if (dismissed.contains(collateralKey)) {
return;
}

_hasPromptedForCollateral = true;

final wantsMN = await showDialog<bool>(
context: context,
barrierDismissible: true,
builder: (ctx) => StackDialog(
title: "Register Masternode?",
message:
"A 1000 FIRO collateral UTXO was found in your wallet. "
"Would you like to register a masternode now?",
leftButton: TextButton(
style: Theme.of(ctx)
.extension<StackColors>()!
.getSecondaryEnabledButtonStyle(ctx),
child: Text(
"Later",
style: STextStyles.button(
ctx,
).copyWith(
color: Theme.of(ctx).extension<StackColors>()!.accentColorDark,
),
),
onPressed: () => Navigator.of(ctx).pop(false),
),
rightButton: TextButton(
style: Theme.of(ctx)
.extension<StackColors>()!
.getPrimaryEnabledButtonStyle(ctx),
child: Text(
"Register",
style: STextStyles.button(ctx),
),
onPressed: () => Navigator.of(ctx).pop(true),
),
),
);

if (wantsMN == false) {
await _persistDismissedCollateral(
wallet,
collateral.txid,
collateral.vout,
);
}

if (wantsMN != true || !mounted) {
return;
}

if (Util.isDesktop) {
final txid = await showDialog<Object>(
context: context,
barrierDismissible: true,
builder: (context) => SDialog(
child: CreateMasternodeView(
firoWalletId: widget.walletId,
collateralTxid: collateral.txid,
collateralVout: collateral.vout,
collateralAddress: collateral.address,
),
),
);
_handleSuccessTxid(txid);
} else {
final txid = await Navigator.of(context).pushNamed(
CreateMasternodeView.routeName,
arguments: {
'walletId': widget.walletId,
'collateralTxid': collateral.txid,
'collateralVout': collateral.vout,
'collateralAddress': collateral.address,
},
);
_handleSuccessTxid(txid);
}
} finally {
_isCheckingForCollateral = false;
}
}

void _handleSuccessTxid(Object? txid) {
Expand Down Expand Up @@ -75,12 +273,18 @@ class _MasternodesHomeViewState extends ConsumerState<MasternodesHomeView> {
void initState() {
super.initState();

// TODO polling and update on successful registration
_masternodesFuture =
(ref.read(pWallets).getWallet(widget.walletId) as FiroWallet)
.getMyMasternodes();

WidgetsBinding.instance.addPostFrameCallback((_) {
unawaited(_maybePromptForExistingCollateral());
});
}

@override
void dispose() => super.dispose();

@override
Widget build(BuildContext context) {
final isDesktop = Util.isDesktop;
Expand Down Expand Up @@ -143,7 +347,7 @@ class _MasternodesHomeViewState extends ConsumerState<MasternodesHomeView> {
.srcIn,
),
),
onPressed: _showDesktopCreateMasternodeDialog,
onPressed: _createMasternode,
),
),
)
Expand Down Expand Up @@ -184,13 +388,7 @@ class _MasternodesHomeViewState extends ConsumerState<MasternodesHomeView> {
width: 20,
height: 20,
),
onPressed: () async {
final txid = await Navigator.of(context).pushNamed(
CreateMasternodeView.routeName,
arguments: widget.walletId,
);
_handleSuccessTxid(txid);
},
onPressed: _createMasternode,
),
),
),
Expand Down Expand Up @@ -229,17 +427,7 @@ class _MasternodesHomeViewState extends ConsumerState<MasternodesHomeView> {
label: "Create Your First Masternode",
horizontalContentPadding: 16,
buttonHeight: Util.isDesktop ? .l : null,
onPressed: () async {
if (Util.isDesktop) {
await _showDesktopCreateMasternodeDialog();
} else {
final txid = await Navigator.of(context).pushNamed(
CreateMasternodeView.routeName,
arguments: widget.walletId,
);
_handleSuccessTxid(txid);
}
},
onPressed: _createMasternode,
),
],
),
Expand Down
Loading