gecko/lib/providers/substrate_sdk.dart

778 lines
22 KiB
Dart

// 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<void> initApi() async {
sdkLoading = true;
await keyring.init([ss58]);
keyring.setSS58(ss58);
await sdk.init(keyring);
sdkReady = true;
sdkLoading = false;
notifyListeners();
}
Future<void> connectNode(BuildContext ctx) async {
List<NetworkParams> node = [];
HomeProvider _homeProvider = Provider.of<HomeProvider>(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<String> 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<List<AddressInfo>> getKeyStoreAddress() async {
List<AddressInfo> 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<List<int>> 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<Map> 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<bool> 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<double> 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<double> 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<bool> checkPassword(String address, String pass) async {
final account = getKeypair(address);
// log.d(account.address);
return await sdk.api.keyring.checkPassword(account, pass);
}
Future<String> 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<KeyPairData?> changePassword(BuildContext context, String address,
String passOld, String? passNew) async {
final account = getKeypair(address);
MyWalletsProvider _myWalletProvider =
Provider.of<MyWalletsProvider>(context, listen: false);
keyring.setCurrent(account);
_myWalletProvider.resetPinCode();
return await sdk.api.keyring.changePassword(keyring, passOld, passNew);
}
Future<void> deleteAllAccounts() async {
for (var account in keyring.allAccounts) {
await sdk.api.keyring.deleteAccount(keyring, account);
}
}
Future<void> deleteAccounts(List<String> address) async {
for (var a in address) {
final account = getKeypair(a);
await sdk.api.keyring.deleteAccount(keyring, account);
}
}
Future<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<bool> isMember(String address) async {
return await idtyStatus(address) == 'Validated';
}
Future<String> 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<Map<String, int>> certState(String from, String to) async {
Map<String, int> _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<Map> 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<String> 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<String> 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<bool> 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<SubstrateSdk>(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);
}