diff --git a/lib/globals.dart b/lib/globals.dart index 5751866..df96282 100644 --- a/lib/globals.dart +++ b/lib/globals.dart @@ -20,7 +20,7 @@ late Box walletBox; late Box chestBox; late Box configBox; late Box g1WalletsBox; -late Box keystoreBox; +// late Box keystoreBox; String cesiumPod = "https://g1.data.le-sou.org"; // String cesiumPod = "https://g1.data.e-is.pro"; diff --git a/lib/main.dart b/lib/main.dart index 775a137..31f98c0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -72,7 +72,7 @@ Future main() async { chestBox = await Hive.openBox("chestBox"); configBox = await Hive.openBox("configBox"); g1WalletsBox = await Hive.openBox("g1WalletsBox"); - keystoreBox = await Hive.openBox("keystoreBox"); + // keystoreBox = await Hive.openBox("keystoreBox"); g1WalletsBox.clear(); diff --git a/lib/providers/home.dart b/lib/providers/home.dart index 07f0a6b..b10ef85 100644 --- a/lib/providers/home.dart +++ b/lib/providers/home.dart @@ -7,6 +7,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'dart:async'; import 'package:gecko/globals.dart'; +import 'package:gecko/providers/substrate_sdk.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:path_provider/path_provider.dart' as pp; @@ -146,7 +147,7 @@ class HomeProvider with ChangeNotifier { _message = "Aucun noeud Duniter disponible, veuillez réessayer ultérieurement"; } else { - _message = "Vous êtes connecté au noeud\n${endPointGVA.split('/')[2]}"; + _message = "Vous êtes connecté au noeud\n${SubstrateSdk().subNode}"; } final snackBar = SnackBar( content: Text(_message), duration: const Duration(seconds: 2)); diff --git a/lib/providers/substrate_sdk.dart b/lib/providers/substrate_sdk.dart index c94baf6..b419a6b 100644 --- a/lib/providers/substrate_sdk.dart +++ b/lib/providers/substrate_sdk.dart @@ -1,15 +1,20 @@ import 'dart:convert'; +import 'dart:io'; +import 'package:crypto/crypto.dart'; +import 'package:fast_base58/fast_base58.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:gecko/globals.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:truncate/truncate.dart'; class SubstrateSdk with ChangeNotifier { - final String subNode = '192.168.1.85:9944'; + final List subNode = ['127.0.0.1:9944', '192.168.1.85:9944']; final bool isSsl = false; + final int ss58 = 42; final WalletSDK sdk = WalletSDK(); final Keyring keyring = Keyring(); @@ -25,7 +30,8 @@ class SubstrateSdk with ChangeNotifier { Future initApi() async { sdkLoading = true; - await keyring.init([0, 2]); + await keyring.init([ss58]); + keyring.setSS58(ss58); await sdk.init(keyring); sdkReady = true; @@ -35,14 +41,18 @@ class SubstrateSdk with ChangeNotifier { Future connectNode() async { final String socketKind = isSsl ? 'wss' : 'ws'; - final node = NetworkParams(); - node.name = 'pokaniter'; - node.endpoint = '$socketKind://$subNode'; - node.ss58 = 42; - final res = await sdk.api.connectNode(keyring, [node]).timeout( - const Duration(seconds: 10), - onTimeout: () => null, - ); + List node = []; + for (final sn in subNode) { + final n = NetworkParams(); + n.name = 'duniter'; + n.endpoint = '$socketKind://$sn'; + n.ss58 = ss58; + node.add(n); + } + final res = await sdk.api.connectNode(keyring, node).timeout( + const Duration(seconds: 10), + onTimeout: () => null, + ); if (res != null) { nodeConnected = true; notifyListeners(); @@ -55,7 +65,17 @@ class SubstrateSdk with ChangeNotifier { }); } - Future importFromKeystore() async { + Future importAccount({bool fromMnemonic = false}) async { + final KeyType keytype; + final String keyToImport; + if (fromMnemonic) { + keytype = KeyType.mnemonic; + keyToImport = generatedMnemonic; + } else { + keytype = KeyType.keystore; + keyToImport = jsonKeystore.text.replaceAll("'", "\\'"); + } + importIsLoading = true; notifyListeners(); final clipboardData = await Clipboard.getData(Clipboard.kTextPlain); @@ -63,8 +83,8 @@ class SubstrateSdk with ChangeNotifier { final json = await sdk.api.keyring .importAccount( keyring, - keyType: KeyType.keystore, - key: jsonKeystore.text.replaceAll("'", "\\'"), + keyType: keytype, + key: keyToImport, name: 'testKey', password: keystorePassword.text, ) @@ -72,44 +92,119 @@ class SubstrateSdk with ChangeNotifier { importIsLoading = false; notifyListeners(); }); - final acc = await sdk.api.keyring - .addAccount( - keyring, - keyType: KeyType.mnemonic, - acc: json!, - password: keystorePassword.text, - ) - .catchError((e) { + if (json == null) return false; + try { + final acc = await sdk.api.keyring.addAccount( + keyring, + keyType: KeyType.mnemonic, + acc: json, + password: keystorePassword.text, + ); + Clipboard.setData(ClipboardData(text: jsonEncode(acc.toJson()))); + } catch (e) { importIsLoading = false; notifyListeners(); - }); + } // await keystoreBox.clear(); - await keystoreBox.add(acc.toJson()); - Clipboard.setData(ClipboardData(text: jsonEncode(acc.toJson()))); + // await keystoreBox.add(acc.toJson()); importIsLoading = false; + await Future.delayed(const Duration(milliseconds: 20)); notifyListeners(); + return true; } void reload() { notifyListeners(); } - List getKeyStoreAddress() { - List result = []; + Future> getKeyStoreAddress() async { + List result = []; - for (var element in keystoreBox.values) { + // sdk.api.account.unsubscribeBalance(); + for (var element in keyring.allAccounts) { // Clipboard.setData(ClipboardData(text: jsonEncode(element))); - result.add(element['address']); + 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(); + if (nodeConnected) { + final brutBalance = await sdk.api.account.queryBalance(element.address); + account.balance = int.parse(brutBalance!.freeBalance) / 100; + } + result.add(account); } return result; } - Future generateMnemonic() async { - final gen = await sdk.api.keyring.generateMnemonic(42); + Future deleteAllAccounts() async { + for (var account in keyring.allAccounts) { + await sdk.api.keyring.deleteAccount(keyring, account); + } + } + + Future generateMnemonic() async { + final gen = await sdk.api.keyring.generateMnemonic(ss58); generatedMnemonic = gen.mnemonic!; - notifyListeners(); - return gen.mnemonic!; + + final res = await importAccount(fromMnemonic: true); + + return res; + } + + pay(BuildContext context, String address, double amount, + String password) async { + final sender = TxSenderData( + keyring.current.address, + keyring.current.pubKey, + ); + final txInfo = TxInfoData('balances', 'transfer', sender); + try { + final hash = await sdk.api.tx.signAndSend( + txInfo, + [address, amount * 100], + password, + onStatusChange: (status) { + print('status: ' + status); + if (status == 'Ready') { + snack(context, 'Paiement effectué avec succès !'); + } + }, + ); + print(hash.toString()); + } catch (err) { + print(err.toString()); + } } } + +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}); +} + +String getShortPubkey(String pubkey) { + List pubkeyByte = Base58Decode(pubkey); + Digest pubkeyS256 = sha256.convert(sha256.convert(pubkeyByte).bytes); + String pubkeyCheksum = Base58Encode(pubkeyS256.bytes); + String pubkeyChecksumShort = + truncate(pubkeyCheksum, 3, omission: "", position: TruncatePosition.end); + + String pubkeyShort = truncate(pubkey, 5, + omission: String.fromCharCode(0x2026), + position: TruncatePosition.end) + + truncate(pubkey, 4, omission: "", position: TruncatePosition.start) + + ':$pubkeyChecksumShort'; + + return pubkeyShort; +} diff --git a/lib/screens/substrate_sandbox.dart b/lib/screens/substrate_sandbox.dart index 9dac1f9..cc31791 100644 --- a/lib/screens/substrate_sandbox.dart +++ b/lib/screens/substrate_sandbox.dart @@ -52,30 +52,50 @@ class SubstrateSandBox extends StatelessWidget { height: 35, ), onTap: () async { - await keystoreBox.clear(); + await _sub.deleteAllAccounts(); _sub.reload(); }, ), const SizedBox(width: 10), ]), - - Text(keystoreBox.isEmpty - ? '-' - : _sub.getKeyStoreAddress().toString()), - // const SizedBox(height: 40), - // const Text('Trousseau:'), - // TextField( - // controller: _sub.jsonKeystore, - // onChanged: (_) => _sub.reload(), - // minLines: 5, - // maxLines: 5, - // ), + FutureBuilder( + future: _sub.getKeyStoreAddress(), + builder: (BuildContext context, + AsyncSnapshot> _data) { + return Column(children: [ + for (final AddressInfo e in _data.data!) + Row(children: [ + InkWell( + onTap: () => _sub.keyring.setCurrent(_sub + .keyring.keyPairs + .firstWhere((element) => + element.address == e.address!)), + child: Text( + getShortPubkey(e.address!), + style: const TextStyle( + fontFamily: 'Monospace'), + ), + ), + const SizedBox(width: 20), + InkWell( + onTap: () async => await _sub.pay( + context, + e.address!, + 10, + _sub.keystorePassword.text), + child: Text("${e.balance.toString()} ğdev"), + ) + ]) + ]); + }), const SizedBox(height: 20), const Text('Mot de passe du trousseau:'), TextField( controller: _sub.keystorePassword, obscureText: true, obscuringCharacter: '•', + enableSuggestions: false, + autocorrect: false, onChanged: (_) => _sub.reload(), ), Column( @@ -89,9 +109,14 @@ class SubstrateSandBox extends StatelessWidget { ), onPressed: _sub.keystorePassword.text.isNotEmpty ? () async { - await _sub.importFromKeystore(); + final res = await _sub.importAccount(); _sub.importIsLoading = false; _sub.reload(); + snack( + context, + res + ? 'Portefeuille importé' + : 'Le format de trousseau est invalide'); } : null, child: const Text( @@ -108,10 +133,17 @@ class SubstrateSandBox extends StatelessWidget { onPrimary: Colors.black, // foreground ), onPressed: () async { - await _sub.generateMnemonic(); + final res = await _sub.generateMnemonic(); + _sub.importIsLoading = false; + _sub.reload(); + snack( + context, + res + ? 'Portefeuille importé' + : 'Le format de trousseau est invalide'); }, child: const Text( - 'Générer un mnemonic', + "Générer un mnemonic et l'importer", style: TextStyle(fontSize: 20), ), ), diff --git a/pubspec.yaml b/pubspec.yaml index ade1322..aebe1b4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,7 +5,7 @@ description: Pay with G1. # pub.dev using `pub publish`. This is preferred for private packages. publish_to: 'none' # Remove this line if you wish to publish to pub.dev -version: 0.0.4+8 +version: 0.0.5+1 environment: sdk: '>=2.12.0 <3.0.0'