gecko/lib/providers/generate_wallets.dart

400 lines
12 KiB
Dart

import 'dart:math';
import 'package:durt/durt.dart' as durt;
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:gecko/globals.dart';
import 'package:gecko/models/bip39_words.dart';
import 'package:gecko/models/chest_data.dart';
import 'package:gecko/models/wallet_data.dart';
import 'package:gecko/providers/substrate_sdk.dart';
import 'package:gecko/widgets/scan_derivations_info.dart';
import 'package:polkawallet_sdk/api/apiKeyring.dart';
import 'package:provider/provider.dart';
import "package:unorm_dart/unorm_dart.dart" as unorm;
class GenerateWalletsProvider with ChangeNotifier {
GenerateWalletsProvider();
final walletNameFocus = FocusNode();
Color? askedWordColor = Colors.black;
bool isAskedWordValid = false;
var scanStatus = ScanDerivationsStatus.none;
int scanedValidWalletNumber = -1;
int scanedWalletNumber = -1;
int numberScan = 60;
late int nbrWord;
String? nbrWordAlpha;
String? generatedMnemonic;
bool walletIsGenerated = true;
final mnemonicController = TextEditingController();
final pin = TextEditingController();
// Import wallet
TextEditingController cesiumID = TextEditingController();
TextEditingController cesiumPWD = TextEditingController();
TextEditingController cesiumPubkey = TextEditingController();
bool isCesiumIDVisible = false;
bool isCesiumPWDVisible = false;
bool canImport = false;
late durt.CesiumWallet cesiumWallet;
// Import Chest
TextEditingController cellController0 = TextEditingController();
TextEditingController cellController1 = TextEditingController();
TextEditingController cellController2 = TextEditingController();
TextEditingController cellController3 = TextEditingController();
TextEditingController cellController4 = TextEditingController();
TextEditingController cellController5 = TextEditingController();
TextEditingController cellController6 = TextEditingController();
TextEditingController cellController7 = TextEditingController();
TextEditingController cellController8 = TextEditingController();
TextEditingController cellController9 = TextEditingController();
TextEditingController cellController10 = TextEditingController();
TextEditingController cellController11 = TextEditingController();
bool isFirstTimeSentenceComplete = true;
Future storeHDWChest(BuildContext context) async {
int chestNumber = chestBox.isEmpty ? 0 : chestBox.keys.last + 1;
String chestName;
if (chestNumber == 0) {
chestName = 'geckoChest'.tr();
} else {
chestName = '${'geckoChest'.tr()}${chestNumber + 1}';
}
await configBox.put('currentChest', chestNumber);
ChestData thisChest = ChestData(
name: chestName,
defaultWallet: 0,
imageName: '${chestNumber % 8}.png',
);
await chestBox.add(thisChest);
int? chestKey = chestBox.keys.last;
await configBox.put('currentChest', chestKey);
notifyListeners();
}
void checkAskedWord(String inputWord, String mnemo) {
final expectedWord = mnemo.split(' ')[nbrWord];
final normInputWord = unorm.nfkd(inputWord);
log.i("Is $expectedWord equal to input $normInputWord ?");
if (expectedWord == normInputWord ||
(kDebugMode && inputWord == 'triche')) {
log.d('Word is OK');
isAskedWordValid = true;
askedWordColor = Colors.green[600];
// walletNameFocus.nextFocus();
notifyListeners();
} else {
isAskedWordValid = false;
}
// notifyListeners();
}
String removeDiacritics(String str) {
var withDia =
'ÀÁÂÃÄÅàáâãäåÒÓÔÕÕÖØòóôõöøÈÉÊËèéêëðÇçÐÌÍÎÏìíîïÙÚÛÜùúûüÑñŠšŸÿýŽž';
var withoutDia =
'AAAAAAaaaaaaOOOOOOOooooooEEEEeeeeeCcDIIIIiiiiUUUUuuuuNnSsYyyZz';
for (int i = 0; i < withDia.length; i++) {
str = str.replaceAll(withDia[i], withoutDia[i]);
}
return str;
}
int getRandomInt() {
var rng = Random();
return rng.nextInt(12);
}
String? intToString(int nbr) {
Map nbrToString = {};
nbrToString[1] = '1th'.tr();
nbrToString[2] = '2th'.tr();
nbrToString[3] = '3th'.tr();
nbrToString[4] = '4th'.tr();
nbrToString[5] = '5th'.tr();
nbrToString[6] = '6th'.tr();
nbrToString[7] = '7th'.tr();
nbrToString[8] = '8th'.tr();
nbrToString[9] = '9th'.tr();
nbrToString[10] = '10th'.tr();
nbrToString[11] = '11th'.tr();
nbrToString[12] = '12th'.tr();
nbrWordAlpha = nbrToString[nbr];
return nbrWordAlpha;
}
void nameChanged() {
notifyListeners();
}
String changePinCode({required bool reload}) {
pin.text = durt.randomSecretCode(pinLength);
if (reload) {
notifyListeners();
}
return pin.text;
}
Future<void> generateCesiumWalletPubkey(
String cesiumID, String cesiumPWD) async {
cesiumWallet = durt.CesiumWallet(cesiumID, cesiumPWD);
String walletPubkey = cesiumWallet.pubkey;
cesiumPubkey.text = walletPubkey;
log.d(walletPubkey);
}
void cesiumIDisVisible() {
isCesiumIDVisible = !isCesiumIDVisible;
notifyListeners();
}
void cesiumPWDisVisible() {
isCesiumPWDVisible = !isCesiumPWDVisible;
notifyListeners();
}
void resetCesiumImportView() {
cesiumID.text = cesiumPWD.text = cesiumPubkey.text = pin.text = '';
canImport = isCesiumIDVisible = isCesiumPWDVisible = false;
notifyListeners();
}
Future<List<String>> generateWordList(BuildContext context) async {
final sub = Provider.of<SubstrateSdk>(context, listen: false);
generatedMnemonic = await sub.generateMnemonic(lang: appLang);
List<String> wordsList = [];
String word;
int nbr = 1;
for (word in generatedMnemonic!.split(' ')) {
wordsList.add("$nbr:$word");
nbr++;
}
return wordsList;
}
bool isBipWord(String word, [bool checkRedondance = true]) {
bool isValid = false;
notifyListeners();
// Needed for bad encoding of UTF-8
word = word.replaceAll('é', '');
word = word.replaceAll('è', '');
int nbrMatch = 0;
if (bip39Words(appLang).contains(word.toLowerCase())) {
for (var bipWord in bip39Words(appLang)) {
if (bipWord.startsWith(word)) {
isValid = nbrMatch == 0 ? true : false;
if (checkRedondance) nbrMatch = nbrMatch + 1;
}
}
}
return isValid;
}
bool isBipWordsList(List<String> words) {
bool isValid = true;
for (String word in words) {
// Needed for bad encoding of UTF-8
word = word.replaceAll('é', '');
word = word.replaceAll('è', '');
if (!bip39Words(appLang).contains(word.toLowerCase())) {
isValid = false;
}
}
return isValid;
}
void resetImportView() {
cellController0.text = cellController1.text = cellController2.text =
cellController3.text = cellController4.text = cellController5.text =
cellController6.text = cellController7.text = cellController8.text =
cellController9.text =
cellController10.text = cellController11.text = '';
isFirstTimeSentenceComplete = true;
notifyListeners();
}
bool isSentenceComplete(BuildContext context) {
if (isBipWordsList(
[
cellController0.text,
cellController1.text,
cellController2.text,
cellController3.text,
cellController4.text,
cellController5.text,
cellController6.text,
cellController7.text,
cellController8.text,
cellController9.text,
cellController10.text,
cellController11.text
],
)) {
if (isFirstTimeSentenceComplete) {
FocusScope.of(context).unfocus();
}
isFirstTimeSentenceComplete = false;
return true;
} else {
return false;
}
}
Future pasteMnemonic(BuildContext context) async {
final sentence = await Clipboard.getData('text/plain');
int nbr = 0;
List cells = [
cellController0,
cellController1,
cellController2,
cellController3,
cellController4,
cellController5,
cellController6,
cellController7,
cellController8,
cellController9,
cellController10,
cellController11
];
if (sentence?.text == null) return;
for (var word in sentence!.text!.split(' ')) {
bool isValid = isBipWord(word, false);
if (isValid) {
cells[nbr].text = word;
}
nbr++;
}
}
void reloadBuild() {
notifyListeners();
}
Future<bool> scanDerivations(BuildContext context) async {
final sub = Provider.of<SubstrateSdk>(context, listen: false);
final currentChestNumber = configBox.get('currentChest');
bool isAlive = false;
scanedWalletNumber = 0;
Map<String, int> addressToScan = {};
notifyListeners();
if (!sub.nodeConnected) {
return false;
}
scanStatus = ScanDerivationsStatus.rootScanning;
final hasRoot = await scanRootBalance(sub, currentChestNumber);
notifyListeners();
if (hasRoot) {
isAlive = true;
}
scanStatus = ScanDerivationsStatus.scanning;
for (int derivationNbr in [for (var i = 0; i < numberScan; i += 1) i]) {
final addressData = await sub.sdk.api.keyring.addressFromMnemonic(
sub.currencyParameters['ss58']!,
cryptoType: CryptoType.sr25519,
mnemonic: generatedMnemonic!,
derivePath: '//$derivationNbr');
addressToScan.putIfAbsent(addressData.address!, () => derivationNbr);
}
final balanceList =
await sub.getBalanceMulti(addressToScan.keys.toList()).timeout(
const Duration(seconds: 20),
onTimeout: () => {},
);
// Remove unused wallets
balanceList.removeWhere((key, value) => value['transferableBalance'] == 0);
scanedValidWalletNumber = balanceList.length + scanedWalletNumber;
scanStatus = ScanDerivationsStatus.import;
for (String scannedWallet in balanceList.keys) {
isAlive = true;
String walletName = scanedWalletNumber == 0
? 'currentWallet'.tr()
: '${'wallet'.tr()} ${scanedWalletNumber + 1}';
await sub.importAccount(
mnemonic: generatedMnemonic!,
derivePath: "//${addressToScan[scannedWallet]}",
password: pin.text);
WalletData myWallet = WalletData(
chest: currentChestNumber,
address: scannedWallet,
number: scanedWalletNumber,
name: walletName,
derivation: addressToScan[scannedWallet],
imageDefaultPath: '${scanedWalletNumber % 4}.png',
isOwned: true);
await walletBox.put(myWallet.address, myWallet);
scanedWalletNumber++;
notifyListeners();
}
log.d(scanedWalletNumber);
scanStatus = ScanDerivationsStatus.none;
scanedWalletNumber = scanedValidWalletNumber = -1;
notifyListeners();
return isAlive;
}
Future<bool> scanRootBalance(SubstrateSdk sub, int currentChestNumber) async {
final addressData = await sub.sdk.api.keyring.addressFromMnemonic(
sub.currencyParameters['ss58']!,
cryptoType: CryptoType.sr25519,
mnemonic: generatedMnemonic!);
final balance = await sub.getBalance(addressData.address!).timeout(
const Duration(seconds: 1),
onTimeout: () => {},
);
log.d(
"${addressData.address!}: ${balance['transferableBalance']} $currencyName");
if (balance['transferableBalance'] != 0) {
String walletName = 'myRootWallet'.tr();
await sub.importAccount(mnemonic: generatedMnemonic!, password: pin.text);
WalletData myWallet = WalletData(
chest: currentChestNumber,
address: addressData.address!,
number: 0,
name: walletName,
derivation: -1,
imageDefaultPath: '0.png',
isOwned: true);
await walletBox.put(myWallet.address, myWallet);
scanedWalletNumber++;
return true;
} else {
return false;
}
}
}