diff --git a/examples/bin/extrinsic_demo.dart b/examples/bin/extrinsic_demo.dart index e82e5828..d43a3a67 100644 --- a/examples/bin/extrinsic_demo.dart +++ b/examples/bin/extrinsic_demo.dart @@ -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'; @@ -63,6 +65,39 @@ Future main(List 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> + final Map> 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: { + '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'); @@ -75,6 +110,8 @@ Future main(List arguments) async { blockNumber: blockNumber, nonce: 0, tip: 0, + // KeyPair Values of Custom Signed Extensions goes here + customSignedExtensions: {}, ).encode(api.registry, SignatureType.sr25519); final hexExtrinsic = hex.encode(extrinsic); diff --git a/examples/pubspec.yaml b/examples/pubspec.yaml index bd05e3ad..a0221aaf 100644 --- a/examples/pubspec.yaml +++ b/examples/pubspec.yaml @@ -27,4 +27,4 @@ dev_dependencies: polkadart: output_dir: lib/generated chains: - polkadot: wss://rpc.polkadot.io + polkadot: wss://rpc.ibp.network/polkadot diff --git a/packages/polkadart/example/multisig_example.dart b/packages/polkadart/example/multisig_example.dart index 883b55db..41577d27 100644 --- a/packages/polkadart/example/multisig_example.dart +++ b/packages/polkadart/example/multisig_example.dart @@ -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 diff --git a/packages/polkadart/lib/extrinsic/abstract_payload.dart b/packages/polkadart/lib/extrinsic/abstract_payload.dart index 3518a791..6e14a8b8 100644 --- a/packages/polkadart/lib/extrinsic/abstract_payload.dart +++ b/packages/polkadart/lib/extrinsic/abstract_payload.dart @@ -6,7 +6,7 @@ abstract class Payload { final int eraPeriod; // CheckMortality final int nonce; // CheckNonce final dynamic tip; // ChargeTransactionPayment - final int? assetId; // ChargeAssetTxPayment + final Map customSignedExtensions; const Payload({ required this.method, @@ -14,10 +14,10 @@ abstract class Payload { required this.eraPeriod, required this.nonce, required this.tip, - this.assetId, + this.customSignedExtensions = const {}, }); - toEncodedMap(dynamic registry); + Map toEncodedMap(dynamic registry); bool usesChargeAssetTxPayment(dynamic registry) { if (registry.getSignedExtensionTypes() is Map) { @@ -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 ''; } diff --git a/packages/polkadart/lib/extrinsic/extrinsic_payload.dart b/packages/polkadart/lib/extrinsic/extrinsic_payload.dart index 1c43c198..45fc770d 100644 --- a/packages/polkadart/lib/extrinsic/extrinsic_payload.dart +++ b/packages/polkadart/lib/extrinsic/extrinsic_payload.dart @@ -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, @@ -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 toEncodedMap(dynamic registry) { return { 'signer': signer, 'method': method, @@ -51,7 +55,7 @@ class ExtrinsicPayload extends Payload { blockNumber: payload.blockNumber, nonce: payload.nonce, tip: payload.tip, - assetId: payload.assetId, + customSignedExtensions: payload.customSignedExtensions, ); } @@ -87,21 +91,51 @@ class ExtrinsicPayload extends Payload { } else { signedExtensions = SignedExtensions.substrateSignedExtensions; } + late List keys; - if (registry.getSignedExtensionTypes() is Map) { - keys = (registry.getSignedExtensionTypes() as Map>) - .keys - .toList(); - } else { - keys = registry.getSignedExtensionTypes() as List; + { + // + // + // Prepare keys for the encoding + if (registry.getSignedExtensionTypes() is Map) { + keys = + (registry.getSignedExtensionTypes() as Map>) + .keys + .toList(); + } else { + keys = (registry.getSignedExtensionTypes() as List) + .cast(); + } } - 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); + } } } @@ -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 ''; - } - } } diff --git a/packages/polkadart/lib/extrinsic/signing_payload.dart b/packages/polkadart/lib/extrinsic/signing_payload.dart index 2938dc91..96c60f56 100644 --- a/packages/polkadart/lib/extrinsic/signing_payload.dart +++ b/packages/polkadart/lib/extrinsic/signing_payload.dart @@ -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, @@ -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 toEncodedMap(dynamic registry) { return { 'method': method, 'specVersion': encodeHex(U32Codec.codec.encode(specVersion)), @@ -63,43 +67,108 @@ class SigningPayload extends Payload { final encodedMap = toEncodedMap(registry); - late List typeKeys; - if (registry.getSignedExtensionTypes() is Map) { - // Usage here for the Registry from the polkadart_scale_codec - typeKeys = - (registry.getSignedExtensionTypes() as Map>) - .keys - .toList(); - } else { - // Usage here for the generated lib from the polkadart_cli - typeKeys = registry.getSignedExtensionTypes() as List; + late List 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>) + .keys + .toList(); + } else { + // Usage here for the generated lib from the polkadart_cli + signedExtensionKeys = + (registry.getSignedExtensionTypes() as List) + .cast(); + } } - 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 extraKeys; - if (registry.getSignedExtensionTypes() is Map) { - // Usage here for the Registry from the polkadart_scale_codec - extraKeys = - (registry.getSignedExtensionTypes() as Map>) - .keys - .toList(); - } else { - // Usage here for the generated lib from the polkadart_cli - extraKeys = registry.getSignedExtensionExtra() as List; - } - for (final extension in extraKeys) { - final payload = - signedExtensions.additionalSignedExtension(extension, encodedMap); + late List 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>) + .keys + .toList(); + } else { + // Usage here for the generated lib from the polkadart_cli + additionalSignedExtensionKeys = + (registry.getSignedExtensionExtra() as List) + .cast(); + } + } - 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); + } } } diff --git a/packages/polkadart/lib/multisig/multisig.dart b/packages/polkadart/lib/multisig/multisig.dart index cf35e3f9..b66692a5 100644 --- a/packages/polkadart/lib/multisig/multisig.dart +++ b/packages/polkadart/lib/multisig/multisig.dart @@ -11,7 +11,6 @@ class Multisig { required Provider provider, required List otherSignatoriesAddressList, int tip = 0, - int assetId = 0, int eraPeriod = 64, }) async { final meta = await MultiSigMeta.fromProvider(provider: provider); @@ -32,7 +31,6 @@ class Multisig { amount, ), tip: tip, - assetId: assetId, eraPeriod: eraPeriod, signer: depositorKeyPair, provider: provider, @@ -93,7 +91,6 @@ class Multisig { required KeyPair signer, required Provider provider, required int tip, - required int assetId, required int eraPeriod, }) async { final nonce = await SystemApi(provider).accountNextIndex(signer.address); @@ -102,7 +99,6 @@ class Multisig { /// Creating the first approveAsMulti from the account creator. final unsignedApprovalPayload = SigningPayload( method: method, - assetId: assetId, blockHash: hex.encode(meta.blockHash), blockNumber: meta.blockNumber, eraPeriod: eraPeriod, @@ -147,7 +143,6 @@ class Multisig { required KeyPair signer, Duration storageKeyDelay = const Duration(seconds: 20), int tip = 0, - int assetId = 0, int eraPeriod = 64, }) async { final MultisigStorage? multisigStorage = await fetchMultisigStorage( @@ -184,7 +179,6 @@ class Multisig { multiSigStorage: multisigStorage, ), tip: tip, - assetId: assetId, eraPeriod: eraPeriod, signer: signer, provider: provider, @@ -202,7 +196,6 @@ class Multisig { required KeyPair signer, Duration storageKeyDelay = const Duration(seconds: 20), int tip = 0, - int assetId = 0, int eraPeriod = 64, }) async { final MultisigStorage? multisigStorage = await fetchMultisigStorage( @@ -250,7 +243,6 @@ class Multisig { multiSigStorage: multisigStorage, ), tip: tip, - assetId: assetId, eraPeriod: eraPeriod, signer: signer, provider: provider, @@ -268,7 +260,6 @@ class Multisig { required KeyPair signer, Duration storageKeyDelay = const Duration(seconds: 25), int tip = 0, - int assetId = 0, int eraPeriod = 64, }) async { final MultisigStorage? multisigStorage = await fetchMultisigStorage( @@ -310,7 +301,6 @@ class Multisig { ), signer: signer, tip: tip, - assetId: assetId, eraPeriod: eraPeriod, provider: provider, ); diff --git a/packages/polkadart_scale_codec/lib/core/registry.dart b/packages/polkadart_scale_codec/lib/core/registry.dart index 9552a454..d8f0cc78 100644 --- a/packages/polkadart_scale_codec/lib/core/registry.dart +++ b/packages/polkadart_scale_codec/lib/core/registry.dart @@ -8,6 +8,7 @@ class Registry { final Map _proxyLoaders = {}; final Map _proxies = {}; final Map signedExtensions = {}; + final Map additionalSignedExtensions = {}; late int extrinsicVersion; Registry(); @@ -22,6 +23,12 @@ class Registry { return signedExtensions; } + /// + /// Get Additional Signed Extensions + Map getAdditionalSignedExtensionTypes() { + return additionalSignedExtensions; + } + ProxyCodec _createProxy( Map metadata, String key, dynamic value) { if (_proxies[key] != null) { diff --git a/packages/substrate_metadata/lib/parsers/v14_parser.dart b/packages/substrate_metadata/lib/parsers/v14_parser.dart index 0da8a512..433926a1 100644 --- a/packages/substrate_metadata/lib/parsers/v14_parser.dart +++ b/packages/substrate_metadata/lib/parsers/v14_parser.dart @@ -233,55 +233,77 @@ class V14Parser { extrinsicSignature[name] = codec; } } - final signedExtensionsCompositeCodec = {}; - // Set the extrinsic version which would be helpful further in extrinsic and signing payload. - _resultingRegistry.extrinsicVersion = - rawMetadata['extrinsic']['version']!; + // SignedExtensions Parsing + { + final signedExtensionsCompositeCodec = {}; - // clear the signedExtensions in the registry - _resultingRegistry.signedExtensions.clear(); + // Set the extrinsic version which would be helpful further in extrinsic and signing payload. + _resultingRegistry.extrinsicVersion = + rawMetadata['extrinsic']['version']!; - for (final signedExtensions in rawMetadata['extrinsic'] - ['signedExtensions']) { - final type = siTypes[signedExtensions['type']]; - final identifier = signedExtensions['identifier'].toString(); + // clear the signedExtensions in the registry + _resultingRegistry.signedExtensions.clear(); - // put a NullCodec which does nothing - _resultingRegistry.signedExtensions[identifier] = NullCodec.codec; + for (final signedExtensions in rawMetadata['extrinsic'] + ['signedExtensions']) { + final type = siTypes[signedExtensions['type']]; + final additionalSignedType = + siTypes[signedExtensions['additionalSigned']]; + final identifier = signedExtensions['identifier'].toString(); - if (type == null || type.toLowerCase() == 'null') { - continue; - } - final typeCodec = _resultingRegistry.parseSpecificCodec( - _metadataExpander.customCodecRegister, type); + // put a NullCodec which does nothing + _resultingRegistry.signedExtensions[identifier] = NullCodec.codec; - // overwrite the codec here. - _resultingRegistry.signedExtensions[identifier] = typeCodec; + // put a NullCodec which does nothing + _resultingRegistry.additionalSignedExtensions[identifier] = + NullCodec.codec; - String newIdentifier = identifier.replaceAll('Check', ''); - switch (newIdentifier) { - case 'Mortality': - signedExtensionsCompositeCodec['era'] = - _resultingRegistry.parseSpecificCodec( - _metadataExpander.customCodecRegister, 'Era'); + if (type == null || type.toLowerCase() == 'null') { continue; - case 'ChargeTransactionPayment': - newIdentifier = 'tip'; - break; - default: + } + final typeCodec = _resultingRegistry.parseSpecificCodec( + _metadataExpander.customCodecRegister, type); + + // overwrite the codec here. + _resultingRegistry.signedExtensions[identifier] = typeCodec; + + if (additionalSignedType != null || + additionalSignedType?.toLowerCase() != 'null') { + final additionalSignedTypeCodec = + _resultingRegistry.parseSpecificCodec( + _metadataExpander.customCodecRegister, + additionalSignedType!); + + // overwrite the additional signed codec here. + _resultingRegistry.additionalSignedExtensions[identifier] = + additionalSignedTypeCodec; + } + + String newIdentifier = identifier.replaceAll('Check', ''); + switch (newIdentifier) { + case 'Mortality': + signedExtensionsCompositeCodec['era'] = + _resultingRegistry.parseSpecificCodec( + _metadataExpander.customCodecRegister, 'Era'); + continue; + case 'ChargeTransactionPayment': + newIdentifier = 'tip'; + break; + default: + } + signedExtensionsCompositeCodec[newIdentifier.snakeCase()] = typeCodec; } - signedExtensionsCompositeCodec[newIdentifier.snakeCase()] = typeCodec; - } - final extrinsicCodec = CompositeCodec( - { - ...extrinsicSignature, - 'signedExtensions': CompositeCodec(signedExtensionsCompositeCodec), - }, - ); + final extrinsicCodec = CompositeCodec( + { + ...extrinsicSignature, + 'signedExtensions': CompositeCodec(signedExtensionsCompositeCodec), + }, + ); - _resultingRegistry.addCodec('ExtrinsicSignatureCodec', extrinsicCodec); + _resultingRegistry.addCodec('ExtrinsicSignatureCodec', extrinsicCodec); + } } return ChainInfo(