From e8f53766a08016198ae4d29aad6a4d08dae06298 Mon Sep 17 00:00:00 2001 From: M Date: Thu, 12 Nov 2020 18:31:53 +0200 Subject: [PATCH 01/70] Added selection of new wallet type. Returned support of bitcoin wallet. --- lib/bitcoin/bitcoin_wallet.dart | 8 ++- .../bitcoin_wallet_creation_credentials.dart | 12 ++-- lib/bitcoin/bitcoin_wallet_service.dart | 19 +++++- lib/core/wallet_credentials.dart | 2 +- lib/di.dart | 8 ++- lib/router.dart | 46 +++++--------- .../new_wallet/new_wallet_type_page.dart | 60 ++++++++++++++++--- .../screens/wallet_list/wallet_list_page.dart | 4 +- lib/view_model/wallet_creation_vm.dart | 2 +- lib/view_model/wallet_new_vm.dart | 6 +- 10 files changed, 110 insertions(+), 57 deletions(-) diff --git a/lib/bitcoin/bitcoin_wallet.dart b/lib/bitcoin/bitcoin_wallet.dart index e43c0aeaa..33e69daf7 100644 --- a/lib/bitcoin/bitcoin_wallet.dart +++ b/lib/bitcoin/bitcoin_wallet.dart @@ -58,6 +58,7 @@ abstract class BitcoinWalletBase extends WalletBase with Store { {@required String password, @required String name, @required String dirPath, + @required WalletInfo walletInfo, String jsonSource}) { final data = json.decode(jsonSource) as Map; final mnemonic = data['mnemonic'] as String; @@ -83,7 +84,8 @@ abstract class BitcoinWalletBase extends WalletBase with Store { name: name, accountIndex: accountIndex, initialAddresses: addresses, - initialBalance: balance); + initialBalance: balance, + walletInfo: walletInfo); } static BitcoinWallet build( @@ -91,6 +93,7 @@ abstract class BitcoinWalletBase extends WalletBase with Store { @required String password, @required String name, @required String dirPath, + @required WalletInfo walletInfo, List initialAddresses, BitcoinBalance initialBalance, int accountIndex = 0}) { @@ -107,7 +110,8 @@ abstract class BitcoinWalletBase extends WalletBase with Store { accountIndex: accountIndex, initialAddresses: initialAddresses, initialBalance: initialBalance, - transactionHistory: history); + transactionHistory: history, + walletInfo: walletInfo); } @override diff --git a/lib/bitcoin/bitcoin_wallet_creation_credentials.dart b/lib/bitcoin/bitcoin_wallet_creation_credentials.dart index 051a5700e..f8df42df0 100644 --- a/lib/bitcoin/bitcoin_wallet_creation_credentials.dart +++ b/lib/bitcoin/bitcoin_wallet_creation_credentials.dart @@ -1,21 +1,23 @@ import 'package:cake_wallet/core/wallet_credentials.dart'; +import 'package:cake_wallet/entities/wallet_info.dart'; class BitcoinNewWalletCredentials extends WalletCredentials { - BitcoinNewWalletCredentials({String name}) : super(name: name); + BitcoinNewWalletCredentials({String name, WalletInfo walletInfo}) + : super(name: name, walletInfo: walletInfo); } class BitcoinRestoreWalletFromSeedCredentials extends WalletCredentials { BitcoinRestoreWalletFromSeedCredentials( - {String name, String password, this.mnemonic}) - : super(name: name, password: password); + {String name, String password, this.mnemonic, WalletInfo walletInfo}) + : super(name: name, password: password, walletInfo: walletInfo); final String mnemonic; } class BitcoinRestoreWalletFromWIFCredentials extends WalletCredentials { BitcoinRestoreWalletFromWIFCredentials( - {String name, String password, this.wif}) - : super(name: name, password: password); + {String name, String password, this.wif, WalletInfo walletInfo}) + : super(name: name, password: password, walletInfo: walletInfo); final String wif; } diff --git a/lib/bitcoin/bitcoin_wallet_service.dart b/lib/bitcoin/bitcoin_wallet_service.dart index 2e9a1c305..9272f0cb1 100644 --- a/lib/bitcoin/bitcoin_wallet_service.dart +++ b/lib/bitcoin/bitcoin_wallet_service.dart @@ -2,15 +2,22 @@ import 'dart:io'; import 'package:bip39/bip39.dart' as bip39; import 'package:cake_wallet/bitcoin/file.dart'; import 'package:cake_wallet/bitcoin/bitcoin_wallet_creation_credentials.dart'; +import 'package:cake_wallet/core/wallet_base.dart'; import 'package:cake_wallet/core/wallet_service.dart'; import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart'; import 'package:cake_wallet/entities/pathForWallet.dart'; +import 'package:cake_wallet/entities/wallet_info.dart'; import 'package:cake_wallet/entities/wallet_type.dart'; +import 'package:hive/hive.dart'; class BitcoinWalletService extends WalletService< BitcoinNewWalletCredentials, BitcoinRestoreWalletFromSeedCredentials, BitcoinRestoreWalletFromWIFCredentials> { + BitcoinWalletService(this.walletInfoSource); + + final Box walletInfoSource; + @override Future create(BitcoinNewWalletCredentials credentials) async { final dirPath = await pathForWalletDir( @@ -19,7 +26,8 @@ class BitcoinWalletService extends WalletService< dirPath: dirPath, mnemonic: bip39.generateMnemonic(), password: credentials.password, - name: credentials.name); + name: credentials.name, + walletInfo: credentials.walletInfo); await wallet.save(); await wallet.init(); @@ -37,11 +45,15 @@ class BitcoinWalletService extends WalletService< await pathForWalletDir(name: name, type: WalletType.bitcoin); final walletPath = '$walletDirPath/$name'; final walletJSONRaw = await read(path: walletPath, password: password); + final walletInfo = walletInfoSource.values.firstWhere( + (info) => info.id == WalletBase.idFor(name, WalletType.bitcoin), + orElse: () => null); final wallet = BitcoinWalletBase.fromJSON( password: password, name: name, dirPath: walletDirPath, - jsonSource: walletJSONRaw); + jsonSource: walletJSONRaw, + walletInfo: walletInfo); await wallet.init(); return wallet; @@ -68,7 +80,8 @@ class BitcoinWalletService extends WalletService< dirPath: dirPath, name: credentials.name, password: credentials.password, - mnemonic: credentials.mnemonic); + mnemonic: credentials.mnemonic, + walletInfo: credentials.walletInfo); await wallet.save(); await wallet.init(); diff --git a/lib/core/wallet_credentials.dart b/lib/core/wallet_credentials.dart index 3998bb005..ddfafab1a 100644 --- a/lib/core/wallet_credentials.dart +++ b/lib/core/wallet_credentials.dart @@ -1,7 +1,7 @@ import 'package:cake_wallet/entities/wallet_info.dart'; abstract class WalletCredentials { - WalletCredentials({this.name, this.password, this.height}); + WalletCredentials({this.name, this.password, this.height, this.walletInfo}); final String name; final int height; diff --git a/lib/di.dart b/lib/di.dart index 9dad3efb3..b3c8f1d6b 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -14,6 +14,7 @@ import 'package:cake_wallet/src/screens/contact/contact_page.dart'; import 'package:cake_wallet/src/screens/exchange_trade/exchange_confirm_page.dart'; import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_page.dart'; import 'package:cake_wallet/src/screens/faq/faq_page.dart'; +import 'package:cake_wallet/src/screens/new_wallet/new_wallet_type_page.dart'; import 'package:cake_wallet/src/screens/nodes/node_create_or_edit_page.dart'; import 'package:cake_wallet/src/screens/nodes/nodes_list_page.dart'; import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart'; @@ -353,7 +354,7 @@ Future setup( getIt.registerFactory(() => MoneroWalletService(walletInfoSource)); - getIt.registerFactory(() => BitcoinWalletService()); + getIt.registerFactory(() => BitcoinWalletService(walletInfoSource)); getIt.registerFactoryParam( (WalletType param1, __) { @@ -397,4 +398,9 @@ Future setup( transactionInfo, getIt.get().shouldSaveRecipientAddress, transactionDescriptionBox)); + + getIt.registerFactoryParam( + (para1, param2) => NewWalletTypePage(getIt.get(), + onTypeSelected: para1, isNewWallet: param2)); } diff --git a/lib/router.dart b/lib/router.dart index acad61d04..aa3f2cf71 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -60,29 +60,18 @@ Route createRoute(RouteSettings settings) { case Routes.newWalletFromWelcome: return CupertinoPageRoute( - builder: (_) => getIt.get(param1: - (PinCodeState context, dynamic _) async { - try { - context.changeProcessText( - 'Creating new wallet'); // FIXME: Unnamed constant - final newWalletVM = - getIt.get(param1: WalletType.monero); - await newWalletVM.create( - options: 'English'); // FIXME: Unnamed constant - context.hideProgressText(); - await Navigator.of(context.context) - .pushNamed(Routes.seed, arguments: true); - } catch (e) { - context.changeProcessText('Error: ${e.toString()}'); - } - }), + builder: (_) => getIt.get( + param1: (PinCodeState context, dynamic _) => + Navigator.of(context.context) + .pushNamed(Routes.newWalletType)), fullscreenDialog: true); case Routes.newWalletType: return CupertinoPageRoute( - builder: (_) => NewWalletTypePage( - onTypeSelected: (context, type) => Navigator.of(context) - .pushNamed(Routes.newWallet, arguments: type))); + builder: (_) => getIt.get( + param1: (BuildContext context, WalletType _) => + Navigator.of(context).pushNamed(Routes.seed, arguments: true), + param2: true)); case Routes.newWallet: final type = WalletType.monero; // settings.arguments as WalletType; @@ -104,11 +93,11 @@ Route createRoute(RouteSettings settings) { case Routes.restoreWalletType: return CupertinoPageRoute( - builder: (_) => NewWalletTypePage( - onTypeSelected: (context, type) => Navigator.of(context) - .pushNamed(Routes.restoreWalletOptions, arguments: type), - isNewWallet: false, - )); + builder: (_) => getIt.get( + param1: (BuildContext context, WalletType type) => + Navigator.of(context) + .pushNamed(Routes.restoreWalletFromSeed, arguments: type), + param2: false)); case Routes.restoreOptions: final type = settings.arguments as WalletType; @@ -146,7 +135,7 @@ Route createRoute(RouteSettings settings) { return CupertinoPageRoute( builder: (_) => getIt.get( param1: (PinCodeState context, dynamic _) => - Navigator.pushNamed(context.context, Routes.restoreWallet)), + Navigator.pushNamed(context.context, Routes.restoreWalletType)), fullscreenDialog: true); case Routes.seed: @@ -160,12 +149,7 @@ Route createRoute(RouteSettings settings) { getIt.get(param1: WalletType.monero)); case Routes.restoreWalletFromSeed: - // final args = settings.arguments as List; - final type = WalletType.monero; //args.first as WalletType; - // final language = type == WalletType.monero - // ? args[1] as String - // : LanguageList.english; - + final type = settings.arguments as WalletType; return CupertinoPageRoute( builder: (_) => RestoreWalletFromSeedPage(type: type)); diff --git a/lib/src/screens/new_wallet/new_wallet_type_page.dart b/lib/src/screens/new_wallet/new_wallet_type_page.dart index cf3b3b896..5751b11bd 100644 --- a/lib/src/screens/new_wallet/new_wallet_type_page.dart +++ b/lib/src/screens/new_wallet/new_wallet_type_page.dart @@ -1,6 +1,11 @@ +import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/entities/wallet_type.dart'; +import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/store/settings_store.dart'; +import 'package:cake_wallet/utils/show_bar.dart'; +import 'package:cake_wallet/view_model/wallet_new_vm.dart'; +import 'package:flushbar/flushbar.dart'; import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; import 'package:cake_wallet/generated/i18n.dart'; @@ -10,25 +15,28 @@ import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart'; class NewWalletTypePage extends BasePage { - NewWalletTypePage({this.onTypeSelected, this.isNewWallet = true}); + NewWalletTypePage(this.walletNewVM, + {this.onTypeSelected, this.isNewWallet}); final void Function(BuildContext, WalletType) onTypeSelected; final bool isNewWallet; + final WalletNewVM walletNewVM; @override - String get title => isNewWallet - ? S.current.new_wallet - : S.current.wallet_list_restore_wallet; + String get title => + isNewWallet ? S.current.new_wallet : S.current.wallet_list_restore_wallet; @override Widget body(BuildContext context) => - WalletTypeForm(onTypeSelected: onTypeSelected); + WalletTypeForm(walletNewVM, isNewWallet, onTypeSelected: onTypeSelected); } class WalletTypeForm extends StatefulWidget { - WalletTypeForm({this.onTypeSelected}); + WalletTypeForm(this.walletNewVM, this.isNewWallet, {this.onTypeSelected}); final void Function(BuildContext, WalletType) onTypeSelected; + final WalletNewVM walletNewVM; + final bool isNewWallet; @override WalletTypeFormState createState() => WalletTypeFormState(); @@ -42,10 +50,12 @@ class WalletTypeFormState extends State { final bitcoinIcon = Image.asset('assets/images/bitcoin.png', height: 24, width: 24); final walletTypeImage = Image.asset('assets/images/wallet_type.png'); - final walletTypeLightImage = Image.asset('assets/images/wallet_type_light.png'); + final walletTypeLightImage = + Image.asset('assets/images/wallet_type_light.png'); WalletType selected; List types; + Flushbar _progressBar; @override void initState() { @@ -56,7 +66,8 @@ class WalletTypeFormState extends State { @override Widget build(BuildContext context) { final walletImage = getIt.get().isDarkTheme - ? walletTypeImage : walletTypeLightImage; + ? walletTypeImage + : walletTypeLightImage; return Container( padding: EdgeInsets.only(top: 24), @@ -94,7 +105,7 @@ class WalletTypeFormState extends State { ), bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24), bottomSection: PrimaryButton( - onPressed: () => widget.onTypeSelected(context, selected), + onPressed: () => onTypeSelected(), text: S.of(context).seed_language_next, color: Colors.green, textColor: Colors.white, @@ -114,4 +125,35 @@ class WalletTypeFormState extends State { return null; } } + + Future onTypeSelected() async { + if (!widget.isNewWallet) { + widget.onTypeSelected(context, selected); + return; + } + + try { + _changeProcessText(S.of(context).creating_new_wallet); + widget.walletNewVM.type = selected; + await widget.walletNewVM + .create(options: 'English'); // FIXME: Unnamed constant + await _progressBar?.dismiss(); + final state = widget.walletNewVM.state; + + if (state is ExecutedSuccessfullyState) { + widget.onTypeSelected(context, selected); + } + + if (state is FailureState) { + _changeProcessText( + S.of(context).creating_new_wallet_error(state.error)); + } + } catch (e) { + _changeProcessText(S.of(context).creating_new_wallet_error(e.toString())); + } + } + + void _changeProcessText(String text) { + _progressBar = createBar(text, duration: null)..show(context); + } } diff --git a/lib/src/screens/wallet_list/wallet_list_page.dart b/lib/src/screens/wallet_list/wallet_list_page.dart index 5162b1c5b..37745cdec 100644 --- a/lib/src/screens/wallet_list/wallet_list_page.dart +++ b/lib/src/screens/wallet_list/wallet_list_page.dart @@ -165,7 +165,7 @@ class WalletListBodyState extends State { ), bottomSection: Column(children: [ PrimaryImageButton( - onPressed: () => _generateNewWallet(), + onPressed: () => Navigator.of(context).pushNamed(Routes.newWalletType), image: newWalletImage, text: S.of(context).wallet_list_create_new_wallet, color: Theme.of(context).accentTextTheme.subtitle.decorationColor, @@ -175,7 +175,7 @@ class WalletListBodyState extends State { SizedBox(height: 10.0), PrimaryImageButton( onPressed: () => - Navigator.of(context).pushNamed(Routes.restoreWallet), + Navigator.of(context).pushNamed(Routes.restoreWalletType), image: restoreWalletImage, text: S.of(context).wallet_list_restore_wallet, color: Theme.of(context).accentTextTheme.caption.color, diff --git a/lib/view_model/wallet_creation_vm.dart b/lib/view_model/wallet_creation_vm.dart index d29002f2b..3509f8c07 100644 --- a/lib/view_model/wallet_creation_vm.dart +++ b/lib/view_model/wallet_creation_vm.dart @@ -27,7 +27,7 @@ abstract class WalletCreationVMBase with Store { @observable ExecutionState state; - final WalletType type; + WalletType type; final bool isRecovery; final Box _walletInfoSource; final AppStore _appStore; diff --git a/lib/view_model/wallet_new_vm.dart b/lib/view_model/wallet_new_vm.dart index 5ebf05f1b..c21f1b8c1 100644 --- a/lib/view_model/wallet_new_vm.dart +++ b/lib/view_model/wallet_new_vm.dart @@ -43,6 +43,8 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { } @override - Future process(WalletCredentials credentials) async => - _walletCreationService.create(credentials); + Future process(WalletCredentials credentials) async { + _walletCreationService.changeWalletType(type: type); + return _walletCreationService.create(credentials); + } } From 9ad76376d9cf5f1b1a69467c9a286d4b32404aed Mon Sep 17 00:00:00 2001 From: M Date: Fri, 27 Nov 2020 11:52:05 +0200 Subject: [PATCH 02/70] Mnemonic for electrum --- lib/bitcoin/bitcoin_mnemonic.dart | 2297 +++++++++++++++++++++++ lib/bitcoin/bitcoin_wallet.dart | 9 +- lib/bitcoin/bitcoin_wallet_service.dart | 4 +- pubspec.lock | 16 +- pubspec.yaml | 3 + 5 files changed, 2322 insertions(+), 7 deletions(-) create mode 100644 lib/bitcoin/bitcoin_mnemonic.dart diff --git a/lib/bitcoin/bitcoin_mnemonic.dart b/lib/bitcoin/bitcoin_mnemonic.dart new file mode 100644 index 000000000..ea7a66920 --- /dev/null +++ b/lib/bitcoin/bitcoin_mnemonic.dart @@ -0,0 +1,2297 @@ +import 'dart:convert'; +import 'dart:math'; +import 'dart:typed_data'; +import 'package:crypto/crypto.dart'; +import 'package:unorm_dart/unorm_dart.dart' as unorm; +import 'package:cryptography/cryptography.dart' as cryptography; + +const segwit = '100'; +final wordlist = englishWordlist; + +Uint8List randomBytes(int length, {bool secure = false}) { + assert(length > 0); + + final random = secure ? Random.secure() : Random(); + final ret = Uint8List(length); + + for (var i = 0; i < length; i++) { + ret[i] = random.nextInt(256); + } + + return ret; +} + +double logBase(num x, num base) => log(x) / log(base); + +String mnemonicEncode(int i) { + var _i = i; + final n = wordlist.length; + final words = []; + + while (_i > 0) { + final x = i % n; + _i = (i / n).floor(); + words.add(wordlist[x]); + } + + return words.join(' '); +} + +int mnemonicDecode(String seed) { + var i = 0; + final n = wordlist.length; + final words = seed.split(' '); + + while (words.length > 0) { + final word = words.removeLast(); + final k = wordlist.indexOf(word); + i = i * n + k; + } + + return i; +} + +bool isNewSeed(String seed, {String prefix = segwit}) { + final hmacSha512 = Hmac(sha512, utf8.encode('Seed version')); + final digest = hmacSha512.convert(utf8.encode(normalizeText(seed))); + final hx = digest.toString(); + return hx.startsWith(prefix.toLowerCase()); +} + +void maskBytes(Uint8List bytes, int bits) { + final skipCount = (bits / 8).floor(); + var lastByte = (1 << bits % 8) - 1; + + for (var i = bytes.length - 1 - skipCount; i >= 0; i--) { + bytes[i] &= lastByte; + + if (lastByte > 0) { + lastByte = 0; + } + } +} + +String bufferToBin(Uint8List data) { + final q1 = data.map((e) => e.toRadixString(2).padLeft(8, '0')); + final q2 = q1.join(''); + return q2; +} + +String encode(Uint8List data) { + final dataBitLen = data.length * 8; + final wordBitLen = logBase(wordlist.length, 2).ceil(); + final wordCount = (dataBitLen / wordBitLen).floor(); + maskBytes(data, wordCount * wordBitLen); + final bin = bufferToBin(data); + final binStr = bin.substring(bin.length - (wordCount * wordBitLen)); + final result = []; + + for (var i = 0; i < wordCount; i++) { + final wordBin = binStr.substring(i * wordBitLen, (i + 1) * wordBitLen); + result.add(wordlist[int.parse(wordBin, radix: 2)]); + } + + return result.join(' '); +} + +List prefixMatches(String source, List prefixes) { + final hmacSha512 = Hmac(sha512, utf8.encode('Seed version')); + final digest = hmacSha512.convert(utf8.encode(normalizeText(source))); + final hx = digest.toString(); + + return prefixes.map((prefix) => hx.startsWith(prefix.toLowerCase())).toList(); +} + +String generateMnemonic({int strength = 132, String prefix = segwit}) { + final wordBitlen = logBase(wordlist.length, 2).ceil(); + final wordCount = strength / wordBitlen; + final byteCount = ((wordCount * wordBitlen).ceil() / 8).ceil(); + var result = ''; + + do { + final bytes = randomBytes(byteCount); + maskBytes(bytes, strength); + result = encode(bytes); + } while (!prefixMatches(result, [prefix]).first); + + return result; +} + +Uint8List mnemonicToSeedBytes(String mnemonic, {String prefix = segwit}) { + final pbkdf2 = cryptography.Pbkdf2( + macAlgorithm: cryptography.Hmac(cryptography.sha512), + iterations: 2048, + bits: 512); + final text = normalizeText(mnemonic); + + return pbkdf2.deriveBitsSync(text.codeUnits, + nonce: cryptography.Nonce('electrum'.codeUnits)); +} + +final COMBININGCODEPOINTS = combiningcodepoints(); + +List combiningcodepoints() { + final source = '300:34e|350:36f|483:487|591:5bd|5bf|5c1|5c2|5c4|5c5|5c7|610:61a|64b:65f|670|' + + '6d6:6dc|6df:6e4|6e7|6e8|6ea:6ed|711|730:74a|7eb:7f3|816:819|81b:823|825:827|' + + '829:82d|859:85b|8d4:8e1|8e3:8ff|93c|94d|951:954|9bc|9cd|a3c|a4d|abc|acd|b3c|' + + 'b4d|bcd|c4d|c55|c56|cbc|ccd|d4d|dca|e38:e3a|e48:e4b|eb8|eb9|ec8:ecb|f18|f19|' + + 'f35|f37|f39|f71|f72|f74|f7a:f7d|f80|f82:f84|f86|f87|fc6|1037|1039|103a|108d|' + + '135d:135f|1714|1734|17d2|17dd|18a9|1939:193b|1a17|1a18|1a60|1a75:1a7c|1a7f|' + + '1ab0:1abd|1b34|1b44|1b6b:1b73|1baa|1bab|1be6|1bf2|1bf3|1c37|1cd0:1cd2|' + + '1cd4:1ce0|1ce2:1ce8|1ced|1cf4|1cf8|1cf9|1dc0:1df5|1dfb:1dff|20d0:20dc|20e1|' + + '20e5:20f0|2cef:2cf1|2d7f|2de0:2dff|302a:302f|3099|309a|a66f|a674:a67d|a69e|' + + 'a69f|a6f0|a6f1|a806|a8c4|a8e0:a8f1|a92b:a92d|a953|a9b3|a9c0|aab0|aab2:aab4|' + + 'aab7|aab8|aabe|aabf|aac1|aaf6|abed|fb1e|fe20:fe2f|101fd|102e0|10376:1037a|' + + '10a0d|10a0f|10a38:10a3a|10a3f|10ae5|10ae6|11046|1107f|110b9|110ba|11100:11102|' + + '11133|11134|11173|111c0|111ca|11235|11236|112e9|112ea|1133c|1134d|11366:1136c|' + + '11370:11374|11442|11446|114c2|114c3|115bf|115c0|1163f|116b6|116b7|1172b|11c3f|' + + '16af0:16af4|16b30:16b36|1bc9e|1d165:1d169|1d16d:1d172|1d17b:1d182|1d185:1d18b|' + + '1d1aa:1d1ad|1d242:1d244|1e000:1e006|1e008:1e018|1e01b:1e021|1e023|1e024|' + + '1e026:1e02a|1e8d0:1e8d6|1e944:1e94a'; + + return source.split('|').map((e) { + if (e.contains(':')) { + return e.split(':').map((hex) => int.parse(hex, radix: 16)); + } + + return int.parse(e, radix: 16); + }).fold([], (List acc, element) { + if (element is List) { + for (var i = element[0] as int; i <= (element[1] as int); i++) {} + } else if (element is int) { + acc.add(element); + } + + return acc; + }).toList(); +} + +String removeCombiningCharacters(String source) { + return source + .split('') + .where((char) => !COMBININGCODEPOINTS.contains(char.codeUnits.first)) + .join(''); +} + +bool isCJK(String char) { + final n = char.codeUnitAt(0); + + for (var x in CJKINTERVALS) { + final imin = x[0] as num; + final imax = x[1] as num; + + if (n >= imin && n <= imax) return true; + } + + return false; +} + +String removeCJKSpaces(String source) { + final splitted = source.split(''); + final filtered = []; + + for (var i = 0; i < splitted.length; i++) { + final char = splitted[i]; + final isSpace = char.trim() == ''; + final prevIsCJK = i != 0 && isCJK(splitted[i - 1]); + final nextIsCJK = i != splitted.length - 1 && isCJK(splitted[i + 1]); + + if (!(isSpace && prevIsCJK && nextIsCJK)) { + filtered.add(char); + } + } + + return filtered.join(''); +} + +String normalizeText(String source) { + final res = removeCombiningCharacters(unorm.nfkd(source).toLowerCase()) + .trim() + .split('/\s+/') + .join(' '); + + return removeCJKSpaces(res); +} + +const CJKINTERVALS = [ + [0x4e00, 0x9fff, 'CJK Unified Ideographs'], + [0x3400, 0x4dbf, 'CJK Unified Ideographs Extension A'], + [0x20000, 0x2a6df, 'CJK Unified Ideographs Extension B'], + [0x2a700, 0x2b73f, 'CJK Unified Ideographs Extension C'], + [0x2b740, 0x2b81f, 'CJK Unified Ideographs Extension D'], + [0xf900, 0xfaff, 'CJK Compatibility Ideographs'], + [0x2f800, 0x2fa1d, 'CJK Compatibility Ideographs Supplement'], + [0x3190, 0x319f, 'Kanbun'], + [0x2e80, 0x2eff, 'CJK Radicals Supplement'], + [0x2f00, 0x2fdf, 'CJK Radicals'], + [0x31c0, 0x31ef, 'CJK Strokes'], + [0x2ff0, 0x2fff, 'Ideographic Description Characters'], + [0xe0100, 0xe01ef, 'Variation Selectors Supplement'], + [0x3100, 0x312f, 'Bopomofo'], + [0x31a0, 0x31bf, 'Bopomofo Extended'], + [0xff00, 0xffef, 'Halfwidth and Fullwidth Forms'], + [0x3040, 0x309f, 'Hiragana'], + [0x30a0, 0x30ff, 'Katakana'], + [0x31f0, 0x31ff, 'Katakana Phonetic Extensions'], + [0x1b000, 0x1b0ff, 'Kana Supplement'], + [0xac00, 0xd7af, 'Hangul Syllables'], + [0x1100, 0x11ff, 'Hangul Jamo'], + [0xa960, 0xa97f, 'Hangul Jamo Extended A'], + [0xd7b0, 0xd7ff, 'Hangul Jamo Extended B'], + [0x3130, 0x318f, 'Hangul Compatibility Jamo'], + [0xa4d0, 0xa4ff, 'Lisu'], + [0x16f00, 0x16f9f, 'Miao'], + [0xa000, 0xa48f, 'Yi Syllables'], + [0xa490, 0xa4cf, 'Yi Radicals'], +]; + +final englishWordlist = [ + 'abandon', + 'ability', + 'able', + 'about', + 'above', + 'absent', + 'absorb', + 'abstract', + 'absurd', + 'abuse', + 'access', + 'accident', + 'account', + 'accuse', + 'achieve', + 'acid', + 'acoustic', + 'acquire', + 'across', + 'act', + 'action', + 'actor', + 'actress', + 'actual', + 'adapt', + 'add', + 'addict', + 'address', + 'adjust', + 'admit', + 'adult', + 'advance', + 'advice', + 'aerobic', + 'affair', + 'afford', + 'afraid', + 'again', + 'age', + 'agent', + 'agree', + 'ahead', + 'aim', + 'air', + 'airport', + 'aisle', + 'alarm', + 'album', + 'alcohol', + 'alert', + 'alien', + 'all', + 'alley', + 'allow', + 'almost', + 'alone', + 'alpha', + 'already', + 'also', + 'alter', + 'always', + 'amateur', + 'amazing', + 'among', + 'amount', + 'amused', + 'analyst', + 'anchor', + 'ancient', + 'anger', + 'angle', + 'angry', + 'animal', + 'ankle', + 'announce', + 'annual', + 'another', + 'answer', + 'antenna', + 'antique', + 'anxiety', + 'any', + 'apart', + 'apology', + 'appear', + 'apple', + 'approve', + 'april', + 'arch', + 'arctic', + 'area', + 'arena', + 'argue', + 'arm', + 'armed', + 'armor', + 'army', + 'around', + 'arrange', + 'arrest', + 'arrive', + 'arrow', + 'art', + 'artefact', + 'artist', + 'artwork', + 'ask', + 'aspect', + 'assault', + 'asset', + 'assist', + 'assume', + 'asthma', + 'athlete', + 'atom', + 'attack', + 'attend', + 'attitude', + 'attract', + 'auction', + 'audit', + 'august', + 'aunt', + 'author', + 'auto', + 'autumn', + 'average', + 'avocado', + 'avoid', + 'awake', + 'aware', + 'away', + 'awesome', + 'awful', + 'awkward', + 'axis', + 'baby', + 'bachelor', + 'bacon', + 'badge', + 'bag', + 'balance', + 'balcony', + 'ball', + 'bamboo', + 'banana', + 'banner', + 'bar', + 'barely', + 'bargain', + 'barrel', + 'base', + 'basic', + 'basket', + 'battle', + 'beach', + 'bean', + 'beauty', + 'because', + 'become', + 'beef', + 'before', + 'begin', + 'behave', + 'behind', + 'believe', + 'below', + 'belt', + 'bench', + 'benefit', + 'best', + 'betray', + 'better', + 'between', + 'beyond', + 'bicycle', + 'bid', + 'bike', + 'bind', + 'biology', + 'bird', + 'birth', + 'bitter', + 'black', + 'blade', + 'blame', + 'blanket', + 'blast', + 'bleak', + 'bless', + 'blind', + 'blood', + 'blossom', + 'blouse', + 'blue', + 'blur', + 'blush', + 'board', + 'boat', + 'body', + 'boil', + 'bomb', + 'bone', + 'bonus', + 'book', + 'boost', + 'border', + 'boring', + 'borrow', + 'boss', + 'bottom', + 'bounce', + 'box', + 'boy', + 'bracket', + 'brain', + 'brand', + 'brass', + 'brave', + 'bread', + 'breeze', + 'brick', + 'bridge', + 'brief', + 'bright', + 'bring', + 'brisk', + 'broccoli', + 'broken', + 'bronze', + 'broom', + 'brother', + 'brown', + 'brush', + 'bubble', + 'buddy', + 'budget', + 'buffalo', + 'build', + 'bulb', + 'bulk', + 'bullet', + 'bundle', + 'bunker', + 'burden', + 'burger', + 'burst', + 'bus', + 'business', + 'busy', + 'butter', + 'buyer', + 'buzz', + 'cabbage', + 'cabin', + 'cable', + 'cactus', + 'cage', + 'cake', + 'call', + 'calm', + 'camera', + 'camp', + 'can', + 'canal', + 'cancel', + 'candy', + 'cannon', + 'canoe', + 'canvas', + 'canyon', + 'capable', + 'capital', + 'captain', + 'car', + 'carbon', + 'card', + 'cargo', + 'carpet', + 'carry', + 'cart', + 'case', + 'cash', + 'casino', + 'castle', + 'casual', + 'cat', + 'catalog', + 'catch', + 'category', + 'cattle', + 'caught', + 'cause', + 'caution', + 'cave', + 'ceiling', + 'celery', + 'cement', + 'census', + 'century', + 'cereal', + 'certain', + 'chair', + 'chalk', + 'champion', + 'change', + 'chaos', + 'chapter', + 'charge', + 'chase', + 'chat', + 'cheap', + 'check', + 'cheese', + 'chef', + 'cherry', + 'chest', + 'chicken', + 'chief', + 'child', + 'chimney', + 'choice', + 'choose', + 'chronic', + 'chuckle', + 'chunk', + 'churn', + 'cigar', + 'cinnamon', + 'circle', + 'citizen', + 'city', + 'civil', + 'claim', + 'clap', + 'clarify', + 'claw', + 'clay', + 'clean', + 'clerk', + 'clever', + 'click', + 'client', + 'cliff', + 'climb', + 'clinic', + 'clip', + 'clock', + 'clog', + 'close', + 'cloth', + 'cloud', + 'clown', + 'club', + 'clump', + 'cluster', + 'clutch', + 'coach', + 'coast', + 'coconut', + 'code', + 'coffee', + 'coil', + 'coin', + 'collect', + 'color', + 'column', + 'combine', + 'come', + 'comfort', + 'comic', + 'common', + 'company', + 'concert', + 'conduct', + 'confirm', + 'congress', + 'connect', + 'consider', + 'control', + 'convince', + 'cook', + 'cool', + 'copper', + 'copy', + 'coral', + 'core', + 'corn', + 'correct', + 'cost', + 'cotton', + 'couch', + 'country', + 'couple', + 'course', + 'cousin', + 'cover', + 'coyote', + 'crack', + 'cradle', + 'craft', + 'cram', + 'crane', + 'crash', + 'crater', + 'crawl', + 'crazy', + 'cream', + 'credit', + 'creek', + 'crew', + 'cricket', + 'crime', + 'crisp', + 'critic', + 'crop', + 'cross', + 'crouch', + 'crowd', + 'crucial', + 'cruel', + 'cruise', + 'crumble', + 'crunch', + 'crush', + 'cry', + 'crystal', + 'cube', + 'culture', + 'cup', + 'cupboard', + 'curious', + 'current', + 'curtain', + 'curve', + 'cushion', + 'custom', + 'cute', + 'cycle', + 'dad', + 'damage', + 'damp', + 'dance', + 'danger', + 'daring', + 'dash', + 'daughter', + 'dawn', + 'day', + 'deal', + 'debate', + 'debris', + 'decade', + 'december', + 'decide', + 'decline', + 'decorate', + 'decrease', + 'deer', + 'defense', + 'define', + 'defy', + 'degree', + 'delay', + 'deliver', + 'demand', + 'demise', + 'denial', + 'dentist', + 'deny', + 'depart', + 'depend', + 'deposit', + 'depth', + 'deputy', + 'derive', + 'describe', + 'desert', + 'design', + 'desk', + 'despair', + 'destroy', + 'detail', + 'detect', + 'develop', + 'device', + 'devote', + 'diagram', + 'dial', + 'diamond', + 'diary', + 'dice', + 'diesel', + 'diet', + 'differ', + 'digital', + 'dignity', + 'dilemma', + 'dinner', + 'dinosaur', + 'direct', + 'dirt', + 'disagree', + 'discover', + 'disease', + 'dish', + 'dismiss', + 'disorder', + 'display', + 'distance', + 'divert', + 'divide', + 'divorce', + 'dizzy', + 'doctor', + 'document', + 'dog', + 'doll', + 'dolphin', + 'domain', + 'donate', + 'donkey', + 'donor', + 'door', + 'dose', + 'double', + 'dove', + 'draft', + 'dragon', + 'drama', + 'drastic', + 'draw', + 'dream', + 'dress', + 'drift', + 'drill', + 'drink', + 'drip', + 'drive', + 'drop', + 'drum', + 'dry', + 'duck', + 'dumb', + 'dune', + 'during', + 'dust', + 'dutch', + 'duty', + 'dwarf', + 'dynamic', + 'eager', + 'eagle', + 'early', + 'earn', + 'earth', + 'easily', + 'east', + 'easy', + 'echo', + 'ecology', + 'economy', + 'edge', + 'edit', + 'educate', + 'effort', + 'egg', + 'eight', + 'either', + 'elbow', + 'elder', + 'electric', + 'elegant', + 'element', + 'elephant', + 'elevator', + 'elite', + 'else', + 'embark', + 'embody', + 'embrace', + 'emerge', + 'emotion', + 'employ', + 'empower', + 'empty', + 'enable', + 'enact', + 'end', + 'endless', + 'endorse', + 'enemy', + 'energy', + 'enforce', + 'engage', + 'engine', + 'enhance', + 'enjoy', + 'enlist', + 'enough', + 'enrich', + 'enroll', + 'ensure', + 'enter', + 'entire', + 'entry', + 'envelope', + 'episode', + 'equal', + 'equip', + 'era', + 'erase', + 'erode', + 'erosion', + 'error', + 'erupt', + 'escape', + 'essay', + 'essence', + 'estate', + 'eternal', + 'ethics', + 'evidence', + 'evil', + 'evoke', + 'evolve', + 'exact', + 'example', + 'excess', + 'exchange', + 'excite', + 'exclude', + 'excuse', + 'execute', + 'exercise', + 'exhaust', + 'exhibit', + 'exile', + 'exist', + 'exit', + 'exotic', + 'expand', + 'expect', + 'expire', + 'explain', + 'expose', + 'express', + 'extend', + 'extra', + 'eye', + 'eyebrow', + 'fabric', + 'face', + 'faculty', + 'fade', + 'faint', + 'faith', + 'fall', + 'false', + 'fame', + 'family', + 'famous', + 'fan', + 'fancy', + 'fantasy', + 'farm', + 'fashion', + 'fat', + 'fatal', + 'father', + 'fatigue', + 'fault', + 'favorite', + 'feature', + 'february', + 'federal', + 'fee', + 'feed', + 'feel', + 'female', + 'fence', + 'festival', + 'fetch', + 'fever', + 'few', + 'fiber', + 'fiction', + 'field', + 'figure', + 'file', + 'film', + 'filter', + 'final', + 'find', + 'fine', + 'finger', + 'finish', + 'fire', + 'firm', + 'first', + 'fiscal', + 'fish', + 'fit', + 'fitness', + 'fix', + 'flag', + 'flame', + 'flash', + 'flat', + 'flavor', + 'flee', + 'flight', + 'flip', + 'float', + 'flock', + 'floor', + 'flower', + 'fluid', + 'flush', + 'fly', + 'foam', + 'focus', + 'fog', + 'foil', + 'fold', + 'follow', + 'food', + 'foot', + 'force', + 'forest', + 'forget', + 'fork', + 'fortune', + 'forum', + 'forward', + 'fossil', + 'foster', + 'found', + 'fox', + 'fragile', + 'frame', + 'frequent', + 'fresh', + 'friend', + 'fringe', + 'frog', + 'front', + 'frost', + 'frown', + 'frozen', + 'fruit', + 'fuel', + 'fun', + 'funny', + 'furnace', + 'fury', + 'future', + 'gadget', + 'gain', + 'galaxy', + 'gallery', + 'game', + 'gap', + 'garage', + 'garbage', + 'garden', + 'garlic', + 'garment', + 'gas', + 'gasp', + 'gate', + 'gather', + 'gauge', + 'gaze', + 'general', + 'genius', + 'genre', + 'gentle', + 'genuine', + 'gesture', + 'ghost', + 'giant', + 'gift', + 'giggle', + 'ginger', + 'giraffe', + 'girl', + 'give', + 'glad', + 'glance', + 'glare', + 'glass', + 'glide', + 'glimpse', + 'globe', + 'gloom', + 'glory', + 'glove', + 'glow', + 'glue', + 'goat', + 'goddess', + 'gold', + 'good', + 'goose', + 'gorilla', + 'gospel', + 'gossip', + 'govern', + 'gown', + 'grab', + 'grace', + 'grain', + 'grant', + 'grape', + 'grass', + 'gravity', + 'great', + 'green', + 'grid', + 'grief', + 'grit', + 'grocery', + 'group', + 'grow', + 'grunt', + 'guard', + 'guess', + 'guide', + 'guilt', + 'guitar', + 'gun', + 'gym', + 'habit', + 'hair', + 'half', + 'hammer', + 'hamster', + 'hand', + 'happy', + 'harbor', + 'hard', + 'harsh', + 'harvest', + 'hat', + 'have', + 'hawk', + 'hazard', + 'head', + 'health', + 'heart', + 'heavy', + 'hedgehog', + 'height', + 'hello', + 'helmet', + 'help', + 'hen', + 'hero', + 'hidden', + 'high', + 'hill', + 'hint', + 'hip', + 'hire', + 'history', + 'hobby', + 'hockey', + 'hold', + 'hole', + 'holiday', + 'hollow', + 'home', + 'honey', + 'hood', + 'hope', + 'horn', + 'horror', + 'horse', + 'hospital', + 'host', + 'hotel', + 'hour', + 'hover', + 'hub', + 'huge', + 'human', + 'humble', + 'humor', + 'hundred', + 'hungry', + 'hunt', + 'hurdle', + 'hurry', + 'hurt', + 'husband', + 'hybrid', + 'ice', + 'icon', + 'idea', + 'identify', + 'idle', + 'ignore', + 'ill', + 'illegal', + 'illness', + 'image', + 'imitate', + 'immense', + 'immune', + 'impact', + 'impose', + 'improve', + 'impulse', + 'inch', + 'include', + 'income', + 'increase', + 'index', + 'indicate', + 'indoor', + 'industry', + 'infant', + 'inflict', + 'inform', + 'inhale', + 'inherit', + 'initial', + 'inject', + 'injury', + 'inmate', + 'inner', + 'innocent', + 'input', + 'inquiry', + 'insane', + 'insect', + 'inside', + 'inspire', + 'install', + 'intact', + 'interest', + 'into', + 'invest', + 'invite', + 'involve', + 'iron', + 'island', + 'isolate', + 'issue', + 'item', + 'ivory', + 'jacket', + 'jaguar', + 'jar', + 'jazz', + 'jealous', + 'jeans', + 'jelly', + 'jewel', + 'job', + 'join', + 'joke', + 'journey', + 'joy', + 'judge', + 'juice', + 'jump', + 'jungle', + 'junior', + 'junk', + 'just', + 'kangaroo', + 'keen', + 'keep', + 'ketchup', + 'key', + 'kick', + 'kid', + 'kidney', + 'kind', + 'kingdom', + 'kiss', + 'kit', + 'kitchen', + 'kite', + 'kitten', + 'kiwi', + 'knee', + 'knife', + 'knock', + 'know', + 'lab', + 'label', + 'labor', + 'ladder', + 'lady', + 'lake', + 'lamp', + 'language', + 'laptop', + 'large', + 'later', + 'latin', + 'laugh', + 'laundry', + 'lava', + 'law', + 'lawn', + 'lawsuit', + 'layer', + 'lazy', + 'leader', + 'leaf', + 'learn', + 'leave', + 'lecture', + 'left', + 'leg', + 'legal', + 'legend', + 'leisure', + 'lemon', + 'lend', + 'length', + 'lens', + 'leopard', + 'lesson', + 'letter', + 'level', + 'liar', + 'liberty', + 'library', + 'license', + 'life', + 'lift', + 'light', + 'like', + 'limb', + 'limit', + 'link', + 'lion', + 'liquid', + 'list', + 'little', + 'live', + 'lizard', + 'load', + 'loan', + 'lobster', + 'local', + 'lock', + 'logic', + 'lonely', + 'long', + 'loop', + 'lottery', + 'loud', + 'lounge', + 'love', + 'loyal', + 'lucky', + 'luggage', + 'lumber', + 'lunar', + 'lunch', + 'luxury', + 'lyrics', + 'machine', + 'mad', + 'magic', + 'magnet', + 'maid', + 'mail', + 'main', + 'major', + 'make', + 'mammal', + 'man', + 'manage', + 'mandate', + 'mango', + 'mansion', + 'manual', + 'maple', + 'marble', + 'march', + 'margin', + 'marine', + 'market', + 'marriage', + 'mask', + 'mass', + 'master', + 'match', + 'material', + 'math', + 'matrix', + 'matter', + 'maximum', + 'maze', + 'meadow', + 'mean', + 'measure', + 'meat', + 'mechanic', + 'medal', + 'media', + 'melody', + 'melt', + 'member', + 'memory', + 'mention', + 'menu', + 'mercy', + 'merge', + 'merit', + 'merry', + 'mesh', + 'message', + 'metal', + 'method', + 'middle', + 'midnight', + 'milk', + 'million', + 'mimic', + 'mind', + 'minimum', + 'minor', + 'minute', + 'miracle', + 'mirror', + 'misery', + 'miss', + 'mistake', + 'mix', + 'mixed', + 'mixture', + 'mobile', + 'model', + 'modify', + 'mom', + 'moment', + 'monitor', + 'monkey', + 'monster', + 'month', + 'moon', + 'moral', + 'more', + 'morning', + 'mosquito', + 'mother', + 'motion', + 'motor', + 'mountain', + 'mouse', + 'move', + 'movie', + 'much', + 'muffin', + 'mule', + 'multiply', + 'muscle', + 'museum', + 'mushroom', + 'music', + 'must', + 'mutual', + 'myself', + 'mystery', + 'myth', + 'naive', + 'name', + 'napkin', + 'narrow', + 'nasty', + 'nation', + 'nature', + 'near', + 'neck', + 'need', + 'negative', + 'neglect', + 'neither', + 'nephew', + 'nerve', + 'nest', + 'net', + 'network', + 'neutral', + 'never', + 'news', + 'next', + 'nice', + 'night', + 'noble', + 'noise', + 'nominee', + 'noodle', + 'normal', + 'north', + 'nose', + 'notable', + 'note', + 'nothing', + 'notice', + 'novel', + 'now', + 'nuclear', + 'number', + 'nurse', + 'nut', + 'oak', + 'obey', + 'object', + 'oblige', + 'obscure', + 'observe', + 'obtain', + 'obvious', + 'occur', + 'ocean', + 'october', + 'odor', + 'off', + 'offer', + 'office', + 'often', + 'oil', + 'okay', + 'old', + 'olive', + 'olympic', + 'omit', + 'once', + 'one', + 'onion', + 'online', + 'only', + 'open', + 'opera', + 'opinion', + 'oppose', + 'option', + 'orange', + 'orbit', + 'orchard', + 'order', + 'ordinary', + 'organ', + 'orient', + 'original', + 'orphan', + 'ostrich', + 'other', + 'outdoor', + 'outer', + 'output', + 'outside', + 'oval', + 'oven', + 'over', + 'own', + 'owner', + 'oxygen', + 'oyster', + 'ozone', + 'pact', + 'paddle', + 'page', + 'pair', + 'palace', + 'palm', + 'panda', + 'panel', + 'panic', + 'panther', + 'paper', + 'parade', + 'parent', + 'park', + 'parrot', + 'party', + 'pass', + 'patch', + 'path', + 'patient', + 'patrol', + 'pattern', + 'pause', + 'pave', + 'payment', + 'peace', + 'peanut', + 'pear', + 'peasant', + 'pelican', + 'pen', + 'penalty', + 'pencil', + 'people', + 'pepper', + 'perfect', + 'permit', + 'person', + 'pet', + 'phone', + 'photo', + 'phrase', + 'physical', + 'piano', + 'picnic', + 'picture', + 'piece', + 'pig', + 'pigeon', + 'pill', + 'pilot', + 'pink', + 'pioneer', + 'pipe', + 'pistol', + 'pitch', + 'pizza', + 'place', + 'planet', + 'plastic', + 'plate', + 'play', + 'please', + 'pledge', + 'pluck', + 'plug', + 'plunge', + 'poem', + 'poet', + 'point', + 'polar', + 'pole', + 'police', + 'pond', + 'pony', + 'pool', + 'popular', + 'portion', + 'position', + 'possible', + 'post', + 'potato', + 'pottery', + 'poverty', + 'powder', + 'power', + 'practice', + 'praise', + 'predict', + 'prefer', + 'prepare', + 'present', + 'pretty', + 'prevent', + 'price', + 'pride', + 'primary', + 'print', + 'priority', + 'prison', + 'private', + 'prize', + 'problem', + 'process', + 'produce', + 'profit', + 'program', + 'project', + 'promote', + 'proof', + 'property', + 'prosper', + 'protect', + 'proud', + 'provide', + 'public', + 'pudding', + 'pull', + 'pulp', + 'pulse', + 'pumpkin', + 'punch', + 'pupil', + 'puppy', + 'purchase', + 'purity', + 'purpose', + 'purse', + 'push', + 'put', + 'puzzle', + 'pyramid', + 'quality', + 'quantum', + 'quarter', + 'question', + 'quick', + 'quit', + 'quiz', + 'quote', + 'rabbit', + 'raccoon', + 'race', + 'rack', + 'radar', + 'radio', + 'rail', + 'rain', + 'raise', + 'rally', + 'ramp', + 'ranch', + 'random', + 'range', + 'rapid', + 'rare', + 'rate', + 'rather', + 'raven', + 'raw', + 'razor', + 'ready', + 'real', + 'reason', + 'rebel', + 'rebuild', + 'recall', + 'receive', + 'recipe', + 'record', + 'recycle', + 'reduce', + 'reflect', + 'reform', + 'refuse', + 'region', + 'regret', + 'regular', + 'reject', + 'relax', + 'release', + 'relief', + 'rely', + 'remain', + 'remember', + 'remind', + 'remove', + 'render', + 'renew', + 'rent', + 'reopen', + 'repair', + 'repeat', + 'replace', + 'report', + 'require', + 'rescue', + 'resemble', + 'resist', + 'resource', + 'response', + 'result', + 'retire', + 'retreat', + 'return', + 'reunion', + 'reveal', + 'review', + 'reward', + 'rhythm', + 'rib', + 'ribbon', + 'rice', + 'rich', + 'ride', + 'ridge', + 'rifle', + 'right', + 'rigid', + 'ring', + 'riot', + 'ripple', + 'risk', + 'ritual', + 'rival', + 'river', + 'road', + 'roast', + 'robot', + 'robust', + 'rocket', + 'romance', + 'roof', + 'rookie', + 'room', + 'rose', + 'rotate', + 'rough', + 'round', + 'route', + 'royal', + 'rubber', + 'rude', + 'rug', + 'rule', + 'run', + 'runway', + 'rural', + 'sad', + 'saddle', + 'sadness', + 'safe', + 'sail', + 'salad', + 'salmon', + 'salon', + 'salt', + 'salute', + 'same', + 'sample', + 'sand', + 'satisfy', + 'satoshi', + 'sauce', + 'sausage', + 'save', + 'say', + 'scale', + 'scan', + 'scare', + 'scatter', + 'scene', + 'scheme', + 'school', + 'science', + 'scissors', + 'scorpion', + 'scout', + 'scrap', + 'screen', + 'script', + 'scrub', + 'sea', + 'search', + 'season', + 'seat', + 'second', + 'secret', + 'section', + 'security', + 'seed', + 'seek', + 'segment', + 'select', + 'sell', + 'seminar', + 'senior', + 'sense', + 'sentence', + 'series', + 'service', + 'session', + 'settle', + 'setup', + 'seven', + 'shadow', + 'shaft', + 'shallow', + 'share', + 'shed', + 'shell', + 'sheriff', + 'shield', + 'shift', + 'shine', + 'ship', + 'shiver', + 'shock', + 'shoe', + 'shoot', + 'shop', + 'short', + 'shoulder', + 'shove', + 'shrimp', + 'shrug', + 'shuffle', + 'shy', + 'sibling', + 'sick', + 'side', + 'siege', + 'sight', + 'sign', + 'silent', + 'silk', + 'silly', + 'silver', + 'similar', + 'simple', + 'since', + 'sing', + 'siren', + 'sister', + 'situate', + 'six', + 'size', + 'skate', + 'sketch', + 'ski', + 'skill', + 'skin', + 'skirt', + 'skull', + 'slab', + 'slam', + 'sleep', + 'slender', + 'slice', + 'slide', + 'slight', + 'slim', + 'slogan', + 'slot', + 'slow', + 'slush', + 'small', + 'smart', + 'smile', + 'smoke', + 'smooth', + 'snack', + 'snake', + 'snap', + 'sniff', + 'snow', + 'soap', + 'soccer', + 'social', + 'sock', + 'soda', + 'soft', + 'solar', + 'soldier', + 'solid', + 'solution', + 'solve', + 'someone', + 'song', + 'soon', + 'sorry', + 'sort', + 'soul', + 'sound', + 'soup', + 'source', + 'south', + 'space', + 'spare', + 'spatial', + 'spawn', + 'speak', + 'special', + 'speed', + 'spell', + 'spend', + 'sphere', + 'spice', + 'spider', + 'spike', + 'spin', + 'spirit', + 'split', + 'spoil', + 'sponsor', + 'spoon', + 'sport', + 'spot', + 'spray', + 'spread', + 'spring', + 'spy', + 'square', + 'squeeze', + 'squirrel', + 'stable', + 'stadium', + 'staff', + 'stage', + 'stairs', + 'stamp', + 'stand', + 'start', + 'state', + 'stay', + 'steak', + 'steel', + 'stem', + 'step', + 'stereo', + 'stick', + 'still', + 'sting', + 'stock', + 'stomach', + 'stone', + 'stool', + 'story', + 'stove', + 'strategy', + 'street', + 'strike', + 'strong', + 'struggle', + 'student', + 'stuff', + 'stumble', + 'style', + 'subject', + 'submit', + 'subway', + 'success', + 'such', + 'sudden', + 'suffer', + 'sugar', + 'suggest', + 'suit', + 'summer', + 'sun', + 'sunny', + 'sunset', + 'super', + 'supply', + 'supreme', + 'sure', + 'surface', + 'surge', + 'surprise', + 'surround', + 'survey', + 'suspect', + 'sustain', + 'swallow', + 'swamp', + 'swap', + 'swarm', + 'swear', + 'sweet', + 'swift', + 'swim', + 'swing', + 'switch', + 'sword', + 'symbol', + 'symptom', + 'syrup', + 'system', + 'table', + 'tackle', + 'tag', + 'tail', + 'talent', + 'talk', + 'tank', + 'tape', + 'target', + 'task', + 'taste', + 'tattoo', + 'taxi', + 'teach', + 'team', + 'tell', + 'ten', + 'tenant', + 'tennis', + 'tent', + 'term', + 'test', + 'text', + 'thank', + 'that', + 'theme', + 'then', + 'theory', + 'there', + 'they', + 'thing', + 'this', + 'thought', + 'three', + 'thrive', + 'throw', + 'thumb', + 'thunder', + 'ticket', + 'tide', + 'tiger', + 'tilt', + 'timber', + 'time', + 'tiny', + 'tip', + 'tired', + 'tissue', + 'title', + 'toast', + 'tobacco', + 'today', + 'toddler', + 'toe', + 'together', + 'toilet', + 'token', + 'tomato', + 'tomorrow', + 'tone', + 'tongue', + 'tonight', + 'tool', + 'tooth', + 'top', + 'topic', + 'topple', + 'torch', + 'tornado', + 'tortoise', + 'toss', + 'total', + 'tourist', + 'toward', + 'tower', + 'town', + 'toy', + 'track', + 'trade', + 'traffic', + 'tragic', + 'train', + 'transfer', + 'trap', + 'trash', + 'travel', + 'tray', + 'treat', + 'tree', + 'trend', + 'trial', + 'tribe', + 'trick', + 'trigger', + 'trim', + 'trip', + 'trophy', + 'trouble', + 'truck', + 'true', + 'truly', + 'trumpet', + 'trust', + 'truth', + 'try', + 'tube', + 'tuition', + 'tumble', + 'tuna', + 'tunnel', + 'turkey', + 'turn', + 'turtle', + 'twelve', + 'twenty', + 'twice', + 'twin', + 'twist', + 'two', + 'type', + 'typical', + 'ugly', + 'umbrella', + 'unable', + 'unaware', + 'uncle', + 'uncover', + 'under', + 'undo', + 'unfair', + 'unfold', + 'unhappy', + 'uniform', + 'unique', + 'unit', + 'universe', + 'unknown', + 'unlock', + 'until', + 'unusual', + 'unveil', + 'update', + 'upgrade', + 'uphold', + 'upon', + 'upper', + 'upset', + 'urban', + 'urge', + 'usage', + 'use', + 'used', + 'useful', + 'useless', + 'usual', + 'utility', + 'vacant', + 'vacuum', + 'vague', + 'valid', + 'valley', + 'valve', + 'van', + 'vanish', + 'vapor', + 'various', + 'vast', + 'vault', + 'vehicle', + 'velvet', + 'vendor', + 'venture', + 'venue', + 'verb', + 'verify', + 'version', + 'very', + 'vessel', + 'veteran', + 'viable', + 'vibrant', + 'vicious', + 'victory', + 'video', + 'view', + 'village', + 'vintage', + 'violin', + 'virtual', + 'virus', + 'visa', + 'visit', + 'visual', + 'vital', + 'vivid', + 'vocal', + 'voice', + 'void', + 'volcano', + 'volume', + 'vote', + 'voyage', + 'wage', + 'wagon', + 'wait', + 'walk', + 'wall', + 'walnut', + 'want', + 'warfare', + 'warm', + 'warrior', + 'wash', + 'wasp', + 'waste', + 'water', + 'wave', + 'way', + 'wealth', + 'weapon', + 'wear', + 'weasel', + 'weather', + 'web', + 'wedding', + 'weekend', + 'weird', + 'welcome', + 'west', + 'wet', + 'whale', + 'what', + 'wheat', + 'wheel', + 'when', + 'where', + 'whip', + 'whisper', + 'wide', + 'width', + 'wife', + 'wild', + 'will', + 'win', + 'window', + 'wine', + 'wing', + 'wink', + 'winner', + 'winter', + 'wire', + 'wisdom', + 'wise', + 'wish', + 'witness', + 'wolf', + 'woman', + 'wonder', + 'wood', + 'wool', + 'word', + 'work', + 'world', + 'worry', + 'worth', + 'wrap', + 'wreck', + 'wrestle', + 'wrist', + 'write', + 'wrong', + 'yard', + 'year', + 'yellow', + 'you', + 'young', + 'youth', + 'zebra', + 'zero', + 'zone', + 'zoo' +]; diff --git a/lib/bitcoin/bitcoin_wallet.dart b/lib/bitcoin/bitcoin_wallet.dart index 33e69daf7..ef3f10eb4 100644 --- a/lib/bitcoin/bitcoin_wallet.dart +++ b/lib/bitcoin/bitcoin_wallet.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'dart:convert'; +import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart'; import 'package:mobx/mobx.dart'; -import 'package:bip39/bip39.dart' as bip39; import 'package:flutter/foundation.dart'; import 'package:rxdart/rxdart.dart'; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; @@ -42,8 +42,9 @@ abstract class BitcoinWalletBase extends WalletBase with Store { BitcoinBalance initialBalance}) : balance = initialBalance ?? BitcoinBalance(confirmed: 0, unconfirmed: 0), - hd = bitcoin.HDWallet.fromSeed(bip39.mnemonicToSeed(mnemonic), - network: bitcoin.bitcoin), + hd = bitcoin.HDWallet.fromSeed(mnemonicToSeedBytes(mnemonic), + network: bitcoin.bitcoin) + .derivePath("m/0'/0"), addresses = initialAddresses != null ? ObservableList.of(initialAddresses) : ObservableList(), @@ -58,7 +59,7 @@ abstract class BitcoinWalletBase extends WalletBase with Store { {@required String password, @required String name, @required String dirPath, - @required WalletInfo walletInfo, + @required WalletInfo walletInfo, String jsonSource}) { final data = json.decode(jsonSource) as Map; final mnemonic = data['mnemonic'] as String; diff --git a/lib/bitcoin/bitcoin_wallet_service.dart b/lib/bitcoin/bitcoin_wallet_service.dart index 9272f0cb1..49481545c 100644 --- a/lib/bitcoin/bitcoin_wallet_service.dart +++ b/lib/bitcoin/bitcoin_wallet_service.dart @@ -1,5 +1,5 @@ import 'dart:io'; -import 'package:bip39/bip39.dart' as bip39; +import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart'; import 'package:cake_wallet/bitcoin/file.dart'; import 'package:cake_wallet/bitcoin/bitcoin_wallet_creation_credentials.dart'; import 'package:cake_wallet/core/wallet_base.dart'; @@ -24,7 +24,7 @@ class BitcoinWalletService extends WalletService< type: WalletType.bitcoin, name: credentials.name); final wallet = BitcoinWalletBase.build( dirPath: dirPath, - mnemonic: bip39.generateMnemonic(), + mnemonic: generateMnemonic(), password: credentials.password, name: credentials.name, walletInfo: credentials.walletInfo); diff --git a/pubspec.lock b/pubspec.lock index f254541af..659efc336 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -247,12 +247,19 @@ packages: source: hosted version: "2.1.1" crypto: - dependency: transitive + dependency: "direct main" description: name: crypto url: "https://pub.dartlang.org" source: hosted version: "2.1.5" + cryptography: + dependency: "direct main" + description: + name: cryptography + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.1" cupertino_icons: dependency: "direct main" description: @@ -924,6 +931,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.0-nullsafety.3" + unorm_dart: + dependency: "direct main" + description: + name: unorm_dart + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.2" url_launcher: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 0634e3f2a..b59ba287b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -60,6 +60,7 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^0.1.2 encrypt: ^4.0.0 + crypto: ^2.1.5 password: ^1.0.0 basic_utils: ^1.0.8 bitcoin_flutter: ^2.0.0 @@ -67,6 +68,8 @@ dependencies: connectivity: ^0.4.9+2 keyboard_actions: ^3.3.0 flushbar: ^1.10.4 + unorm_dart: ^0.1.2 + cryptography: ^1.4.0 dev_dependencies: flutter_test: From 62a877dd6168549f003d58e92226df91c5a369de Mon Sep 17 00:00:00 2001 From: M Date: Mon, 30 Nov 2020 19:17:44 +0200 Subject: [PATCH 03/70] Fixes --- lib/bitcoin/bitcoin_mnemonic.dart | 11 ++ lib/bitcoin/bitcoin_transaction_history.dart | 10 +- lib/bitcoin/bitcoin_transaction_info.dart | 11 ++ lib/bitcoin/bitcoin_wallet.dart | 28 ++++- lib/bitcoin/bitcoin_wallet_service.dart | 1 + lib/bitcoin/electrum.dart | 37 +++--- lib/core/seed_validator.dart | 4 +- lib/main.dart | 1 + lib/router.dart | 6 +- .../new_wallet/new_wallet_type_page.dart | 7 +- .../new_wallet/widgets/select_button.dart | 2 +- .../wallet_restore_from_seed_form.dart | 67 ++++++---- .../screens/restore/wallet_restore_page.dart | 119 ++++++++++-------- lib/src/widgets/seed_widget.dart | 12 +- .../dashboard/dashboard_view_model.dart | 1 + lib/view_model/wallet_restore_view_model.dart | 29 ++++- 16 files changed, 223 insertions(+), 123 deletions(-) diff --git a/lib/bitcoin/bitcoin_mnemonic.dart b/lib/bitcoin/bitcoin_mnemonic.dart index ea7a66920..7f4bcd13c 100644 --- a/lib/bitcoin/bitcoin_mnemonic.dart +++ b/lib/bitcoin/bitcoin_mnemonic.dart @@ -128,6 +128,17 @@ Uint8List mnemonicToSeedBytes(String mnemonic, {String prefix = segwit}) { nonce: cryptography.Nonce('electrum'.codeUnits)); } +bool matchesAnyPrefix(String mnemonic) => + prefixMatches(mnemonic, [segwit]).any((el) => el); + +bool validateMnemonic(String mnemonic, {String prefix = segwit}) { + try { + return matchesAnyPrefix(mnemonic); + } catch(e) { + return false; + } +} + final COMBININGCODEPOINTS = combiningcodepoints(); List combiningcodepoints() { diff --git a/lib/bitcoin/bitcoin_transaction_history.dart b/lib/bitcoin/bitcoin_transaction_history.dart index c687c0ba4..09cefe5d4 100644 --- a/lib/bitcoin/bitcoin_transaction_history.dart +++ b/lib/bitcoin/bitcoin_transaction_history.dart @@ -65,7 +65,7 @@ abstract class BitcoinTransactionHistoryBase return historiesWithDetails.fold>( {}, (acc, tx) { - acc[tx.id] = tx; + acc[tx.id] = acc[tx.id]?.updated(tx) ?? tx; return acc; }); } @@ -103,10 +103,6 @@ abstract class BitcoinTransactionHistoryBase Future save() async { final data = json.encode({'height': _height, 'transactions': transactions}); - - print('data'); - print(data); - await writeData(path: path, password: _password, data: data); } @@ -168,7 +164,9 @@ abstract class BitcoinTransactionHistoryBase }); _height = content['height'] as int; - } catch (_) {} + } catch (e) { + print(e); + } } void _updateOrInsert(BitcoinTransactionInfo transaction) { diff --git a/lib/bitcoin/bitcoin_transaction_info.dart b/lib/bitcoin/bitcoin_transaction_info.dart index 29cb8521e..40487ceb7 100644 --- a/lib/bitcoin/bitcoin_transaction_info.dart +++ b/lib/bitcoin/bitcoin_transaction_info.dart @@ -130,6 +130,17 @@ class BitcoinTransactionInfo extends TransactionInfo { @override void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount); + BitcoinTransactionInfo updated(BitcoinTransactionInfo info) { + return BitcoinTransactionInfo( + id: id, + height: info.height, + amount: info.amount, + direction: direction ?? info.direction, + date: date ?? info.date, + isPending: isPending ?? info.isPending, + confirmations: info.confirmations); + } + Map toJson() { final m = {}; m['id'] = id; diff --git a/lib/bitcoin/bitcoin_wallet.dart b/lib/bitcoin/bitcoin_wallet.dart index ef3f10eb4..d6f1ce8f0 100644 --- a/lib/bitcoin/bitcoin_wallet.dart +++ b/lib/bitcoin/bitcoin_wallet.dart @@ -175,6 +175,22 @@ abstract class BitcoinWalletBase extends WalletBase with Store { return address; } + Future> generateNewAddresses(int count) async { + final list = []; + + for (var i = 0; i < count; i++) { + _accountIndex += 1; + final address = BitcoinAddressRecord(_getAddress(index: _accountIndex), + index: _accountIndex, label: null); + list.add(address); + } + + addresses.addAll(list); + await save(); + + return list; + } + Future updateAddress(String address, {String label}) async { for (final addr in addresses) { if (addr.address == address) { @@ -190,8 +206,10 @@ abstract class BitcoinWalletBase extends WalletBase with Store { Future startSync() async { try { syncStatus = StartingSyncStatus(); - transactionHistory.updateAsync( - onFinished: () => print('transactionHistory update finished!')); + transactionHistory.updateAsync(onFinished: () { + print('transactionHistory update finished!'); + transactionHistory.save(); + }); _subscribeForUpdates(); await _updateBalance(); syncStatus = SyncedSyncStatus(); @@ -315,8 +333,10 @@ abstract class BitcoinWalletBase extends WalletBase with Store { bitcoinAmountToDouble(amount: _feeMultiplier(priority)); @override - Future save() async => - await write(path: path, password: _password, data: toJSON()); + Future save() async { + await write(path: path, password: _password, data: toJSON()); + await transactionHistory.save(); + } bitcoin.ECPair keyPairFor({@required int index}) => generateKeyPair(hd: hd, index: index); diff --git a/lib/bitcoin/bitcoin_wallet_service.dart b/lib/bitcoin/bitcoin_wallet_service.dart index 49481545c..429a4e2ea 100644 --- a/lib/bitcoin/bitcoin_wallet_service.dart +++ b/lib/bitcoin/bitcoin_wallet_service.dart @@ -84,6 +84,7 @@ class BitcoinWalletService extends WalletService< walletInfo: credentials.walletInfo); await wallet.save(); await wallet.init(); + await wallet.generateNewAddresses(32); return wallet; } diff --git a/lib/bitcoin/electrum.dart b/lib/bitcoin/electrum.dart index 5348959e7..f679a62c6 100644 --- a/lib/bitcoin/electrum.dart +++ b/lib/bitcoin/electrum.dart @@ -78,14 +78,14 @@ class ElectrumClient { print(jsoned); final method = jsoned['method']; final id = jsoned['id'] as String; - final params = jsoned['result']; + final result = jsoned['result']; if (method is String) { _methodHandler(method: method, request: jsoned); return; } - _finish(id, params); + _finish(id, result); } catch (e) { print(e); } @@ -209,16 +209,20 @@ class ElectrumClient { Future> getTransactionExpanded( {@required String hash}) async { - final originalTx = await getTransactionRaw(hash: hash); - final vins = originalTx['vin'] as List; + try { + final originalTx = await getTransactionRaw(hash: hash); + final vins = originalTx['vin'] as List; - for (dynamic vin in vins) { - if (vin is Map) { - vin['tx'] = await getTransactionRaw(hash: vin['txid'] as String); + for (dynamic vin in vins) { + if (vin is Map) { + vin['tx'] = await getTransactionRaw(hash: vin['txid'] as String); + } } - } - return originalTx; + return originalTx; + } catch (_) { + return {}; + } } Future broadcastTransaction( @@ -256,11 +260,13 @@ class ElectrumClient { return 0; }); - BehaviorSubject scripthashUpdate(String scripthash) => - subscribe( - id: 'blockchain.scripthash.subscribe:$scripthash', - method: 'blockchain.scripthash.subscribe', - params: [scripthash]); + BehaviorSubject scripthashUpdate(String scripthash) { + _id += 1; + return subscribe( + id: 'blockchain.scripthash.subscribe:$scripthash', + method: 'blockchain.scripthash.subscribe', + params: [scripthash]); + } BehaviorSubject subscribe( {@required String id, @@ -273,7 +279,8 @@ class ElectrumClient { return subscription; } - Future call({String method, List params = const []}) { + Future call({String method, List params = const []}) async { + await Future.delayed(Duration(milliseconds: 100)); final completer = Completer(); _id += 1; final id = _id; diff --git a/lib/core/seed_validator.dart b/lib/core/seed_validator.dart index 36a06cb03..94ada7481 100644 --- a/lib/core/seed_validator.dart +++ b/lib/core/seed_validator.dart @@ -1,4 +1,4 @@ -import 'package:bip39/src/wordlists/english.dart' as bitcoin_english; +import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart' as bitcoin_electrum; import 'package:cake_wallet/core/validator.dart'; import 'package:cake_wallet/entities/mnemonic_item.dart'; import 'package:cake_wallet/entities/wallet_type.dart'; @@ -64,7 +64,7 @@ class SeedValidator extends Validator { static List getBitcoinWordList(String language) { assert(language.toLowerCase() == LanguageList.english.toLowerCase()); - return bitcoin_english.WORDLIST; + return bitcoin_electrum.englishWordlist; } @override diff --git a/lib/main.dart b/lib/main.dart index 9cd55e054..42f76eb5c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:hive/hive.dart'; diff --git a/lib/router.dart b/lib/router.dart index aa3f2cf71..43c1ebc05 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -74,7 +74,7 @@ Route createRoute(RouteSettings settings) { param2: true)); case Routes.newWallet: - final type = WalletType.monero; // settings.arguments as WalletType; + final type = settings.arguments as WalletType; final walletNewVM = getIt.get(param1: type); return CupertinoPageRoute( @@ -96,7 +96,7 @@ Route createRoute(RouteSettings settings) { builder: (_) => getIt.get( param1: (BuildContext context, WalletType type) => Navigator.of(context) - .pushNamed(Routes.restoreWalletFromSeed, arguments: type), + .pushNamed(Routes.restoreWallet, arguments: type), param2: false)); case Routes.restoreOptions: @@ -146,7 +146,7 @@ Route createRoute(RouteSettings settings) { case Routes.restoreWallet: return MaterialPageRoute( builder: (_) => - getIt.get(param1: WalletType.monero)); + getIt.get(param1: settings.arguments as WalletType)); case Routes.restoreWalletFromSeed: final type = settings.arguments as WalletType; diff --git a/lib/src/screens/new_wallet/new_wallet_type_page.dart b/lib/src/screens/new_wallet/new_wallet_type_page.dart index 5751b11bd..0924988d8 100644 --- a/lib/src/screens/new_wallet/new_wallet_type_page.dart +++ b/lib/src/screens/new_wallet/new_wallet_type_page.dart @@ -70,7 +70,7 @@ class WalletTypeFormState extends State { : walletTypeLightImage; return Container( - padding: EdgeInsets.only(top: 24), + padding: EdgeInsets.only(top: 24, bottom: 24), child: ScrollableWithBottomSection( contentPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24), content: Column( @@ -107,7 +107,10 @@ class WalletTypeFormState extends State { bottomSection: PrimaryButton( onPressed: () => onTypeSelected(), text: S.of(context).seed_language_next, - color: Colors.green, + color: Theme.of(context) + .accentTextTheme + .subtitle + .decorationColor, textColor: Colors.white, isDisabled: selected == null, ), diff --git a/lib/src/screens/new_wallet/widgets/select_button.dart b/lib/src/screens/new_wallet/widgets/select_button.dart index bd69a8015..42b99e40e 100644 --- a/lib/src/screens/new_wallet/widgets/select_button.dart +++ b/lib/src/screens/new_wallet/widgets/select_button.dart @@ -16,7 +16,7 @@ class SelectButton extends StatelessWidget { @override Widget build(BuildContext context) { final color = isSelected - ? Theme.of(context).accentTextTheme.subtitle.decorationColor + ? Colors.green : Theme.of(context).accentTextTheme.caption.color; final textColor = isSelected ? Theme.of(context).accentTextTheme.headline.decorationColor diff --git a/lib/src/screens/restore/wallet_restore_from_seed_form.dart b/lib/src/screens/restore/wallet_restore_from_seed_form.dart index 6044531d9..e94f63bf0 100644 --- a/lib/src/screens/restore/wallet_restore_from_seed_form.dart +++ b/lib/src/screens/restore/wallet_restore_from_seed_form.dart @@ -1,3 +1,5 @@ +import 'package:cake_wallet/entities/wallet_type.dart'; +import 'package:cake_wallet/view_model/wallet_restore_view_model.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; @@ -7,12 +9,20 @@ import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart'; class WalletRestoreFromSeedForm extends StatefulWidget { - WalletRestoreFromSeedForm({Key key, this.blockHeightFocusNode, - this.onHeightOrDateEntered}) + WalletRestoreFromSeedForm( + {Key key, + @required this.displayLanguageSelector, + @required this.displayBlockHeightSelector, + @required this.type, + this.blockHeightFocusNode, + this.onHeightOrDateEntered}) : super(key: key); + final WalletType type; + final bool displayLanguageSelector; + final bool displayBlockHeightSelector; final FocusNode blockHeightFocusNode; - final Function (bool) onHeightOrDateEntered; + final Function(bool) onHeightOrDateEntered; @override WalletRestoreFromSeedFormState createState() => @@ -41,32 +51,35 @@ class WalletRestoreFromSeedFormState extends State { return Container( padding: EdgeInsets.only(left: 25, right: 25), child: Column(children: [ - SeedWidget(key: seedWidgetStateKey, language: language), - GestureDetector( - onTap: () async { - final selected = await showPopUp( - context: context, - builder: (BuildContext context) => - SeedLanguagePicker(selected: language)); + SeedWidget( + key: seedWidgetStateKey, language: language, type: widget.type), + if (widget.displayLanguageSelector) + GestureDetector( + onTap: () async { + final selected = await showPopUp( + context: context, + builder: (BuildContext context) => + SeedLanguagePicker(selected: language)); - if (selected == null || selected.isEmpty) { - return; - } + if (selected == null || selected.isEmpty) { + return; + } - _changeLanguage(selected); - }, - child: Container( - color: Colors.transparent, - padding: EdgeInsets.only(top: 20.0), - child: IgnorePointer( - child: BaseTextFormField( - controller: languageController, - enableInteractiveSelection: false, - readOnly: true)))), - BlockchainHeightWidget( - focusNode: widget.blockHeightFocusNode, - key: blockchainHeightKey, - onHeightOrDateEntered: widget.onHeightOrDateEntered) + _changeLanguage(selected); + }, + child: Container( + color: Colors.transparent, + padding: EdgeInsets.only(top: 20.0), + child: IgnorePointer( + child: BaseTextFormField( + controller: languageController, + enableInteractiveSelection: false, + readOnly: true)))), + if (widget.displayBlockHeightSelector) + BlockchainHeightWidget( + focusNode: widget.blockHeightFocusNode, + key: blockchainHeightKey, + onHeightOrDateEntered: widget.onHeightOrDateEntered) ])); } diff --git a/lib/src/screens/restore/wallet_restore_page.dart b/lib/src/screens/restore/wallet_restore_page.dart index 1a00639b6..adae48c89 100644 --- a/lib/src/screens/restore/wallet_restore_page.dart +++ b/lib/src/screens/restore/wallet_restore_page.dart @@ -25,16 +25,30 @@ class WalletRestorePage extends BasePage { _pages = [], _blockHeightFocusNode = FocusNode(), _controller = PageController(initialPage: 0) { - _pages.addAll([ - WalletRestoreFromSeedForm( - key: walletRestoreFromSeedFormKey, - blockHeightFocusNode: _blockHeightFocusNode, - onHeightOrDateEntered: (value) - => walletRestoreViewModel.isButtonEnabled = value), - WalletRestoreFromKeysFrom(key: walletRestoreFromKeysFormKey, - onHeightOrDateEntered: (value) - => walletRestoreViewModel.isButtonEnabled = value) - ]); + walletRestoreViewModel.availableModes.forEach((mode) { + switch (mode) { + case WalletRestoreMode.seed: + _pages.add(WalletRestoreFromSeedForm( + displayBlockHeightSelector: + walletRestoreViewModel.hasBlockchainHeightLanguageSelector, + displayLanguageSelector: + walletRestoreViewModel.hasSeedLanguageSelector, + type: walletRestoreViewModel.type, + key: walletRestoreFromSeedFormKey, + blockHeightFocusNode: _blockHeightFocusNode, + onHeightOrDateEntered: (value) => + walletRestoreViewModel.isButtonEnabled = value)); + break; + case WalletRestoreMode.keys: + _pages.add(WalletRestoreFromKeysFrom( + key: walletRestoreFromKeysFormKey, + onHeightOrDateEntered: (value) => + walletRestoreViewModel.isButtonEnabled = value)); + break; + default: + break; + } + }); } @override @@ -76,20 +90,19 @@ class WalletRestorePage extends BasePage { } }); - reaction((_) => walletRestoreViewModel.mode, (WalletRestoreMode mode) - { - walletRestoreViewModel.isButtonEnabled = false; + reaction((_) => walletRestoreViewModel.mode, (WalletRestoreMode mode) { + walletRestoreViewModel.isButtonEnabled = false; - walletRestoreFromSeedFormKey.currentState.blockchainHeightKey - .currentState.restoreHeightController.text = ''; - walletRestoreFromSeedFormKey.currentState.blockchainHeightKey - .currentState.dateController.text = ''; + walletRestoreFromSeedFormKey.currentState.blockchainHeightKey.currentState + .restoreHeightController.text = ''; + walletRestoreFromSeedFormKey.currentState.blockchainHeightKey.currentState + .dateController.text = ''; - walletRestoreFromKeysFormKey.currentState.blockchainHeightKey - .currentState.restoreHeightController.text = ''; - walletRestoreFromKeysFormKey.currentState.blockchainHeightKey - .currentState.dateController.text = ''; - }); + walletRestoreFromKeysFormKey.currentState.blockchainHeightKey.currentState + .restoreHeightController.text = ''; + walletRestoreFromKeysFormKey.currentState.blockchainHeightKey.currentState + .dateController.text = ''; + }); return Column(mainAxisAlignment: MainAxisAlignment.center, children: [ Expanded( @@ -100,40 +113,37 @@ class WalletRestorePage extends BasePage { }, controller: _controller, itemCount: _pages.length, - itemBuilder: (_, index) => SingleChildScrollView(child: _pages[index]))), - Padding( - padding: EdgeInsets.only(top: 10), - child: SmoothPageIndicator( - controller: _controller, - count: _pages.length, - effect: ColorTransitionEffect( - spacing: 6.0, - radius: 6.0, - dotWidth: 6.0, - dotHeight: 6.0, - dotColor: Theme.of(context).hintColor.withOpacity(0.5), - activeDotColor: Theme.of(context).hintColor), - )), + itemBuilder: (_, index) => + SingleChildScrollView(child: _pages[index]))), + if (_pages.length > 1) + Padding( + padding: EdgeInsets.only(top: 10), + child: SmoothPageIndicator( + controller: _controller, + count: _pages.length, + effect: ColorTransitionEffect( + spacing: 6.0, + radius: 6.0, + dotWidth: 6.0, + dotHeight: 6.0, + dotColor: Theme.of(context).hintColor.withOpacity(0.5), + activeDotColor: Theme.of(context).hintColor), + )), Padding( padding: EdgeInsets.only(top: 20, bottom: 40, left: 25, right: 25), child: Observer( builder: (context) { return LoadingPrimaryButton( - onPressed: () => - walletRestoreViewModel.create(options: _credentials()), - text: S.of(context).restore_recover, - color: Theme - .of(context) - .accentTextTheme - .subtitle - .decorationColor, - textColor: Theme - .of(context) - .accentTextTheme - .headline - .decorationColor, - isLoading: walletRestoreViewModel.state is IsExecutingState, - isDisabled: !walletRestoreViewModel.isButtonEnabled,); + onPressed: () => + walletRestoreViewModel.create(options: _credentials()), + text: S.of(context).restore_recover, + color: + Theme.of(context).accentTextTheme.subtitle.decorationColor, + textColor: + Theme.of(context).accentTextTheme.headline.decorationColor, + isLoading: walletRestoreViewModel.state is IsExecutingState, + isDisabled: !walletRestoreViewModel.isButtonEnabled, + ); }, )) ]); @@ -145,8 +155,11 @@ class WalletRestorePage extends BasePage { if (walletRestoreViewModel.mode == WalletRestoreMode.seed) { credentials['seed'] = walletRestoreFromSeedFormKey .currentState.seedWidgetStateKey.currentState.text; - credentials['height'] = walletRestoreFromSeedFormKey - .currentState.blockchainHeightKey.currentState.height; + + if (walletRestoreViewModel.hasBlockchainHeightLanguageSelector) { + credentials['height'] = walletRestoreFromSeedFormKey + .currentState.blockchainHeightKey.currentState.height; + } } else { credentials['address'] = walletRestoreFromKeysFormKey.currentState.addressController.text; diff --git a/lib/src/widgets/seed_widget.dart b/lib/src/widgets/seed_widget.dart index ddebbffa0..b53dd8644 100644 --- a/lib/src/widgets/seed_widget.dart +++ b/lib/src/widgets/seed_widget.dart @@ -12,20 +12,21 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:flutter/widgets.dart'; class SeedWidget extends StatefulWidget { - SeedWidget({Key key, this.language}) : super(key: key); + SeedWidget({Key key, this.language, this.type}) : super(key: key); final String language; + final WalletType type; @override - SeedWidgetState createState() => SeedWidgetState(language); + SeedWidgetState createState() => SeedWidgetState(language, type); } class SeedWidgetState extends State { - SeedWidgetState(String language) + SeedWidgetState(String language, this.type) : controller = TextEditingController(), focusNode = FocusNode(), words = SeedValidator.getWordList( - type: WalletType.monero, language: language) { + type:type, language: language) { focusNode.addListener(() { setState(() { if (!focusNode.hasFocus && controller.text.isEmpty) { @@ -41,6 +42,7 @@ class SeedWidgetState extends State { final TextEditingController controller; final FocusNode focusNode; + final WalletType type; List words; bool _showPlaceholder; @@ -55,7 +57,7 @@ class SeedWidgetState extends State { void changeSeedLanguage(String language) { setState(() { words = SeedValidator.getWordList( - type: WalletType.monero, language: language); + type: type, language: language); }); } diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index e76a663ec..5c1b33304 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -178,6 +178,7 @@ abstract class DashboardViewModelBase with Store { @action void _onWalletChange(WalletBase wallet) { this.wallet = wallet; + type = wallet.type; name = wallet.name; transactions.clear(); transactions.addAll(wallet.transactionHistory.transactions.values.map( diff --git a/lib/view_model/wallet_restore_view_model.dart b/lib/view_model/wallet_restore_view_model.dart index 49dab88e4..b015535ae 100644 --- a/lib/view_model/wallet_restore_view_model.dart +++ b/lib/view_model/wallet_restore_view_model.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/bitcoin/bitcoin_wallet_creation_credentials.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; @@ -22,10 +23,16 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { WalletRestoreViewModelBase(AppStore appStore, this._walletCreationService, Box walletInfoSource, {@required WalletType type}) - : super(appStore, walletInfoSource, type: type, isRecovery: true) { - isButtonEnabled = false; + : availableModes = type == WalletType.monero + ? WalletRestoreMode.values + : [WalletRestoreMode.seed], + hasSeedLanguageSelector = type == WalletType.monero, + hasBlockchainHeightLanguageSelector = type == WalletType.monero, + super(appStore, walletInfoSource, type: type, isRecovery: true) { + isButtonEnabled = + !hasSeedLanguageSelector && !hasBlockchainHeightLanguageSelector; mode = WalletRestoreMode.seed; - _walletCreationService.changeWalletType(type: WalletType.monero); + _walletCreationService.changeWalletType(type: type); } @observable @@ -34,6 +41,10 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { @observable bool isButtonEnabled; + final List availableModes; + final bool hasSeedLanguageSelector; + final bool hasBlockchainHeightLanguageSelector; + final WalletCreationService _walletCreationService; @override @@ -44,8 +55,16 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { if (mode == WalletRestoreMode.seed) { final seed = options['seed'] as String; - return MoneroRestoreWalletFromSeedCredentials( - name: name, height: height, mnemonic: seed, password: password); + switch (type) { + case WalletType.monero: + return MoneroRestoreWalletFromSeedCredentials( + name: name, height: height, mnemonic: seed, password: password); + case WalletType.bitcoin: + return BitcoinRestoreWalletFromSeedCredentials( + name: name, mnemonic: seed, password: password); + default: + break; + } } if (mode == WalletRestoreMode.keys) { From 93653d4554697d28f16572f89ef1203f90c9601d Mon Sep 17 00:00:00 2001 From: M Date: Thu, 3 Dec 2020 21:34:56 +0200 Subject: [PATCH 04/70] Added validation for bitcoin seed. Changed transaction properties for bitcoin wallet type. Added special text after sending for bitcoin. --- ...tcoin_mnemonic_is_incorrect_exception.dart | 5 ++++ lib/bitcoin/bitcoin_wallet_service.dart | 5 ++++ lib/entities/transaction_priority.dart | 17 +++++++++++++ lib/generated/i18n.dart | 24 +++++++++---------- .../exchange_trade/exchange_trade_page.dart | 9 +++++-- lib/src/screens/send/send_page.dart | 7 ++++-- lib/view_model/send/send_view_model.dart | 1 + .../settings/settings_view_model.dart | 17 +------------ 8 files changed, 53 insertions(+), 32 deletions(-) create mode 100644 lib/bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart diff --git a/lib/bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart b/lib/bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart new file mode 100644 index 000000000..761b02601 --- /dev/null +++ b/lib/bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart @@ -0,0 +1,5 @@ +class BitcoinMnemonicIsIncorrectException implements Exception { + @override + String toString() => + 'Bitcoin mnemonic has incorrect format. Mnemonic should contain 12 words separated by space.'; +} diff --git a/lib/bitcoin/bitcoin_wallet_service.dart b/lib/bitcoin/bitcoin_wallet_service.dart index 429a4e2ea..205abdcca 100644 --- a/lib/bitcoin/bitcoin_wallet_service.dart +++ b/lib/bitcoin/bitcoin_wallet_service.dart @@ -1,5 +1,6 @@ import 'dart:io'; import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart'; +import 'package:cake_wallet/bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart'; import 'package:cake_wallet/bitcoin/file.dart'; import 'package:cake_wallet/bitcoin/bitcoin_wallet_creation_credentials.dart'; import 'package:cake_wallet/core/wallet_base.dart'; @@ -74,6 +75,10 @@ class BitcoinWalletService extends WalletService< @override Future restoreFromSeed( BitcoinRestoreWalletFromSeedCredentials credentials) async { + if (!validateMnemonic(credentials.mnemonic)) { + throw BitcoinMnemonicIsIncorrectException(); + } + final dirPath = await pathForWalletDir( type: WalletType.bitcoin, name: credentials.name); final wallet = BitcoinWalletBase.build( diff --git a/lib/entities/transaction_priority.dart b/lib/entities/transaction_priority.dart index 9bad84c9f..08ea41951 100644 --- a/lib/entities/transaction_priority.dart +++ b/lib/entities/transaction_priority.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/entities/wallet_type.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/entities/enumerable_item.dart'; @@ -19,6 +20,22 @@ class TransactionPriority extends EnumerableItem with Serializable { static const fastest = TransactionPriority(title: 'Fastest', raw: 4); static const standart = slow; + + static List forWalletType(WalletType type) { + switch (type) { + case WalletType.monero: + return TransactionPriority.all; + case WalletType.bitcoin: + return [ + TransactionPriority.slow, + TransactionPriority.regular, + TransactionPriority.fast + ]; + default: + return []; + } + } + static TransactionPriority deserialize({int raw}) { switch (raw) { case 0: diff --git a/lib/generated/i18n.dart b/lib/generated/i18n.dart index 48bff842c..7b1ceecc3 100644 --- a/lib/generated/i18n.dart +++ b/lib/generated/i18n.dart @@ -206,7 +206,7 @@ class S implements WidgetsLocalizations { String get send_new => "New"; String get send_payment_id => "Payment ID (optional)"; String get send_sending => "Sending..."; - String get send_success => "Your Monero was successfully sent"; + String send_success(String crypto) => "Your ${crypto} was successfully sent"; String get send_templates => "Templates"; String get send_title => "Send"; String get send_xmr => "Send XMR"; @@ -612,7 +612,7 @@ class $de extends S { @override String get trade_details_created_at => "Hergestellt in"; @override - String get send_success => "Ihr Monero wurde erfolgreich gesendet"; + String send_success(String crypto) => "Ihr ${crypto} wurde erfolgreich gesendet"; @override String get settings_wallets => "Brieftaschen"; @override @@ -1280,7 +1280,7 @@ class $hi extends S { @override String get trade_details_created_at => "पर बनाया गया"; @override - String get send_success => "आपका Monero सफलतापूर्वक भेजा गया"; + String send_success(String crypto) => "आपका ${crypto} सफलतापूर्वक भेजा गया"; @override String get settings_wallets => "पर्स"; @override @@ -1948,7 +1948,7 @@ class $ru extends S { @override String get trade_details_created_at => "Создано"; @override - String get send_success => "Ваш Monero был успешно отправлен"; + String send_success(String crypto) => "Ваш ${crypto} был успешно отправлен"; @override String get settings_wallets => "Кошельки"; @override @@ -2616,7 +2616,7 @@ class $ko extends S { @override String get trade_details_created_at => "에 작성"; @override - String get send_success => "Monero가 성공적으로 전송되었습니다"; + String send_success(String crypto) => "${crypto}가 성공적으로 전송되었습니다"; @override String get settings_wallets => "지갑"; @override @@ -3284,7 +3284,7 @@ class $pt extends S { @override String get trade_details_created_at => "Criada em"; @override - String get send_success => "Seu Monero foi enviado com sucesso"; + String send_success(String crypto) => "Seu ${crypto} foi enviado com sucesso"; @override String get settings_wallets => "Carteiras"; @override @@ -3952,7 +3952,7 @@ class $uk extends S { @override String get trade_details_created_at => "Створено"; @override - String get send_success => "Ваш Monero успішно надісланий"; + String send_success(String crypto) => "Ваш ${crypto} успішно надісланий"; @override String get settings_wallets => "Гаманці"; @override @@ -4620,7 +4620,7 @@ class $ja extends S { @override String get trade_details_created_at => "で作成"; @override - String get send_success => "Moneroが送信されました"; + String send_success(String crypto) => "${crypto}が送信されました"; @override String get settings_wallets => "財布"; @override @@ -5292,7 +5292,7 @@ class $pl extends S { @override String get trade_details_created_at => "Utworzono w"; @override - String get send_success => "Twoje Monero zostało pomyślnie wysłane"; + String send_success(String crypto) => "Twoje ${crypto} zostało pomyślnie wysłane"; @override String get settings_wallets => "Portfele"; @override @@ -5960,7 +5960,7 @@ class $es extends S { @override String get trade_details_created_at => "Creado en"; @override - String get send_success => "Su Monero fue enviado con éxito"; + String send_success(String crypto) => "Su ${crypto} fue enviado con éxito"; @override String get settings_wallets => "Carteras"; @override @@ -6628,7 +6628,7 @@ class $nl extends S { @override String get trade_details_created_at => "Gemaakt bij"; @override - String get send_success => "Uw Monero is succesvol verzonden"; + String send_success(String crypto) => "Uw ${crypto} is succesvol verzonden"; @override String get settings_wallets => "Portemonnee"; @override @@ -7296,7 +7296,7 @@ class $zh extends S { @override String get trade_details_created_at => "创建于"; @override - String get send_success => "你Monero已成功發送"; + String send_success(String crypto) => "你${crypto}已成功發送"; @override String get settings_wallets => "皮夹"; @override diff --git a/lib/src/screens/exchange_trade/exchange_trade_page.dart b/lib/src/screens/exchange_trade/exchange_trade_page.dart index aced6304a..26851d248 100644 --- a/lib/src/screens/exchange_trade/exchange_trade_page.dart +++ b/lib/src/screens/exchange_trade/exchange_trade_page.dart @@ -227,7 +227,8 @@ class ExchangeTradeState extends State { final sendingState = widget.exchangeTradeViewModel.sendViewModel.state; - return trade.from == CryptoCurrency.xmr && !(sendingState is TransactionCommitted) + return trade.from == CryptoCurrency.xmr && + !(sendingState is TransactionCommitted) ? LoadingPrimaryButton( isDisabled: trade.inputAddress == null || trade.inputAddress.isEmpty, @@ -306,7 +307,11 @@ class ExchangeTradeState extends State { padding: EdgeInsets.only( top: 220, left: 24, right: 24), child: Text( - S.of(context).send_success, + S.of(context).send_success(widget + .exchangeTradeViewModel + .wallet + .currency + .toString()), textAlign: TextAlign.center, style: TextStyle( fontSize: 22, diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index cd4c5b06d..26955c13c 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -634,7 +634,9 @@ class SendPage extends BasePage { padding: EdgeInsets.only( top: 220, left: 24, right: 24), child: Text( - S.of(context).send_success, + S.of(context).send_success( + sendViewModel.currency + .toString()), textAlign: TextAlign.center, style: TextStyle( fontSize: 22, @@ -748,7 +750,8 @@ class SendPage extends BasePage { } Future _setTransactionPriority(BuildContext context) async { - final items = TransactionPriority.all; + final items = + TransactionPriority.forWalletType(sendViewModel.walletType); final selectedItem = items.indexOf(sendViewModel.transactionPriority); await showPopUp( diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 483d40131..cd49c97d0 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -97,6 +97,7 @@ abstract class SendViewModelBase with Store { @computed ObservableList