1+ import 'dart:async' ;
2+
13import 'package:flutter/material.dart' ;
24import 'package:flutter_riverpod/flutter_riverpod.dart' ;
35import 'package:flutter_svg/svg.dart' ;
4-
56import '../../providers/global/wallets_provider.dart' ;
67import '../../themes/stack_colors.dart' ;
8+ import '../../utilities/amount/amount.dart' ;
79import '../../utilities/assets.dart' ;
810import '../../utilities/logger.dart' ;
911import '../../utilities/text_styles.dart' ;
1012import '../../utilities/util.dart' ;
13+ import '../../models/isar/models/blockchain_data/utxo.dart' ;
14+ import '../../wallets/isar/models/wallet_info.dart' ;
1115import '../../wallets/wallet/impl/firo_wallet.dart' ;
1216import '../../widgets/custom_buttons/app_bar_icon_button.dart' ;
1317import '../../widgets/desktop/desktop_app_bar.dart' ;
@@ -34,15 +38,206 @@ class MasternodesHomeView extends ConsumerStatefulWidget {
3438
3539class _MasternodesHomeViewState extends ConsumerState <MasternodesHomeView > {
3640 late Future <List <MasternodeInfo >> _masternodesFuture;
41+ bool _hasPromptedForCollateral = false ;
42+ bool _isCheckingForCollateral = false ;
43+
44+ Set <String > _dismissedCollateral (FiroWallet wallet) {
45+ final raw =
46+ wallet.info.otherData[WalletInfoKeys .firoMasternodeCollateralDismissed];
47+ if (raw is ! List ) {
48+ return {};
49+ }
50+ return raw.whereType <String >().toSet ();
51+ }
3752
38- Future <void > _showDesktopCreateMasternodeDialog () async {
39- final txid = await showDialog <Object >(
40- context: context,
41- barrierDismissible: true ,
42- builder: (context) =>
43- SDialog (child: CreateMasternodeView (firoWalletId: widget.walletId)),
53+ Future <void > _persistDismissedCollateral (
54+ FiroWallet wallet,
55+ String txid,
56+ int vout,
57+ ) async {
58+ final set = _dismissedCollateral (wallet);
59+ set .add ("$txid :$vout " );
60+ await wallet.info.updateOtherData (
61+ newEntries: {
62+ WalletInfoKeys .firoMasternodeCollateralDismissed: set .toList (),
63+ },
64+ isar: wallet.mainDB.isar,
4465 );
45- _handleSuccessTxid (txid);
66+ }
67+
68+ Future <({String txid, int vout, String address})?>
69+ _findCollateralUtxo () async {
70+ final wallet = ref.read (pWallets).getWallet (widget.walletId) as FiroWallet ;
71+ final List <UTXO > utxos =
72+ await (wallet.mainDB.getUTXOs (widget.walletId) as dynamic ).findAll ()
73+ as List <UTXO >;
74+ final currentChainHeight = await wallet.chainHeight;
75+ final masternodeRaw = Amount .fromDecimal (
76+ kMasterNodeValue,
77+ fractionDigits: wallet.cryptoCurrency.fractionDigits,
78+ ).raw.toInt ();
79+
80+ for (final utxo in utxos) {
81+ if (utxo.value == masternodeRaw &&
82+ ! utxo.isBlocked &&
83+ utxo.used != true &&
84+ utxo.isConfirmed (
85+ currentChainHeight,
86+ wallet.cryptoCurrency.minConfirms,
87+ wallet.cryptoCurrency.minCoinbaseConfirms,
88+ ) &&
89+ utxo.address != null ) {
90+ return (txid: utxo.txid, vout: utxo.vout, address: utxo.address! );
91+ }
92+ }
93+ return null ;
94+ }
95+
96+ Future <void > _createMasternode () async {
97+ final collateral = await _findCollateralUtxo ();
98+ if (! mounted) {
99+ return ;
100+ }
101+
102+ if (collateral == null ) {
103+ await showDialog <void >(
104+ context: context,
105+ builder: (_) => StackOkDialog (
106+ title: "No collateral found" ,
107+ message:
108+ "A masternode needs one confirmed, unblocked transparent "
109+ "UTXO of exactly 1000 FIRO.\n\n "
110+ "Total balance above 1000 FIRO is not enough if no single "
111+ "1000 output exists. Also ensure fee is not subtracted from "
112+ "the recipient amount when sending to yourself." ,
113+ desktopPopRootNavigator: Util .isDesktop,
114+ maxWidth: Util .isDesktop ? 400 : null ,
115+ ),
116+ );
117+ return ;
118+ }
119+
120+ if (Util .isDesktop) {
121+ final txid = await showDialog <Object >(
122+ context: context,
123+ barrierDismissible: true ,
124+ builder: (context) => SDialog (
125+ child: CreateMasternodeView (
126+ firoWalletId: widget.walletId,
127+ collateralTxid: collateral.txid,
128+ collateralVout: collateral.vout,
129+ collateralAddress: collateral.address,
130+ ),
131+ ),
132+ );
133+ _handleSuccessTxid (txid);
134+ } else {
135+ final txid = await Navigator .of (context).pushNamed (
136+ CreateMasternodeView .routeName,
137+ arguments: {
138+ 'walletId' : widget.walletId,
139+ 'collateralTxid' : collateral.txid,
140+ 'collateralVout' : collateral.vout,
141+ 'collateralAddress' : collateral.address,
142+ },
143+ );
144+ _handleSuccessTxid (txid);
145+ }
146+ }
147+
148+ Future <void > _maybePromptForExistingCollateral () async {
149+ if (_hasPromptedForCollateral || _isCheckingForCollateral || ! mounted) {
150+ return ;
151+ }
152+ _isCheckingForCollateral = true ;
153+
154+ try {
155+ final collateral = await _findCollateralUtxo ();
156+ if (collateral == null || ! mounted) {
157+ return ;
158+ }
159+
160+ final wallet =
161+ ref.read (pWallets).getWallet (widget.walletId) as FiroWallet ;
162+ final dismissed = _dismissedCollateral (wallet);
163+ final collateralKey = "${collateral .txid }:${collateral .vout }" ;
164+ if (dismissed.contains (collateralKey)) {
165+ return ;
166+ }
167+
168+ _hasPromptedForCollateral = true ;
169+
170+ final wantsMN = await showDialog <bool >(
171+ context: context,
172+ barrierDismissible: true ,
173+ builder: (ctx) => StackDialog (
174+ title: "Register Masternode?" ,
175+ message:
176+ "A 1000 FIRO collateral UTXO was found in your wallet. "
177+ "Would you like to register a masternode now?" ,
178+ leftButton: TextButton (
179+ style: Theme .of (
180+ ctx,
181+ ).extension < StackColors > ()! .getSecondaryEnabledButtonStyle (ctx),
182+ child: Text (
183+ "Later" ,
184+ style: STextStyles .button (ctx).copyWith (
185+ color: Theme .of (ctx).extension < StackColors > ()! .accentColorDark,
186+ ),
187+ ),
188+ onPressed: () => Navigator .of (ctx).pop (false ),
189+ ),
190+ rightButton: TextButton (
191+ style: Theme .of (
192+ ctx,
193+ ).extension < StackColors > ()! .getPrimaryEnabledButtonStyle (ctx),
194+ child: Text ("Register" , style: STextStyles .button (ctx)),
195+ onPressed: () => Navigator .of (ctx).pop (true ),
196+ ),
197+ ),
198+ );
199+
200+ if (wantsMN == false || wantsMN == null ) {
201+ await _persistDismissedCollateral (
202+ wallet,
203+ collateral.txid,
204+ collateral.vout,
205+ );
206+ }
207+
208+ if (wantsMN != true || ! mounted) {
209+ return ;
210+ }
211+
212+ if (Util .isDesktop) {
213+ final txid = await showDialog <Object >(
214+ context: context,
215+ barrierDismissible: true ,
216+ builder: (context) => SDialog (
217+ child: CreateMasternodeView (
218+ firoWalletId: widget.walletId,
219+ collateralTxid: collateral.txid,
220+ collateralVout: collateral.vout,
221+ collateralAddress: collateral.address,
222+ ),
223+ ),
224+ );
225+ _handleSuccessTxid (txid);
226+ } else {
227+ final txid = await Navigator .of (context).pushNamed (
228+ CreateMasternodeView .routeName,
229+ arguments: {
230+ 'walletId' : widget.walletId,
231+ 'collateralTxid' : collateral.txid,
232+ 'collateralVout' : collateral.vout,
233+ 'collateralAddress' : collateral.address,
234+ },
235+ );
236+ _handleSuccessTxid (txid);
237+ }
238+ } finally {
239+ _isCheckingForCollateral = false ;
240+ }
46241 }
47242
48243 void _handleSuccessTxid (Object ? txid) {
@@ -75,10 +270,13 @@ class _MasternodesHomeViewState extends ConsumerState<MasternodesHomeView> {
75270 void initState () {
76271 super .initState ();
77272
78- // TODO polling and update on successful registration
79273 _masternodesFuture =
80274 (ref.read (pWallets).getWallet (widget.walletId) as FiroWallet )
81275 .getMyMasternodes ();
276+
277+ WidgetsBinding .instance.addPostFrameCallback ((_) {
278+ unawaited (_maybePromptForExistingCollateral ());
279+ });
82280 }
83281
84282 @override
@@ -143,7 +341,7 @@ class _MasternodesHomeViewState extends ConsumerState<MasternodesHomeView> {
143341 .srcIn,
144342 ),
145343 ),
146- onPressed: _showDesktopCreateMasternodeDialog ,
344+ onPressed: _createMasternode ,
147345 ),
148346 ),
149347 )
@@ -184,13 +382,7 @@ class _MasternodesHomeViewState extends ConsumerState<MasternodesHomeView> {
184382 width: 20 ,
185383 height: 20 ,
186384 ),
187- onPressed: () async {
188- final txid = await Navigator .of (context).pushNamed (
189- CreateMasternodeView .routeName,
190- arguments: widget.walletId,
191- );
192- _handleSuccessTxid (txid);
193- },
385+ onPressed: _createMasternode,
194386 ),
195387 ),
196388 ),
@@ -229,17 +421,7 @@ class _MasternodesHomeViewState extends ConsumerState<MasternodesHomeView> {
229421 label: "Create Your First Masternode" ,
230422 horizontalContentPadding: 16 ,
231423 buttonHeight: Util .isDesktop ? .l : null ,
232- onPressed: () async {
233- if (Util .isDesktop) {
234- await _showDesktopCreateMasternodeDialog ();
235- } else {
236- final txid = await Navigator .of (context).pushNamed (
237- CreateMasternodeView .routeName,
238- arguments: widget.walletId,
239- );
240- _handleSuccessTxid (txid);
241- }
242- },
424+ onPressed: _createMasternode,
243425 ),
244426 ],
245427 ),
0 commit comments