Nano rep page + derivation fixes (#1655)

* minor nano derivation fixes * rep page fixes * ignore non-critical receive block errors * really be sure the derivation type is set during wallet creation

Matthew Fosse committed Sep 4, 2024 at 19:41 UTC 783f1a234936dd75989956596159dd8a16a53b6f
4 files changed +80 -59
cw_nano/lib/nano_client.dart
+18 -14
@@ -466,21 +466,25 @@ class NanoClient {
466
467 blocks = blocks as Map<String, dynamic>;
468
469 - // confirm all receivable blocks:
470 - for (final blockHash in blocks.keys) {
471 - final block = blocks[blockHash];
472 - final String amountRaw = block["amount"] as String;
473 - await receiveBlock(
474 - blockHash: blockHash,
475 - amountRaw: amountRaw,
476 - privateKey: privateKey,
477 - destinationAddress: destinationAddress,
478 - );
479 - // a bit of a hack:
480 - await Future<void>.delayed(const Duration(seconds: 2));
469 + try {
470 + // confirm all receivable blocks:
471 + for (final blockHash in blocks.keys) {
472 + final block = blocks[blockHash];
473 + final String amountRaw = block["amount"] as String;
474 + await receiveBlock(
475 + blockHash: blockHash,
476 + amountRaw: amountRaw,
477 + privateKey: privateKey,
478 + destinationAddress: destinationAddress,
479 + );
480 + // a bit of a hack:
481 + await Future<void>.delayed(const Duration(seconds: 2));
482 + }
483 + return blocks.keys.length;
484 + } catch (_) {
485 + // we failed to confirm all receivable blocks for w/e reason (PoW / node outage / etc)
486 + return 0;
487 }
482 -
483 - return blocks.keys.length;
488 }
489
490 void stop() {}
cw_nano/lib/nano_wallet_service.dart
+24 -7
@@ -14,8 +14,11 @@ import 'package:bip39/bip39.dart' as bip39;
14 import 'package:nanodart/nanodart.dart';
15 import 'package:nanoutil/nanoutil.dart';
16
17 -class NanoWalletService extends WalletService<NanoNewWalletCredentials,
18 - NanoRestoreWalletFromSeedCredentials, NanoRestoreWalletFromKeysCredentials, NanoNewWalletCredentials> {
17 +class NanoWalletService extends WalletService<
18 + NanoNewWalletCredentials,
19 + NanoRestoreWalletFromSeedCredentials,
20 + NanoRestoreWalletFromKeysCredentials,
21 + NanoNewWalletCredentials> {
22 NanoWalletService(this.walletInfoSource, this.isDirect);
23
24 final Box<WalletInfo> walletInfoSource;
@@ -33,8 +36,12 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
36 String seedKey = NanoSeeds.generateSeed();
37 String mnemonic = NanoDerivations.standardSeedToMnemonic(seedKey);
38
36 - // ensure default if not present:
37 - credentials.walletInfo!.derivationInfo ??= DerivationInfo(derivationType: DerivationType.nano);
39 + // should never happen but just in case:
40 + if (credentials.walletInfo!.derivationInfo == null) {
41 + credentials.walletInfo!.derivationInfo = DerivationInfo(derivationType: DerivationType.nano);
42 + } else if (credentials.walletInfo!.derivationInfo!.derivationType == null) {
43 + credentials.walletInfo!.derivationInfo!.derivationType = DerivationType.nano;
44 + }
45
46 final wallet = NanoWallet(
47 walletInfo: credentials.walletInfo!,
@@ -86,7 +93,8 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
93 }
94
95 @override
89 - Future<NanoWallet> restoreFromKeys(NanoRestoreWalletFromKeysCredentials credentials, {bool? isTestnet}) async {
96 + Future<NanoWallet> restoreFromKeys(NanoRestoreWalletFromKeysCredentials credentials,
97 + {bool? isTestnet}) async {
98 if (credentials.seedKey.contains(' ')) {
99 throw Exception("Invalid key!");
100 } else {
@@ -106,6 +114,13 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
114 }
115 }
116
117 + // should never happen but just in case:
118 + if (credentials.walletInfo!.derivationInfo == null) {
119 + credentials.walletInfo!.derivationInfo = DerivationInfo(derivationType: DerivationType.nano);
120 + } else if (credentials.walletInfo!.derivationInfo!.derivationType == null) {
121 + credentials.walletInfo!.derivationInfo!.derivationType = DerivationType.nano;
122 + }
123 +
124 final wallet = await NanoWallet(
125 password: credentials.password!,
126 mnemonic: mnemonic ?? credentials.seedKey,
@@ -119,11 +134,13 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
134
135 @override
136 Future<NanoWallet> restoreFromHardwareWallet(NanoNewWalletCredentials credentials) {
122 - throw UnimplementedError("Restoring a Nano wallet from a hardware wallet is not yet supported!");
137 + throw UnimplementedError(
138 + "Restoring a Nano wallet from a hardware wallet is not yet supported!");
139 }
140
141 @override
126 - Future<NanoWallet> restoreFromSeed(NanoRestoreWalletFromSeedCredentials credentials, {bool? isTestnet}) async {
142 + Future<NanoWallet> restoreFromSeed(NanoRestoreWalletFromSeedCredentials credentials,
143 + {bool? isTestnet}) async {
144 if (credentials.mnemonic.contains(' ')) {
145 if (!bip39.validateMnemonic(credentials.mnemonic)) {
146 throw nm.NanoMnemonicIsIncorrectException();
lib/src/screens/nano/nano_change_rep_page.dart
+31 -33
@@ -40,13 +40,11 @@ class NanoChangeRepPage extends BasePage {
40 (node) => node.account == currentRepAccount,
41 orElse: () => N2Node(
42 account: currentRepAccount,
43 - alias: currentRepAccount,
43 score: 0,
44 uptime: "???",
45 weight: 0,
46 ),
47 );
49 -
48 return currentNode;
49 }
50
@@ -57,9 +55,7 @@ class NanoChangeRepPage extends BasePage {
55 child: FutureBuilder(
56 future: nano!.getN2Reps(_wallet),
57 builder: (context, snapshot) {
60 - if (snapshot.data == null) {
61 - return SizedBox();
62 - }
58 + final reps = snapshot.data ?? [];
59
60 return Container(
61 padding: EdgeInsets.only(left: 24, right: 24),
@@ -101,29 +97,35 @@ class NanoChangeRepPage extends BasePage {
97 ),
98 _buildSingleRepresentative(
99 context,
104 - getCurrentRepNode(snapshot.data as List<N2Node>),
100 + getCurrentRepNode(reps),
101 isList: false,
102 + divider: false,
103 ),
107 - Divider(height: 20),
108 - Container(
109 - margin: EdgeInsets.only(top: 12),
110 - child: Text(
111 - S.current.nano_pick_new_rep,
112 - style: TextStyle(
113 - fontSize: 16,
114 - fontWeight: FontWeight.w700,
104 + if (reps.isNotEmpty) ...[
105 + Divider(height: 20),
106 + Container(
107 + margin: EdgeInsets.only(top: 12),
108 + child: Text(
109 + S.current.nano_pick_new_rep,
110 + style: TextStyle(
111 + fontSize: 16,
112 + fontWeight: FontWeight.w700,
113 + ),
114 ),
115 ),
117 - ),
116 + Divider(height: 20),
117 + ],
118 ],
119 ),
120 ],
121 ),
122 contentPadding: EdgeInsets.only(bottom: 24),
123 content: Container(
124 - child: Column(
125 - children: _getRepresentativeWidgets(context, snapshot.data as List<N2Node>),
126 - ),
124 + child: reps.isNotEmpty
125 + ? Column(
126 + children: _getRepresentativeWidgets(context, reps),
127 + )
128 + : SizedBox(),
129 ),
130 bottomSectionPadding: EdgeInsets.only(bottom: 24),
131 bottomSection: Observer(
@@ -207,19 +209,22 @@ class NanoChangeRepPage extends BasePage {
209 final List<Widget> ret = [];
210 for (final N2Node node in list) {
211 if (node.alias != null && node.alias!.trim().isNotEmpty) {
210 - ret.add(_buildSingleRepresentative(context, node));
212 + bool divider = node != list.first;
213 + ret.add(_buildSingleRepresentative(context, node, divider: divider, isList: true));
214 }
215 }
216 return ret;
217 }
218
216 - Widget _buildSingleRepresentative(BuildContext context, N2Node rep, {bool isList = true}) {
219 + Widget _buildSingleRepresentative(
220 + BuildContext context,
221 + N2Node rep, {
222 + bool isList = true,
223 + bool divider = false,
224 + }) {
225 return Column(
226 children: <Widget>[
219 - if (isList)
220 - Divider(
221 - height: 2,
222 - ),
227 + if (divider) Divider(height: 2),
228 TextButton(
229 style: TextButton.styleFrom(
230 padding: EdgeInsets.zero,
@@ -244,11 +249,11 @@ class NanoChangeRepPage extends BasePage {
249 crossAxisAlignment: CrossAxisAlignment.start,
250 children: <Widget>[
251 Text(
247 - _sanitizeAlias(rep.alias),
252 + rep.alias ?? rep.account!,
253 style: TextStyle(
254 color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
255 fontWeight: FontWeight.w700,
251 - fontSize: 18,
256 + fontSize: rep.alias == null ? 14 : 18,
257 ),
258 ),
259 Container(
@@ -337,11 +342,4 @@ class NanoChangeRepPage extends BasePage {
342 ],
343 );
344 }
340 -
341 - String _sanitizeAlias(String? alias) {
342 - if (alias != null) {
343 - return alias.replaceAll(RegExp(r'[^a-zA-Z_.!?_;:-]'), '');
344 - }
345 - return '';
346 - }
345 }
lib/view_model/wallet_restore_view_model.dart
+7 -5
@@ -42,7 +42,8 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
42 type == WalletType.tron,
43 isButtonEnabled = false,
44 mode = WalletRestoreMode.seed,
45 - super(appStore, walletInfoSource, walletCreationService, seedSettingsViewModel, type: type, isRecovery: true) {
45 + super(appStore, walletInfoSource, walletCreationService, seedSettingsViewModel,
46 + type: type, isRecovery: true) {
47 switch (type) {
48 case WalletType.monero:
49 availableModes = WalletRestoreMode.values;
@@ -194,10 +195,11 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
195
196 case WalletType.nano:
197 return nano!.createNanoRestoreWalletFromKeysCredentials(
197 - name: name,
198 - password: password,
199 - seedKey: options['private_key'] as String,
200 - derivationType: options["derivationType"] as DerivationType);
198 + name: name,
199 + password: password,
200 + seedKey: options['private_key'] as String,
201 + derivationType: derivationInfo!.derivationType!,
202 + );
203 case WalletType.polygon:
204 return polygon!.createPolygonRestoreWalletFromPrivateKey(
205 name: name,