Skip to content

Commit

Permalink
Custom Signed Extensions Example (#421)
Browse files Browse the repository at this point in the history
* custom signed extensions example

* fixes to add custom signed extensions

* typo

* bug fix and added example
  • Loading branch information
justkawal authored Feb 21, 2024
1 parent 017cb28 commit 4b6dd7e
Show file tree
Hide file tree
Showing 9 changed files with 264 additions and 112 deletions.
37 changes: 37 additions & 0 deletions examples/bin/extrinsic_demo.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import 'package:polkadart/polkadart.dart'
SignatureType,
SigningPayload,
StateApi;
import 'package:polkadart_scale_codec/polkadart_scale_codec.dart'
as scale_codec;
import 'package:polkadart_keyring/polkadart_keyring.dart';

import 'package:polkadart_example/generated/polkadot/polkadot.dart';
Expand Down Expand Up @@ -63,6 +65,39 @@ Future<void> main(List<String> arguments) async {
final payload = payloadToSign.encode(api.registry);
print('Payload: ${hex.encode(payload)}');

// Custom Signed Extensions with TypeRegistry
{
// Get Metadata
final runtimeMetadata = await stateApi.getMetadata();
// Get Registry
final scale_codec.Registry registry =
runtimeMetadata.chainInfo.scaleCodec.registry;

// Get SignedExtensions mapped with codecs Map<String, Codec<dynamic>>
final Map<String, scale_codec.Codec<dynamic>> signedExtensions =
registry.getSignedExtensionTypes();
print('Signed Extensions Keys: ${signedExtensions.keys.toList()}');

final payloadWithCustomSignedExtension = SigningPayload(
method: encodedCall,
specVersion: specVersion,
transactionVersion: transactionVersion,
genesisHash: genesisHash,
blockHash: blockHash,
blockNumber: blockNumber,
eraPeriod: 64,
nonce: 0, // Supposing it is this wallet first transaction
tip: 0,
customSignedExtensions: <String, dynamic>{
'PrevalidateAttests': 0, // A custom Signed Extensions
},
);

final customSignedExtensionPayload =
payloadWithCustomSignedExtension.encode(registry);
print('Payload: ${hex.encode(customSignedExtensionPayload)}');
}

final signature = keyring.sign(payload);
final hexSignature = hex.encode(signature);
print('Signature: $hexSignature');
Expand All @@ -75,6 +110,8 @@ Future<void> main(List<String> arguments) async {
blockNumber: blockNumber,
nonce: 0,
tip: 0,
// KeyPair Values of Custom Signed Extensions goes here
customSignedExtensions: <String, dynamic>{},
).encode(api.registry, SignatureType.sr25519);

final hexExtrinsic = hex.encode(extrinsic);
Expand Down
2 changes: 1 addition & 1 deletion examples/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ dev_dependencies:
polkadart:
output_dir: lib/generated
chains:
polkadot: wss://rpc.polkadot.io
polkadot: wss://rpc.ibp.network/polkadot
3 changes: 2 additions & 1 deletion packages/polkadart/example/multisig_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ void main() async {
final keypairR = await keyring.KeyPair.sr25519.fromUri('//keypairR');
keypairR.ss58Format = 42;

final provider = Provider.fromUri(Uri.parse('wss://westend-rpc.polkadot.io'));
final provider =
Provider.fromUri(Uri.parse('wss://rpc-polkadot.luckyfriday.io'));

///
/// Create and Fund Multisig
Expand Down
10 changes: 6 additions & 4 deletions packages/polkadart/lib/extrinsic/abstract_payload.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ abstract class Payload {
final int eraPeriod; // CheckMortality
final int nonce; // CheckNonce
final dynamic tip; // ChargeTransactionPayment
final int? assetId; // ChargeAssetTxPayment
final Map<String, dynamic> customSignedExtensions;

const Payload({
required this.method,
required this.blockNumber,
required this.eraPeriod,
required this.nonce,
required this.tip,
this.assetId,
this.customSignedExtensions = const <String, dynamic>{},
});

toEncodedMap(dynamic registry);
Map<String, dynamic> toEncodedMap(dynamic registry);

bool usesChargeAssetTxPayment(dynamic registry) {
if (registry.getSignedExtensionTypes() is Map) {
Expand All @@ -31,7 +31,9 @@ abstract class Payload {
String maybeAssetIdEncoded(dynamic registry) {
if (usesChargeAssetTxPayment(registry)) {
// '00' and '01' refer to rust's Option variants 'None' and 'Some'.
return assetId != null ? '01${assetId!.toRadixString(16)}' : '00';
return customSignedExtensions.containsKey('assetId')
? '01${customSignedExtensions['assetId'].toRadixString(16)}'
: '00';
} else {
return '';
}
Expand Down
74 changes: 49 additions & 25 deletions packages/polkadart/lib/extrinsic/extrinsic_payload.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ class ExtrinsicPayload extends Payload {
final Uint8List signer;
final Uint8List signature;

///
/// Create a new instance of [ExtrinsicPayload]
///
/// For adding assetId or other custom signedExtensions to the payload, use [customSignedExtensions] with key 'assetId' with its mapped value.
const ExtrinsicPayload({
required this.signer,
required super.method,
Expand All @@ -21,11 +25,11 @@ class ExtrinsicPayload extends Payload {
required super.blockNumber,
required super.nonce,
required super.tip,
super.assetId,
super.customSignedExtensions,
});

@override
toEncodedMap(dynamic registry) {
Map<String, dynamic> toEncodedMap(dynamic registry) {
return {
'signer': signer,
'method': method,
Expand All @@ -51,7 +55,7 @@ class ExtrinsicPayload extends Payload {
blockNumber: payload.blockNumber,
nonce: payload.nonce,
tip: payload.tip,
assetId: payload.assetId,
customSignedExtensions: payload.customSignedExtensions,
);
}

Expand Down Expand Up @@ -87,21 +91,51 @@ class ExtrinsicPayload extends Payload {
} else {
signedExtensions = SignedExtensions.substrateSignedExtensions;
}

late List<String> keys;
if (registry.getSignedExtensionTypes() is Map) {
keys = (registry.getSignedExtensionTypes() as Map<String, Codec<dynamic>>)
.keys
.toList();
} else {
keys = registry.getSignedExtensionTypes() as List<String>;
{
//
//
// Prepare keys for the encoding
if (registry.getSignedExtensionTypes() is Map) {
keys =
(registry.getSignedExtensionTypes() as Map<String, Codec<dynamic>>)
.keys
.toList();
} else {
keys = (registry.getSignedExtensionTypes() as List<dynamic>)
.cast<String>();
}
}

for (final signedExtensiontype in keys) {
final payload =
signedExtensions.signedExtension(signedExtensiontype, encodedMap);

if (payload.isNotEmpty) {
output.write(hex.decode(payload));
for (final extension in keys) {
if (encodedMap.containsKey(extension)) {
final payload = signedExtensions.signedExtension(extension, encodedMap);

if (payload.isNotEmpty) {
output.write(hex.decode(payload));
}
} else {
if (registry.getSignedExtensionTypes() is List) {
// This method call is from polkadot cli and not from the Reigstry of the polkadart_scale_codec.
continue;
}
// Most probably, it is a custom signed extension.
final signedExtensionMap = registry.getSignedExtensionTypes();

// check if this signed extension is NullCodec or not!
if (signedExtensionMap[extension] != null &&
signedExtensionMap[extension] is! NullCodec &&
signedExtensionMap[extension].hashCode !=
NullCodec.codec.hashCode) {
if (customSignedExtensions.containsKey(extension) == false) {
// throw exception as this is encodable key and we need this key to be present in customSignedExtensions
throw Exception(
'Key `$extension` is missing in customSignedExtensions.');
}
signedExtensionMap[extension]
.encodeTo(customSignedExtensions[extension], output);
}
}
}

Expand All @@ -122,14 +156,4 @@ class ExtrinsicPayload extends Payload {
dynamic registry, SignatureType signatureType, keyring.KeyPair keyPair) {
return keyPair.sign(encode(registry, signatureType));
}

@override
String maybeAssetIdEncoded(dynamic registry) {
if (usesChargeAssetTxPayment(registry)) {
// '00' and '01' refer to rust's Option variants 'None' and 'Some'.
return assetId != null ? '01${assetId!.toRadixString(16)}' : '00';
} else {
return '';
}
}
}
135 changes: 102 additions & 33 deletions packages/polkadart/lib/extrinsic/signing_payload.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ class SigningPayload extends Payload {
final String genesisHash; // CheckGenesis
final String blockHash; // CheckMortality

///
/// Create a new instance of [SigningPayload]
///
/// For adding assetId or other custom signedExtensions to the payload, use [customSignedExtensions] with key 'assetId' with its mapped value.
const SigningPayload({
required super.method,
required this.specVersion,
Expand All @@ -24,11 +28,11 @@ class SigningPayload extends Payload {
required super.eraPeriod,
required super.nonce,
required super.tip,
super.assetId,
super.customSignedExtensions,
});

@override
toEncodedMap(dynamic registry) {
Map<String, dynamic> toEncodedMap(dynamic registry) {
return {
'method': method,
'specVersion': encodeHex(U32Codec.codec.encode(specVersion)),
Expand Down Expand Up @@ -63,43 +67,108 @@ class SigningPayload extends Payload {

final encodedMap = toEncodedMap(registry);

late List<String> typeKeys;
if (registry.getSignedExtensionTypes() is Map) {
// Usage here for the Registry from the polkadart_scale_codec
typeKeys =
(registry.getSignedExtensionTypes() as Map<String, Codec<dynamic>>)
.keys
.toList();
} else {
// Usage here for the generated lib from the polkadart_cli
typeKeys = registry.getSignedExtensionTypes() as List<String>;
late List<String> signedExtensionKeys;

//
//
// Do the keys preparation of signedExtensions
{
if (registry.getSignedExtensionTypes() is Map) {
// Usage here for the Registry from the polkadart_scale_codec
signedExtensionKeys =
(registry.getSignedExtensionTypes() as Map<String, Codec<dynamic>>)
.keys
.toList();
} else {
// Usage here for the generated lib from the polkadart_cli
signedExtensionKeys =
(registry.getSignedExtensionTypes() as List<dynamic>)
.cast<String>();
}
}

for (final extension in typeKeys) {
final payload = signedExtensions.signedExtension(extension, encodedMap);

if (payload.isNotEmpty) {
tempOutput.write(hex.decode(payload));
//
// Traverse through the signedExtension keys and encode the payload
for (final extension in signedExtensionKeys) {
if (encodedMap.containsKey(extension)) {
final payload = signedExtensions.signedExtension(extension, encodedMap);

if (payload.isNotEmpty) {
tempOutput.write(hex.decode(payload));
}
} else {
if (registry.getSignedExtensionTypes() is List) {
// This method call is from polkadot cli and not from the Reigstry of the polkadart_scale_codec.
continue;
}
// Most probably, it is a custom signed extension.
// check if this signed extension is NullCodec or not!
final signedExtensionMap = registry.getSignedExtensionTypes();
if (signedExtensionMap[extension] != null &&
signedExtensionMap[extension] is! NullCodec &&
signedExtensionMap[extension].hashCode !=
NullCodec.codec.hashCode) {
if (customSignedExtensions.containsKey(extension) == false) {
// throw exception as this is encodable key and we need this key to be present in customSignedExtensions
throw Exception(
'Key `$extension` is missing in customSignedExtensions.');
}
signedExtensionMap[extension]
.encodeTo(customSignedExtensions[extension], tempOutput);
}
}
}
late List<String> extraKeys;
if (registry.getSignedExtensionTypes() is Map) {
// Usage here for the Registry from the polkadart_scale_codec
extraKeys =
(registry.getSignedExtensionTypes() as Map<String, Codec<dynamic>>)
.keys
.toList();
} else {
// Usage here for the generated lib from the polkadart_cli
extraKeys = registry.getSignedExtensionExtra() as List<String>;
}

for (final extension in extraKeys) {
final payload =
signedExtensions.additionalSignedExtension(extension, encodedMap);
late List<String> additionalSignedExtensionKeys;
{
//
// Do the keys preparation of signedExtensions
if (registry.getSignedExtensionTypes() is Map) {
// Usage here for the Registry from the polkadart_scale_codec
additionalSignedExtensionKeys =
(registry.getAdditionalSignedExtensionTypes()
as Map<String, Codec<dynamic>>)
.keys
.toList();
} else {
// Usage here for the generated lib from the polkadart_cli
additionalSignedExtensionKeys =
(registry.getSignedExtensionExtra() as List<dynamic>)
.cast<String>();
}
}

if (payload.isNotEmpty) {
tempOutput.write(hex.decode(payload));
//
// Traverse through the additionalSignedExtension keys and encode the payload
for (final extension in additionalSignedExtensionKeys) {
if (encodedMap.containsKey(extension)) {
final payload =
signedExtensions.additionalSignedExtension(extension, encodedMap);

if (payload.isNotEmpty) {
tempOutput.write(hex.decode(payload));
}
} else {
// Most probably, it is a custom signed extension.
// check if this signed extension is NullCodec or not!
if (registry.getSignedExtensionTypes() is List) {
// This method call is from polkadot cli and not from the Reigstry of the polkadart_scale_codec.
continue;
}
final additionalSignedExtensionMap =
registry.getAdditionalSignedExtensionTypes();
if (additionalSignedExtensionMap[extension] != null &&
additionalSignedExtensionMap[extension] is! NullCodec &&
additionalSignedExtensionMap[extension].hashCode !=
NullCodec.codec.hashCode) {
if (customSignedExtensions.containsKey(extension) == false) {
// throw exception as this is encodable key and we need this key to be present in customSignedExtensions
throw Exception(
'Key `$extension` is missing in customSignedExtensions.');
}
additionalSignedExtensionMap[extension]
.encodeTo(customSignedExtensions[extension], tempOutput);
}
}
}

Expand Down
Loading

0 comments on commit 4b6dd7e

Please sign in to comment.