diff --git a/lib/data/models/contact_wot_info.dart b/lib/data/models/contact_wot_info.dart index 00fc0082c799a36d40f7c102944410e3f868e3a7..b847256722b2ff77328fd61f5ea8bbe85090d2e7 100644 --- a/lib/data/models/contact_wot_info.dart +++ b/lib/data/models/contact_wot_info.dart @@ -1,5 +1,5 @@ +import '../../ui/ui_helpers.dart'; import 'contact.dart'; -import 'identity_status.dart'; class ContactWotInfo { ContactWotInfo({ @@ -11,16 +11,10 @@ class ContactWotInfo { final Contact you; bool? canCert; bool? canCreateIdty; + bool? waitingForCerts; - bool get isMe { - return me.pubKey == you.pubKey; - } - - bool get isMember { - return you.status == IdentityStatus.MEMBER; - } - - bool get iAmMember { - return me.status == IdentityStatus.MEMBER; + @override + String toString() { + return 'ContactWotInfo{me: ${humanizeContact('', me)}, you: ${humanizeContact('', you)}, canCert: $canCert, canCreateIdty: $canCreateIdty}, waitingForCert: $waitingForCerts}'; } } diff --git a/lib/g1/g1_v2_helper_others.dart b/lib/g1/g1_v2_helper_others.dart index c49fa13b62888d83a636c61b7635b89cb8a0c030..5cc666eb75b7d79bc07815a3ed5ac0244f953d4d 100644 --- a/lib/g1/g1_v2_helper_others.dart +++ b/lib/g1/g1_v2_helper_others.dart @@ -514,3 +514,126 @@ Uri parseNodeUrl(String url) { final Uri parsedUri = Uri.parse(url); return parsedUri; } + +Future<String> requestDistanceEvaluation(int idtyIndex, + {Duration timeout = defPolkadotTimeout}) async { + final CesiumWallet walletV1 = await SharedPreferencesHelper().getWallet(); + final KeyPair wallet = KeyPair.ed25519.fromSeed(walletV1.seed); + final Completer<String> result = Completer<String>(); + + return executeOnNodes<String>( + (Node node, Provider provider, Gdev gdev) async { + // distance rule has been evaluated positively locally on web of trust at block storage.distance.evaluationBlock() + // TODO(vjrj): Implement this + // gdev.query.distance.evaluationBlock(); + final RuntimeCall call = + gdev.tx.distance.requestDistanceEvaluationFor(target: idtyIndex); + return signAndSend(gdev, wallet, call, provider, result, timeout); + }); +} + +Future<String> signAndSend(Gdev polkadot, KeyPair wallet, RuntimeCall call, + Provider provider, Completer<String> result, Duration timeout) async { + final RuntimeVersion runtimeVersion = + await polkadot.rpc.state.getRuntimeVersion(); + final int currentBlockNumber = (await polkadot.query.system.number()) - 1; + final H256 currentBlockHash = + await polkadot.query.system.blockHash(currentBlockNumber); + final int nonce = await polkadot.rpc.system.accountNextIndex(wallet.address); + + final H256 genesisHash = await polkadot.query.system.blockHash(0); + + final Uint8List encodedCall = call.encode(); + + final Uint8List payload = SigningPayload( + method: encodedCall, + specVersion: runtimeVersion.specVersion, + transactionVersion: runtimeVersion.transactionVersion, + genesisHash: encodeHex(genesisHash), + blockHash: encodeHex(currentBlockHash), + blockNumber: currentBlockNumber, + eraPeriod: 64, + nonce: nonce, + tip: 0) + .encode(polkadot.registry); + + final Uint8List signature = wallet.sign(payload); + final Uint8List extrinsic = ExtrinsicPayload( + signer: wallet.bytes(), + method: encodedCall, + signature: signature, + eraPeriod: 64, + blockNumber: currentBlockNumber, + nonce: nonce, + tip: 0) + .encode(polkadot.registry, SignatureType.ed25519); + + final AuthorApi<Provider> author = AuthorApi<Provider>(provider); + + await author.submitAndWatchExtrinsic(extrinsic, (ExtrinsicStatus status) { + switch (status.type) { + case 'finalized': + result.complete(''); + break; + case 'dropped': + result.complete(tr('op_dropped')); + break; + case 'invalid': + result.complete(tr('op_invalid')); + break; + case 'usurped': + result.complete(tr('op_usurped')); + break; + case 'future': + break; + case 'ready': + break; + case 'inBlock': + break; + case 'broadcast': + break; + default: + result.complete('Unexpected transaction status: ${status.type}.'); + loggerDev('Unexpected transaction status: ${status.type}.'); + break; + } + }).timeout(timeout); + return result.future; +} + +Future<String> createIdentity( + {required Contact you, Duration timeout = defPolkadotTimeout}) async { + final List<Node> nodes = NodeManager().getBestNodes(NodeType.endpoint); + nodes.shuffle(); + final CesiumWallet walletV1 = await SharedPreferencesHelper().getWallet(); + final KeyPair wallet = KeyPair.ed25519.fromSeed(walletV1.seed); + final Completer<String> result = Completer<String>(); + return executeOnNodes((Node node, Provider provider, Gdev polkadot) async { + final RuntimeCall call = polkadot.tx.identity.createIdentity( + ownerKey: Address.decode(you.address).pubkey, + ); + + return signAndSend(polkadot, wallet, call, provider, result, timeout); + }); +} + +Future<String> confirmIdentity(String identityName, + {Duration timeout = defPolkadotTimeout}) async { + final List<Node> nodes = NodeManager().getBestNodes(NodeType.endpoint); + nodes.shuffle(); + final CesiumWallet walletV1 = await SharedPreferencesHelper().getWallet(); + final KeyPair wallet = KeyPair.ed25519.fromSeed(walletV1.seed); + final Completer<String> result = Completer<String>(); + return executeOnNodes((Node node, Provider provider, Gdev polkadot) async { + final RuntimeCall call = + polkadot.tx.identity.confirmIdentity(idtyName: identityName.codeUnits); + return signAndSend(polkadot, wallet, call, provider, result, timeout); + }); +} + +Constants gdevConstants() { + final Provider provider = + Provider.fromUri(parseNodeUrl(NodeManager().endpointNodes.first.url)); + final Gdev gdev = Gdev(provider); + return gdev.constant; +} diff --git a/lib/g1/wot_actions.dart b/lib/g1/wot_actions.dart index 86563da2f8ad2144286576d34f6731112f1c2733..17a956ea2b2ad508eaf46327f855f02dfe2965ed 100644 --- a/lib/g1/wot_actions.dart +++ b/lib/g1/wot_actions.dart @@ -1,161 +1,36 @@ import 'dart:async'; -import 'dart:typed_data'; -import 'package:durt/durt.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:polkadart/apis/apis.dart'; -import 'package:polkadart/extrinsic/extrinsic_payload.dart'; -import 'package:polkadart/extrinsic/signature_type.dart'; -import 'package:polkadart/extrinsic/signing_payload.dart'; -import 'package:polkadart/primitives/primitives.dart'; -import 'package:polkadart/provider.dart'; -import 'package:polkadart/scale_codec.dart'; -import 'package:polkadart_keyring/polkadart_keyring.dart'; -import 'package:ss58/ss58.dart'; -import '../data/models/contact.dart'; import '../data/models/contact_wot_info.dart'; import '../data/models/identity_status.dart'; import '../data/models/menu_action.dart'; -import '../data/models/node.dart'; -import '../data/models/node_manager.dart'; -import '../data/models/node_type.dart'; -import '../generated/gdev/gdev.dart'; -import '../generated/gdev/types/gdev_runtime/runtime_call.dart'; -import '../generated/gdev/types/primitive_types/h256.dart'; -import '../shared_prefs_helper.dart'; import '../ui/logger.dart'; -import 'g1_helper.dart'; +import '../ui/ui_helpers.dart'; import 'g1_v2_helper_others.dart'; -Future<String> requestDistanceEvaluation(int idtyIndex, - {Duration timeout = defPolkadotTimeout}) async { - final CesiumWallet walletV1 = await SharedPreferencesHelper().getWallet(); - final KeyPair wallet = KeyPair.ed25519.fromSeed(walletV1.seed); - final Completer<String> result = Completer<String>(); - - return executeOnNodes<String>( - (Node node, Provider provider, Gdev gdev) async { - // distance rule has been evaluated positively locally on web of trust at block storage.distance.evaluationBlock() - // TODO(vjrj): Implement this - // gdev.query.distance.evaluationBlock(); - final RuntimeCall call = - gdev.tx.distance.requestDistanceEvaluationFor(target: idtyIndex); - return signAndSend(gdev, wallet, call, provider, result, timeout); - }); -} - -Future<String> signAndSend(Gdev polkadot, KeyPair wallet, RuntimeCall call, - Provider provider, Completer<String> result, Duration timeout) async { - final RuntimeVersion runtimeVersion = - await polkadot.rpc.state.getRuntimeVersion(); - final int currentBlockNumber = (await polkadot.query.system.number()) - 1; - final H256 currentBlockHash = - await polkadot.query.system.blockHash(currentBlockNumber); - final int nonce = await polkadot.rpc.system.accountNextIndex(wallet.address); - - final H256 genesisHash = await polkadot.query.system.blockHash(0); - - final Uint8List encodedCall = call.encode(); - - final Uint8List payload = SigningPayload( - method: encodedCall, - specVersion: runtimeVersion.specVersion, - transactionVersion: runtimeVersion.transactionVersion, - genesisHash: encodeHex(genesisHash), - blockHash: encodeHex(currentBlockHash), - blockNumber: currentBlockNumber, - eraPeriod: 64, - nonce: nonce, - tip: 0) - .encode(polkadot.registry); - - final Uint8List signature = wallet.sign(payload); - final Uint8List extrinsic = ExtrinsicPayload( - signer: wallet.bytes(), - method: encodedCall, - signature: signature, - eraPeriod: 64, - blockNumber: currentBlockNumber, - nonce: nonce, - tip: 0) - .encode(polkadot.registry, SignatureType.ed25519); - - final AuthorApi<Provider> author = AuthorApi<Provider>(provider); - - await author.submitAndWatchExtrinsic(extrinsic, (ExtrinsicStatus status) { - switch (status.type) { - case 'finalized': - result.complete(''); - break; - case 'dropped': - result.complete(tr('op_dropped')); - break; - case 'invalid': - result.complete(tr('op_invalid')); - break; - case 'usurped': - result.complete(tr('op_usurped')); - break; - case 'future': - break; - case 'ready': - break; - case 'inBlock': - break; - case 'broadcast': - break; - default: - result.complete('Unexpected transaction status: ${status.type}.'); - loggerDev('Unexpected transaction status: ${status.type}.'); - break; - } - }).timeout(timeout); - return result.future; -} - -Future<String> createIdentity( - {required Contact you, Duration timeout = defPolkadotTimeout}) async { - final List<Node> nodes = NodeManager().getBestNodes(NodeType.endpoint); - nodes.shuffle(); - final CesiumWallet walletV1 = await SharedPreferencesHelper().getWallet(); - final KeyPair wallet = KeyPair.ed25519.fromSeed(walletV1.seed); - final Completer<String> result = Completer<String>(); - return executeOnNodes((Node node, Provider provider, Gdev polkadot) async { - final RuntimeCall call = polkadot.tx.identity.createIdentity( - ownerKey: Address.decode(you.address).pubkey, - ); - - return signAndSend(polkadot, wallet, call, provider, result, timeout); - }); -} - -Future<String> confirmIdentity(String identityName, - {Duration timeout = defPolkadotTimeout}) async { - final List<Node> nodes = NodeManager().getBestNodes(NodeType.endpoint); - nodes.shuffle(); - final CesiumWallet walletV1 = await SharedPreferencesHelper().getWallet(); - final KeyPair wallet = KeyPair.ed25519.fromSeed(walletV1.seed); - final Completer<String> result = Completer<String>(); - return executeOnNodes((Node node, Provider provider, Gdev polkadot) async { - final RuntimeCall call = - polkadot.tx.identity.confirmIdentity(idtyName: identityName.codeUnits); - return signAndSend(polkadot, wallet, call, provider, result, timeout); - }); -} - List<MenuAction> getWotMenuActions( BuildContext context, bool isMe, ContactWotInfo wotInfo) { final List<MenuAction> actions = <MenuAction>[]; final IdentityStatus? status = wotInfo.you.status; - + if (inDevelopment) { + actions.add( + MenuAction( + name: 'isMe: $isMe $wotInfo', + icon: Icons.info, + action: () { + return Future<String>.value(''); + }, + ), + ); + } switch (status) { case IdentityStatus.MEMBER: if (isMe) { actions.addAll(<MenuAction>[ MenuAction( - name: 'Renew Membership', + name: tr('renew_membership'), icon: Icons.refresh, action: () { loggerDev('Renewing Membership'); @@ -163,7 +38,7 @@ List<MenuAction> getWotMenuActions( }, ), MenuAction( - name: 'Revoke Membership', + name: tr('revoke_membership'), icon: Icons.cancel, action: () { loggerDev('Revoking Membership'); @@ -195,20 +70,20 @@ List<MenuAction> getWotMenuActions( if (isMe) { actions.add( MenuAction( - name: 'Confirm Identity', + name: tr('confirm_identity'), icon: Icons.verified, action: () { final TextEditingController controller = TextEditingController(); final RegExp validateIdtyName = RegExp(r'^[a-zA-Z0-9_-]{1,42}$'); - showDialog( context: context, builder: (BuildContext context) { return AlertDialog( - title: Text('Confirm Identity'), + title: Text(tr('confirm_identity')), content: TextField( controller: controller, - decoration: InputDecoration(hintText: 'Identity Name'), + decoration: + InputDecoration(hintText: tr('identity_name_hint')), ), actions: <Widget>[ TextButton( @@ -234,7 +109,7 @@ List<MenuAction> getWotMenuActions( } else { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text(tr('Invalid identity name'))), + content: Text(tr('invalid_identity_name'))), ); } }, @@ -272,7 +147,7 @@ List<MenuAction> getWotMenuActions( void _requestDistanceAction(int? idtyIndex, List<MenuAction> actions) { if (idtyIndex != null) { actions.add(MenuAction( - name: 'Request Distance Evaluation', + name: tr('request_distance_evaluation'), icon: Icons.social_distance, action: () { return requestDistanceEvaluation(idtyIndex); @@ -284,7 +159,7 @@ void _certAction(ContactWotInfo wotInfo, List<MenuAction> actions) { if (wotInfo.canCert ?? false) { actions.add( MenuAction( - name: 'Certify Member', + name: tr('certify'), icon: Icons.verified, action: () { loggerDev('Certifying Member'); diff --git a/lib/ui/widgets/contact_page.dart b/lib/ui/widgets/contact_page.dart index 1905901f13612082cea4dabd7ee43802cc8bdfe7..358e1dcbdd2b098b7ff913634e1432c97db9b83f 100644 --- a/lib/ui/widgets/contact_page.dart +++ b/lib/ui/widgets/contact_page.dart @@ -446,6 +446,14 @@ class _ContactPageState extends State<ContactPage> { idtyCertMeta.issuedCount < Constants().maxByIssuer; wotInfo.canCert = canCert; } + + // Waiting for Certifications + if (you.certsReceived != null && + you.certsReceived!.isNotEmpty && + you.certsReceived!.length < + gdevConstants().wot.minCertForMembership) { + wotInfo.waitingForCerts = true; + } } return wotInfo; }