Skip to content
Snippets Groups Projects
Commit d9db5e23 authored by vjrj's avatar vjrj
Browse files

More work with WOT

parent cbd3176f
No related branches found
No related tags found
No related merge requests found
......@@ -38,6 +38,7 @@ class Contact extends Equatable implements IsJsonSerializable<Contact> {
this.status,
this.isMember,
this.createdOn,
this.index,
this.expireOn})
:
// ensure that Contact does not have v1 checksums
......@@ -91,6 +92,7 @@ class Contact extends Equatable implements IsJsonSerializable<Contact> {
this.status,
this.isMember,
this.createdOn,
this.index,
this.expireOn});
factory Contact.withPubKey(
......@@ -108,6 +110,7 @@ class Contact extends Equatable implements IsJsonSerializable<Contact> {
List<Map<String, String>>? socials,
List<Cert>? certsReceived,
List<Cert>? certsIssued,
int? index,
DateTime? time}) {
return Contact._(
nick: nick,
......@@ -125,7 +128,8 @@ class Contact extends Equatable implements IsJsonSerializable<Contact> {
socials: socials,
time: time,
certsReceived: certsReceived,
certsIssued: certsIssued);
certsIssued: certsIssued,
index: index);
}
factory Contact.empty() {
......@@ -155,7 +159,8 @@ class Contact extends Equatable implements IsJsonSerializable<Contact> {
IdentityStatus? status,
bool? isMember,
int? createdOn,
int? expireOn}) {
int? expireOn,
int? index}) {
return Contact._(
nick: nick,
pubKey: v1pubkeyFromAddress(address),
......@@ -176,7 +181,8 @@ class Contact extends Equatable implements IsJsonSerializable<Contact> {
status: status,
isMember: isMember,
createdOn: createdOn,
expireOn: expireOn);
expireOn: expireOn,
index: index);
}
final String? nick;
......@@ -202,6 +208,7 @@ class Contact extends Equatable implements IsJsonSerializable<Contact> {
final bool? isMember;
final int? createdOn;
final int? expireOn;
final int? index;
Contact merge(Contact c) {
return Contact(
......
......@@ -45,6 +45,8 @@ abstract class _$ContactCWProxy {
Contact createdOn(int? createdOn);
Contact index(int? index);
Contact expireOn(int? expireOn);
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `Contact(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
......@@ -73,6 +75,7 @@ abstract class _$ContactCWProxy {
IdentityStatus? status,
bool? isMember,
int? createdOn,
int? index,
int? expireOn,
});
}
......@@ -143,6 +146,9 @@ class _$ContactCWProxyImpl implements _$ContactCWProxy {
@override
Contact createdOn(int? createdOn) => this(createdOn: createdOn);
@override
Contact index(int? index) => this(index: index);
@override
Contact expireOn(int? expireOn) => this(expireOn: expireOn);
......@@ -174,6 +180,7 @@ class _$ContactCWProxyImpl implements _$ContactCWProxy {
Object? status = const $CopyWithPlaceholder(),
Object? isMember = const $CopyWithPlaceholder(),
Object? createdOn = const $CopyWithPlaceholder(),
Object? index = const $CopyWithPlaceholder(),
Object? expireOn = const $CopyWithPlaceholder(),
}) {
return Contact(
......@@ -253,6 +260,10 @@ class _$ContactCWProxyImpl implements _$ContactCWProxy {
? _value.createdOn
// ignore: cast_nullable_to_non_nullable
: createdOn as int?,
index: index == const $CopyWithPlaceholder()
? _value.index
// ignore: cast_nullable_to_non_nullable
: index as int?,
expireOn: expireOn == const $CopyWithPlaceholder()
? _value.expireOn
// ignore: cast_nullable_to_non_nullable
......@@ -300,6 +311,7 @@ Contact _$ContactFromJson(Map<String, dynamic> json) => Contact(
status: $enumDecodeNullable(_$IdentityStatusEnumMap, json['status']),
isMember: json['isMember'] as bool?,
createdOn: (json['createdOn'] as num?)?.toInt(),
index: (json['index'] as num?)?.toInt(),
expireOn: (json['expireOn'] as num?)?.toInt(),
);
......@@ -324,6 +336,7 @@ Map<String, dynamic> _$ContactToJson(Contact instance) => <String, dynamic>{
'isMember': instance.isMember,
'createdOn': instance.createdOn,
'expireOn': instance.expireOn,
'index': instance.index,
};
const _$IdentityStatusEnumMap = {
......
import 'contact.dart';
import 'identity_status.dart';
class ContactWotInfo {
ContactWotInfo({
required this.me,
required this.you,
});
final Contact me;
final Contact you;
bool? canCert;
bool? canCreateIdty;
bool get isMe {
return me.pubKey == you.pubKey;
}
bool get isMember {
return you.status == IdentityStatus.MEMBER;
}
bool get iAmMember {
return me.status == IdentityStatus.MEMBER;
}
}
......@@ -169,6 +169,7 @@ Contact _contactFromIdentity(dynamic identity) {
status: parseIdentityStatus(
((identity as dynamic).status as dynamic)?.name as String?),
isMember: (identity as dynamic).isMember as bool?,
index: (identity as dynamic).index as int?,
createdOn: ((identity as dynamic).account as dynamic).createdOn as int?,
expireOn: (identity as dynamic).expireOn as int?,
);
......@@ -194,12 +195,10 @@ Cert _buildCert(dynamic cert) {
);
}
Future<Contact> getIdentity({required String address}) async {
Future<Contact?> getIdentity({required String address}) async {
final List<Contact> contacts =
await getIdentities(addresses: <String>[address]);
return contacts.isNotEmpty
? contacts.first
: Contact.withAddress(address: address);
return contacts.isNotEmpty ? contacts.first : null;
}
Contact _contactFromAccount(dynamic account) {
......@@ -233,6 +232,7 @@ Contact _contactFromAccount(dynamic account) {
isMember: (identity as dynamic)?.isMember as bool?,
createdOn: (account as dynamic).createdOn as int?,
expireOn: (identity as dynamic).expireOn as int?,
index: (identity as dynamic).index as int?,
certsIssued: certIssued,
certsReceived: certReceived,
)
......
......@@ -19,12 +19,15 @@ import 'package:polkadart_keyring/polkadart_keyring.dart';
import 'package:ss58/ss58.dart';
import 'package:tuple/tuple.dart' as tp;
import '../data/models/contact.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/frame_system/account_info.dart';
import '../generated/gdev/types/gdev_runtime/runtime_call.dart';
import '../generated/gdev/types/pallet_certification/types/idty_cert_meta.dart';
import '../generated/gdev/types/pallet_identity/types/idty_value.dart';
import '../generated/gdev/types/primitive_types/h256.dart';
import '../generated/gdev/types/sp_runtime/multiaddress/multi_address.dart';
import '../shared_prefs_helper.dart';
......@@ -210,6 +213,56 @@ String formatPubkey(String value) {
}
*/
Future<T> executeOnNodes<T>(
Future<T> Function(Node node, Provider provider, Gdev polkadot) operation,
{Duration timeout = defPolkadotTimeout}) async {
final List<Node> nodes = NodeManager().getBestNodes(NodeType.endpoint);
nodes.shuffle();
for (final Node node in nodes) {
try {
final Provider provider = Provider.fromUri(parseNodeUrl(node.url));
final Gdev polkadot = Gdev(provider);
final T result =
await operation(node, provider, polkadot).timeout(timeout);
return result; // If the operation is successful, return the result
} catch (e, stacktrace) {
NodeManager().increaseNodeErrors(NodeType.endpoint, node);
loggerDev('Error in node ${node.url}', error: e, stackTrace: stacktrace);
}
}
throw Exception(
'All nodes failed to execute the operation'); // If all nodes fail, throw an exception
}
Future<IdtyValue?> polkadotIdentity(Contact contact) async {
return executeOnNodes<IdtyValue?>(
(Node node, Provider provider, Gdev gdev) async {
if (contact.index == null) {
return null;
}
return gdev.query.identity.identities(contact.index!);
});
}
Future<int> polkadotCurrentBlock() async {
return executeOnNodes<int>((Node node, Provider provider, Gdev gdev) async {
return gdev.query.system.number();
});
}
Future<IdtyCertMeta?> polkadotIdtyCertMeta(Contact contact) async {
return executeOnNodes<IdtyCertMeta?>(
(Node node, Provider provider, Gdev gdev) async {
if (contact.index == null) {
return null;
}
return gdev.query.certification.storageIdtyCertMeta(contact.index!);
});
}
Future<BigInt?> getBalanceV2(
{required String address, Duration timeout = defPolkadotTimeout}) async {
final List<Node> nodes = NodeManager().getBestNodes(NodeType.endpoint);
......
......@@ -14,6 +14,8 @@ 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';
......@@ -27,98 +29,106 @@ import '../ui/logger.dart';
import 'g1_helper.dart';
import 'g1_v2_helper_others.dart';
Future<String> createIdentity({Duration timeout = defPolkadotTimeout}) async {
final List<Node> nodes = NodeManager().getBestNodes(NodeType.endpoint);
nodes.shuffle();
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>();
for (final Node node in nodes) {
try {
final Provider provider = Provider.fromUri(parseNodeUrl(node.url));
final Gdev polkadot = Gdev(provider);
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);
});
}
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);
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 H256 genesisHash = await polkadot.query.system.blockHash(0);
final RuntimeCall createIdentityCall =
polkadot.tx.identity.createIdentity(
ownerKey: Address.decode(wallet.address).pubkey,
);
final Uint8List encodedCall = call.encode();
final Uint8List encodedCall = createIdentityCall.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 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 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);
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;
}
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);
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 result.future;
} catch (e, stacktrace) {
NodeManager().increaseNodeErrors(NodeType.endpoint, node);
loggerDev('Error creating identity in node ${node.url}',
error: e, stackTrace: stacktrace);
}
continue;
}
return 'Error creating identity';
return signAndSend(polkadot, wallet, call, provider, result, timeout);
});
}
Future<String> confirmIdentity(String identityName,
......@@ -128,57 +138,17 @@ Future<String> confirmIdentity(String identityName,
final CesiumWallet walletV1 = await SharedPreferencesHelper().getWallet();
final KeyPair wallet = KeyPair.ed25519.fromSeed(walletV1.seed);
final Completer<String> result = Completer<String>();
for (final Node node in nodes) {
try {
final Provider provider = Provider.fromUri(parseNodeUrl(node.url));
final Gdev polkadot = Gdev(provider);
final RuntimeCall rt = polkadot.tx.identity
.confirmIdentity(idtyName: identityName.codeUnits);
final AuthorApi<Provider> author = AuthorApi<Provider>(provider);
final Uint8List sign = wallet.sign(rt.encode());
await author.submitAndWatchExtrinsic(sign, (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 await result.future;
} catch (e, stacktrace) {
NodeManager().increaseNodeErrors(NodeType.endpoint, node);
loggerDev('Error confirming identity in node ${node.url}',
error: e, stackTrace: stacktrace);
}
continue;
}
return 'Error confirming identity';
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(bool isMe, IdentityStatus? status) {
List<MenuAction> getWotMenuActions(
BuildContext context, bool isMe, ContactWotInfo wotInfo) {
final List<MenuAction> actions = <MenuAction>[];
final IdentityStatus? status = wotInfo.you.status;
switch (status) {
case IdentityStatus.MEMBER:
......@@ -189,7 +159,7 @@ List<MenuAction> getWotMenuActions(bool isMe, IdentityStatus? status) {
icon: Icons.refresh,
action: () {
loggerDev('Renewing Membership');
return confirmIdentity('');
return Future<String>.value('');
},
),
MenuAction(
......@@ -202,30 +172,16 @@ List<MenuAction> getWotMenuActions(bool isMe, IdentityStatus? status) {
),
]);
} else {
actions.add(
MenuAction(
name: 'Certify Member',
icon: Icons.verified,
action: () {
loggerDev('Certifying Member');
return Future<String>.value('');
},
),
);
_certAction(wotInfo, actions);
}
break;
case IdentityStatus.UNVALIDATED:
case IdentityStatus.NOTMEMBER:
if (!isMe) {
actions.add(
MenuAction(
name: 'Invite to Membership',
icon: Icons.group_add,
action: () {
loggerDev('Inviting to Membership');
return Future<String>.value('');
},
),
);
_certAction(wotInfo, actions);
_requestDistanceAction(wotInfo.you.index, actions);
} else {
_requestDistanceAction(wotInfo.me.index, actions);
}
break;
......@@ -236,18 +192,74 @@ List<MenuAction> getWotMenuActions(bool isMe, IdentityStatus? status) {
break;
case IdentityStatus.UNCONFIRMED:
break;
if (isMe) {
actions.add(
MenuAction(
name: 'Confirm Identity',
icon: Icons.verified,
action: () {
final TextEditingController controller = TextEditingController();
final RegExp validateIdtyName = RegExp(r'^[a-zA-Z0-9_-]{1,42}$');
case IdentityStatus.UNVALIDATED:
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Confirm Identity'),
content: TextField(
controller: controller,
decoration: InputDecoration(hintText: 'Identity Name'),
),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(tr('cancel')),
),
TextButton(
onPressed: () {
final String input = controller.text.trim();
if (validateIdtyName.hasMatch(input)) {
Navigator.of(context).pop();
confirmIdentity(input).then((String result) {
if (!context.mounted) {
return;
}
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(result)),
);
});
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(tr('Invalid identity name'))),
);
}
},
child: Text(tr('ok')),
),
],
);
},
);
// FIXME(vjrj): Implement this better
return Future<String>.value('');
},
),
);
}
break;
case null:
if (isMe) {
if (!isMe && (wotInfo.canCreateIdty ?? false)) {
actions.add(
MenuAction(
name: tr('wot_create_identity'),
icon: Icons.verified_user_outlined,
action: () {
return createIdentity();
return createIdentity(you: wotInfo.you);
},
),
);
......@@ -256,3 +268,29 @@ List<MenuAction> getWotMenuActions(bool isMe, IdentityStatus? status) {
return actions;
}
void _requestDistanceAction(int? idtyIndex, List<MenuAction> actions) {
if (idtyIndex != null) {
actions.add(MenuAction(
name: 'Request Distance Evaluation',
icon: Icons.social_distance,
action: () {
return requestDistanceEvaluation(idtyIndex);
}));
}
}
void _certAction(ContactWotInfo wotInfo, List<MenuAction> actions) {
if (wotInfo.canCert ?? false) {
actions.add(
MenuAction(
name: 'Certify Member',
icon: Icons.verified,
action: () {
loggerDev('Certifying Member');
return Future<String>.value('');
},
),
);
}
}
......@@ -8,11 +8,17 @@ import 'package:text_scroll/text_scroll.dart';
import '../../data/models/app_cubit.dart';
import '../../data/models/contact.dart';
import '../../data/models/contact_cubit.dart';
import '../../data/models/contact_wot_info.dart';
import '../../data/models/menu_action.dart';
import '../../data/models/multi_wallet_transaction_cubit.dart';
import '../../data/models/node_manager.dart';
import '../../g1/api.dart';
import '../../g1/duniter_indexer_helper.dart';
import '../../g1/g1_v2_helper_others.dart';
import '../../g1/wot_actions.dart';
import '../../generated/gdev/pallets/certification.dart';
import '../../generated/gdev/types/pallet_certification/types/idty_cert_meta.dart';
import '../../generated/gdev/types/pallet_identity/types/idty_value.dart';
import '../../shared_prefs_helper.dart';
import '../ui_helpers.dart';
import 'certifications_page.dart';
......@@ -57,19 +63,24 @@ class _ContactPageState extends State<ContactPage> {
Widget build(BuildContext context) {
final Contact contact = widget.contact;
return FutureBuilder<Contact>(
future: getProfile(widget.contact.pubKey, resize: false, complete: true),
builder: (BuildContext context, AsyncSnapshot<Contact> snapshot) {
return FutureBuilder<ContactWotInfo>(
future: _getWotInfo(widget.contact),
builder: (BuildContext context, AsyncSnapshot<ContactWotInfo> snapshot) {
if (snapshot.hasData) {
return _buildContactWidget(snapshot.data!, context);
}
return _buildContactWidget(contact, context);
return _buildContactWidget(
ContactWotInfo(
me: Contact(pubKey: SharedPreferencesHelper().getPubKey()),
you: contact),
context);
},
);
}
DefaultTabController _buildContactWidget(
Contact contact, BuildContext context) {
ContactWotInfo contactWotInfo, BuildContext context) {
final Contact contact = contactWotInfo.you;
// FIXME, duplicated code in contact_menu
final bool isContact =
context.read<ContactsCubit>().isContact(contact.pubKey);
......@@ -103,7 +114,8 @@ class _ContactPageState extends State<ContactPage> {
),
];
if (isV2) {
getWotMenuActions(me, contact.status).forEach((MenuAction action) {
getWotMenuActions(context, me, contactWotInfo)
.forEach((MenuAction action) {
actions.add(
SpeedDialChild(
child: Icon(action.icon),
......@@ -393,6 +405,50 @@ class _ContactPageState extends State<ContactPage> {
),
);
}
Future<ContactWotInfo> _getWotInfo(Contact contact) async {
final Contact you =
await getProfile(widget.contact.pubKey, resize: false, complete: true);
final Contact me = await getProfile(SharedPreferencesHelper().getPubKey(),
resize: false, complete: true);
final ContactWotInfo wotInfo = ContactWotInfo(me: me, you: you);
if (isV2) {
if (you.status == null) {
// Can create Identity from:
// https://duniter.org/wiki/duniter-v2/doc/wot/
// storage.identity.identities(AliceIndex).status is Member
final bool iAmMember = me.isMember ?? false;
// EveAccount exists with minimum amount of 2 ĞD (existential deposit plus fee buffer)
// storage.system.account(EveAccount).data.free is higher than 200
final BigInt? youBalance =
await getBalanceV2(address: widget.contact.address);
final bool enoughBalance =
youBalance != null && youBalance > BigInt.from(200);
// EveAccount is not already used by an identity
// storage.identity.identities(EveAccount) is None
final bool identityUsed =
(await getIdentity(address: you.address)) != null;
wotInfo.canCreateIdty = iAmMember && enoughBalance && !identityUsed;
}
// Can Certificate
final IdtyValue? myIdty = await polkadotIdentity(me);
final IdtyCertMeta? idtyCertMeta = await polkadotIdtyCertMeta(me);
if (myIdty != null && idtyCertMeta != null) {
final int currentBlock = await polkadotCurrentBlock();
// From: https://duniter.org/wiki/duniter-v2/doc/wot/
// storage.identity.identities(AliceIndex).nextCreatableIdentityOn is lower than current block
// storage.certification.storageIdtyCertMeta(AliceIndex).nextIssuableOn is lower than current block
// storage.certification.storageIdtyCertMeta(AliceIndex).issuedCount is lower than constants.certification.maxByIssuer()
final bool canCert = myIdty.nextCreatableIdentityOn < currentBlock &&
idtyCertMeta.nextIssuableOn < currentBlock &&
idtyCertMeta.issuedCount < Constants().maxByIssuer;
wotInfo.canCert = canCert;
}
}
return wotInfo;
}
}
Widget _buildBadge(BuildContext context, int count) {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment