// ignore_for_file: avoid_print import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:gecko/globals.dart'; import 'package:gecko/models/chest_data.dart'; import 'package:gecko/models/wallet_data.dart'; import 'package:gecko/providers/home.dart'; import 'package:gecko/providers/my_wallets.dart'; import 'package:polkawallet_sdk/api/apiKeyring.dart'; import 'package:polkawallet_sdk/api/types/networkParams.dart'; import 'package:polkawallet_sdk/api/types/txInfoData.dart'; import 'package:polkawallet_sdk/polkawallet_sdk.dart'; import 'package:polkawallet_sdk/storage/keyring.dart'; import 'package:polkawallet_sdk/storage/types/keyPairData.dart'; import 'package:provider/provider.dart'; import 'package:truncate/truncate.dart'; // import 'package:web_socket_channel/io.dart'; class SubstrateSdk with ChangeNotifier { final WalletSDK sdk = WalletSDK(); final Keyring keyring = Keyring(); String generatedMnemonic = ''; bool sdkReady = false; bool sdkLoading = false; bool nodeConnected = false; bool importIsLoading = false; int blocNumber = 0; bool isLoadingEndpoint = false; String debugConnection = ''; String transactionStatus = ''; TextEditingController jsonKeystore = TextEditingController(); TextEditingController keystorePassword = TextEditingController(); Future initApi() async { sdkLoading = true; await keyring.init([ss58]); keyring.setSS58(ss58); await sdk.init(keyring); sdkReady = true; sdkLoading = false; notifyListeners(); } Future connectNode(BuildContext ctx) async { List node = []; HomeProvider _homeProvider = Provider.of(ctx, listen: false); // var connectivityResult = await (Connectivity().checkConnectivity()); // if (connectivityResult == ConnectivityResult.mobile || // connectivityResult == ConnectivityResult.wifi) { // _homeProvider.changeMessage("Vous n'êtes pas connecté à internet", 0); // return; // } _homeProvider.changeMessage("connectionPending".tr(), 0); for (String _endpoint in configBox.get('endpoint')) { final n = NetworkParams(); n.name = currencyName; n.endpoint = _endpoint; n.ss58 = ss58; node.add(n); } int timeout = 10000; // if (n.endpoint!.startsWith('ws://')) { // timeout = 5000; // } //// Check websocket conenction - only for wss // final channel = IOWebSocketChannel.connect( // Uri.parse('wss://192.168.1.72:9944'), // ); // channel.stream.listen( // (dynamic message) { // log.d('message $message'); // }, // onDone: () { // log.d('ws channel closed'); // }, // onError: (error) { // log.d('ws error $error'); // }, // ); if (sdk.api.connectedNode?.endpoint != null) { await sdk.api.setting.unsubscribeBestNumber(); } isLoadingEndpoint = true; notifyListeners(); final res = await sdk.api.connectNode(keyring, node).timeout( Duration(milliseconds: timeout), onTimeout: () => null, ); isLoadingEndpoint = false; notifyListeners(); if (res != null) { nodeConnected = true; // Subscribe bloc number sdk.api.setting.subscribeBestNumber((res) { blocNumber = int.parse(res.toString()); // log.d(sdk.api.connectedNode?.endpoint); if (sdk.api.connectedNode?.endpoint == null) { nodeConnected = false; _homeProvider.changeMessage("networkLost".tr(), 0); } else { nodeConnected = true; } notifyListeners(); }); // currencyName = await getCurencyName(); notifyListeners(); _homeProvider.changeMessage( "wellConnectedToNode" .tr(args: [getConnectedEndpoint()!.split('/')[2]]), 5); // snackNode(ctx, true); } else { nodeConnected = false; debugConnection = res.toString(); notifyListeners(); _homeProvider.changeMessage("noDuniterEndointAvailable".tr(), 0); // snackNode(ctx, false); } log.d(sdk.api.connectedNode?.endpoint); } Future importAccount( {String mnemonic = '', bool fromMnemonic = false, String derivePath = '', String password = ''}) async { // toy exercise immense month enter answer table prefer speed cycle gold phone final clipboardData = await Clipboard.getData(Clipboard.kTextPlain); if (mnemonic != '') { fromMnemonic = true; generatedMnemonic = mnemonic; } else if (clipboardData!.text!.split(' ').length == 12) { fromMnemonic = true; generatedMnemonic = clipboardData.text!; } if (password == '') { password = keystorePassword.text; } final KeyType keytype; final String keyToImport; if (fromMnemonic) { keytype = KeyType.mnemonic; keyToImport = generatedMnemonic; } else { keytype = KeyType.keystore; keyToImport = jsonKeystore.text.replaceAll("'", "\\'"); } importIsLoading = true; notifyListeners(); if (clipboardData?.text != null) jsonKeystore.text = clipboardData!.text!; var json = await sdk.api.keyring .importAccount(keyring, keyType: keytype, key: keyToImport, name: derivePath, password: password, derivePath: derivePath, cryptoType: CryptoType.sr25519) .catchError((e) { importIsLoading = false; notifyListeners(); }); if (json == null) return ''; print(json); try { await sdk.api.keyring.addAccount( keyring, keyType: keytype, acc: json, password: password, ); // Clipboard.setData(ClipboardData(text: jsonEncode(acc.toJson()))); } catch (e) { print(e); importIsLoading = false; notifyListeners(); } importIsLoading = false; notifyListeners(); return keyring.allAccounts.last.address!; } void reload() { notifyListeners(); } Future> getKeyStoreAddress() async { List result = []; // sdk.api.account.unsubscribeBalance(); for (var element in keyring.allAccounts) { // Clipboard.setData(ClipboardData(text: jsonEncode(element))); final account = AddressInfo(address: element.address); // await sdk.api.account.subscribeBalance(element.address, (p0) { // account.balance = int.parse(p0.freeBalance) / 100; // }); // sdk.api.setting.unsubscribeBestNumber(); account.balance = await getBalance(element.address!); result.add(account); } return result; } Future> getCerts(String address) async { final idtyIndex = await sdk.webView! .evalJavascript('api.query.identity.identityIndexOf("$address")'); // log.d('u32: ' + idtyIndex.toString()); final _certsReceiver = await sdk.webView! .evalJavascript('api.query.cert.storageIdtyCertMeta($idtyIndex)') ?? []; return [_certsReceiver['receivedCount'], _certsReceiver['issuedCount']]; } Future getCertData(String from, String to) async { final idtyIndexFrom = await sdk.webView! .evalJavascript('api.query.identity.identityIndexOf("$from")'); final idtyIndexTo = await sdk.webView! .evalJavascript('api.query.identity.identityIndexOf("$to")'); final _certData = await sdk.webView!.evalJavascript( 'api.query.cert.storageCertsByIssuer($idtyIndexFrom, $idtyIndexTo)') ?? ''; if (_certData == '') return {}; // log.d(_certData); return _certData; } Future hasAccountConsumers(String address) async { final _accountInfo = await sdk.webView! .evalJavascript('api.query.system.account("$address")'); final _consumers = _accountInfo['consumers']; // log.d('Consumers: $_consumers'); return _consumers == 0 ? false : true; } Future getBalance(String address, {bool isUd = false}) async { double balance = 0.0; // log.d('nodeConnected: ' + nodeConnected.toString()); if (nodeConnected) { final brutBalance = await sdk.api.account.queryBalance(address); balance = int.parse(brutBalance!.freeBalance) / 100; } else { balance = -1; } return balance; } Future subscribeBalance(String address, {bool isUd = false}) async { double balance = 0.0; if (nodeConnected) { await sdk.api.account.subscribeBalance(address, (_balance) { balance = int.parse(_balance.freeBalance) / 100; notifyListeners(); }); } return balance; } KeyPairData getKeypair(String address) { return keyring.keyPairs.firstWhere((kp) => kp.address == address, orElse: (() => KeyPairData())); } Future checkPassword(String address, String pass) async { final account = getKeypair(address); // log.d(account.address); return await sdk.api.keyring.checkPassword(account, pass); } Future getSeed(String address, String _pin) async { final account = getKeypair(address); keyring.setCurrent(account); final _seed = await sdk.api.keyring.getDecryptedSeed(keyring, _pin); String _seedText; if (_seed == null) { _seedText = ''; } else { _seedText = _seed.seed!.split('//')[0]; } log.d(_seedText); return _seedText; } int getDerivationNumber(String address) { final account = getKeypair(address); final deriveNbr = account.name!.split('//')[1]; return int.parse(deriveNbr); } Future changePassword(BuildContext context, String address, String passOld, String? passNew) async { final account = getKeypair(address); MyWalletsProvider _myWalletProvider = Provider.of(context, listen: false); keyring.setCurrent(account); _myWalletProvider.resetPinCode(); return await sdk.api.keyring.changePassword(keyring, passOld, passNew); } Future deleteAllAccounts() async { for (var account in keyring.allAccounts) { await sdk.api.keyring.deleteAccount(keyring, account); } } Future deleteAccounts(List address) async { for (var a in address) { final account = getKeypair(a); await sdk.api.keyring.deleteAccount(keyring, account); } } Future generateMnemonic({String lang = appLang}) async { final gen = await sdk.api.keyring.generateMnemonic(ss58); generatedMnemonic = gen.mnemonic!; // final res = await importAccount(fromMnemonic: true); // await Clipboard.setData(ClipboardData(text: generatedMnemonic)); return gen.mnemonic!; } Future setCurrentWallet(WalletData _wallet) async { final currentChestNumber = configBox.get('currentChest'); ChestData _newChestData = chestBox.get(currentChestNumber)!; _newChestData.defaultWallet = _wallet.number; await chestBox.put(currentChestNumber, _newChestData); try { final acc = getKeypair(_wallet.address!); keyring.setCurrent(acc); return acc.address!; } catch (e) { return (e.toString()); } } KeyPairData getCurrentWallet() { try { final acc = keyring.current; return acc; } catch (e) { return KeyPairData(); } } Future pay( {required String fromAddress, required String destAddress, required double amount, required String password}) async { transactionStatus = ''; // setCurrentWallet(fromAddress); log.d(keyring.current.address); log.d(fromAddress); log.d(password); // log.d(await checkPassword(fromAddress, password)); final fromPubkey = await sdk.api.account.decodeAddress([fromAddress]); log.d(fromPubkey!.keys.first); final sender = TxSenderData( fromAddress, fromPubkey.keys.first, ); final txInfo = TxInfoData( 'balances', amount == -1 ? 'transferAll' : 'transferKeepAlive', sender); final int amountUnit = (amount * 100).toInt(); try { final hash = await sdk.api.tx.signAndSend( txInfo, [destAddress, amount == -1 ? false : amountUnit], password, onStatusChange: (status) { log.d('Transaction status: ' + status); transactionStatus = status; notifyListeners(); }, ).timeout( const Duration(seconds: 12), onTimeout: () => {}, ); log.d(hash.toString()); if (hash.isEmpty) { transactionStatus = 'timeout'; notifyListeners(); return 'timeout'; } else { transactionStatus = hash.toString(); notifyListeners(); return hash.toString(); } } catch (e) { transactionStatus = e.toString(); notifyListeners(); return e.toString(); } } Future certify( String fromAddress, String password, String toAddress) async { transactionStatus = ''; // setCurrentWallet(fromAddress); log.d('me: ' + fromAddress); log.d('to: ' + toAddress); final _myIdtyStatus = await idtyStatus(fromAddress); final _toIdtyStatus = await idtyStatus(toAddress); log.d(_myIdtyStatus); log.d(_toIdtyStatus); if (_myIdtyStatus != 'Validated') { transactionStatus = 'notMember'; notifyListeners(); return 'notMember'; } final sender = TxSenderData( keyring.current.address, keyring.current.pubKey, ); TxInfoData txInfo; if (_toIdtyStatus == 'noid') { txInfo = TxInfoData( 'identity', 'createIdentity', sender, ); } else if (_toIdtyStatus == 'Validated' || _toIdtyStatus == 'ConfirmedByOwner') { txInfo = TxInfoData( 'cert', 'addCert', sender, ); } else { transactionStatus = 'cantBeCert'; notifyListeners(); return 'cantBeCert'; } log.d('Cert action: ' + txInfo.call!); try { final hash = await sdk.api.tx .signAndSend( txInfo, [toAddress], password, ) .timeout( const Duration(seconds: 12), onTimeout: () => {}, ); log.d(hash); if (hash.isEmpty) { transactionStatus = 'timeout'; notifyListeners(); return 'timeout'; } else { transactionStatus = hash.toString(); notifyListeners(); return hash.toString(); } } catch (e) { transactionStatus = e.toString(); notifyListeners(); return e.toString(); } } Future idtyStatus(String address, [bool smooth = true]) async { // var tata = await sdk.webView! // .evalJavascript('api.query.system.account("$address")'); var idtyIndex = await sdk.webView! .evalJavascript('api.query.identity.identityIndexOf("$address")'); if (idtyIndex == null) { return 'noid'; } final idtyStatus = await sdk.webView! .evalJavascript('api.query.identity.identities($idtyIndex)'); if (idtyStatus != null) { final String _status = idtyStatus['status']; // log.d('Status $address: $_status'); return (_status); } else { return 'expired'; } } Future confirmIdentity( String fromAddress, String name, String password) async { // Confirm identity // setCurrentWallet(fromAddress); log.d('me: ' + keyring.current.address!); final sender = TxSenderData( keyring.current.address, keyring.current.pubKey, ); final txInfo = TxInfoData( 'identity', 'confirmIdentity', sender, ); try { final hash = await sdk.api.tx.signAndSend( txInfo, [name], password, onStatusChange: (status) { log.d('Transaction status: ' + status); transactionStatus = status; notifyListeners(); }, ).timeout( const Duration(seconds: 12), onTimeout: () => {}, ); log.d(hash); if (hash.isEmpty) { transactionStatus = 'timeout'; notifyListeners(); return 'timeout'; } else { transactionStatus = hash.toString(); notifyListeners(); return hash.toString(); } } on Exception catch (e) { log.e(e); transactionStatus = e.toString(); notifyListeners(); return e.toString(); } } Future isMember(String address) async { return await idtyStatus(address) == 'Validated'; } Future getMemberAddress() async { // TODOO: Continue digging memberAddress detection String memberAddress = ''; walletBox.toMap().forEach((key, value) async { final bool _isMember = await isMember(value.address!); log.d(_isMember); if (_isMember) { final currentChestNumber = configBox.get('currentChest'); ChestData _newChestData = chestBox.get(currentChestNumber)!; _newChestData.memberWallet = value.number; await chestBox.put(currentChestNumber, _newChestData); memberAddress = value.address!; return; } }); log.d(memberAddress); return memberAddress; } Future> certState(String from, String to) async { Map _result = {}; if (from != to && await isMember(from)) { final _certData = await getCertData(from, to); final _certMeta = await getCertMeta(from); final int _removableOn = _certData['removableOn'] ?? 0; final int _nextIssuableOn = _certMeta['nextIssuableOn'] ?? 0; final certRemovableDuration = (_removableOn - blocNumber) * 6; const int renewDelay = 2 * 30 * 24 * 3600; // 2 months if (certRemovableDuration >= renewDelay) { final certRenewDuration = certRemovableDuration - renewDelay; _result.putIfAbsent('certRenewable', () => certRenewDuration); } else if (_nextIssuableOn > blocNumber) { final certDelayDuration = (_nextIssuableOn - blocNumber) * 6; _result.putIfAbsent('certDelay', () => certDelayDuration); } else { _result.putIfAbsent('canCert', () => 0); } } return _result; } Future getCertMeta(String address) async { var idtyIndex = await sdk.webView! .evalJavascript('api.query.identity.identityIndexOf("$address")'); final _certMeta = await sdk.webView! .evalJavascript('api.query.cert.storageIdtyCertMeta($idtyIndex)') ?? ''; // if (_certMeta['nextIssuableOn'] != 0) return {}; // log.d(_certMeta); return _certMeta; } Future revokeIdentity(String address, String password) async { final idtyIndex = await sdk.webView! .evalJavascript('api.query.identity.identityIndexOf("$address")'); final sender = TxSenderData( keyring.current.address, keyring.current.pubKey, ); log.d(sender.address); TxInfoData txInfo; txInfo = TxInfoData( 'membership', 'revokeMembership', sender, ); try { final hash = await sdk.api.tx .signAndSend( txInfo, [idtyIndex], password, ) .timeout( const Duration(seconds: 12), onTimeout: () => {}, ); log.d(hash); if (hash.isEmpty) { transactionStatus = 'timeout'; notifyListeners(); return 'timeout'; } else { transactionStatus = hash.toString(); notifyListeners(); return hash.toString(); } } catch (e) { transactionStatus = e.toString(); notifyListeners(); return e.toString(); } } Future getCurencyName() async {} Future derive( BuildContext context, String address, int number, String password) async { final keypair = getKeypair(address); final seedMap = await keyring.store.getDecryptedSeed(keypair.pubKey, password); if (seedMap?['type'] != 'mnemonic') return ''; final List seedList = seedMap!['seed'].split('//'); generatedMnemonic = seedList[0]; return await importAccount( mnemonic: generatedMnemonic, fromMnemonic: true, derivePath: '//$number', password: password); } Future generateRootKeypair(String address, String password) async { final keypair = getKeypair(address); final seedMap = await keyring.store.getDecryptedSeed(keypair.pubKey, password); if (seedMap?['type'] != 'mnemonic') return ''; final List seedList = seedMap!['seed'].split('//'); generatedMnemonic = seedList[0]; return await importAccount(fromMnemonic: true, password: password); } Future isMnemonicValid(String mnemonic) async { // Needed for bad encoding of UTF-8 mnemonic = mnemonic.replaceAll('é', 'é'); mnemonic = mnemonic.replaceAll('è', 'è'); return await sdk.api.keyring.checkMnemonicValid(mnemonic); } String? getConnectedEndpoint() { return sdk.api.connectedNode?.endpoint; } } void snack(BuildContext context, String message, {int duration = 2}) { final snackBar = SnackBar(content: Text(message), duration: Duration(seconds: duration)); ScaffoldMessenger.of(context).showSnackBar(snackBar); } class AddressInfo { final String? address; double balance; AddressInfo({@required this.address, this.balance = 0}); } void snackNode(BuildContext context, bool isConnected) { String _message; if (!isConnected) { _message = "noDuniterNodeAvailableTryLater".tr() + ":\n${configBox.get('endpoint').first}"; } else { SubstrateSdk _sub = Provider.of(context, listen: false); _message = "youAreConnectedToNode".tr() + "\n${_sub.getConnectedEndpoint()!.split('//')[1]}"; } final snackBar = SnackBar( padding: const EdgeInsets.all(20), content: Text(_message, style: const TextStyle(fontSize: 16)), duration: const Duration(seconds: 4)); ScaffoldMessenger.of(context).showSnackBar(snackBar); } String getShortPubkey(String pubkey) { String pubkeyShort = truncate(pubkey, 7, omission: String.fromCharCode(0x2026), position: TruncatePosition.end) + truncate(pubkey, 6, omission: "", position: TruncatePosition.start); return pubkeyShort; } class PasswordException implements Exception { String cause; PasswordException(this.cause); }