Type Registry
What is the Type Registry?
The MetadataTypeRegistry is the central component for working with types in Substrate metadata. It acts as a type resolution engine that:
- Resolves type IDs to their definitions
- Creates and caches codecs for encoding/decoding
- Handles recursive types safely
- Provides efficient lookups for pallets and their components
Think of it as a smart factory that knows how to build the right codec for any type in the metadata.
Creating a Registry
import 'package:substrate_metadata/substrate_metadata.dart';
// From raw metadata bytes
final input = Input.fromBytes(metadataBytes);
final prefixed = RuntimeMetadataPrefixed.codec.decode(input);
final registry = MetadataTypeRegistry(prefixed);
// Or use ChainInfo which includes a registry
final chainInfo = ChainInfo.fromMetadata(metadataBytes);
final registry = chainInfo.registry;
The registry automatically:
- Validates the magic number
- Checks version compatibility (V14 or V15)
- Builds pallet indices for O(1) lookups
- Initializes caching structures
Core Type Resolution
Getting Codecs by Type ID
The most fundamental operation is getting a codec for a type:
// Get codec for a specific type ID
final codec = registry.codecFor(42);
// Use the codec to decode data
final value = codec.decode(input);
// Or encode data
final encoded = codec.encode(myData);
The registry automatically handles:
- Primitive types (u8, u32, bool, etc.)
- Composite types (structs)
- Variant types (enums)
- Sequences (Vec)
- Arrays
- Tuples
- Compact encoding
- Recursive types
Type Caching
Codecs are expensive to create, so the registry caches them:
// First call - creates and caches
final codec1 = registry.codecFor(42);
// Second call - returns cached version
final codec2 = registry.codecFor(42);
// Same instance
assert(identical(codec1, codec2));
// Check cache statistics
final stats = registry.cacheStats;
print('Cached codecs: ${stats.codecCacheSize}');
print('Cached paths: ${stats.typePathCacheSize}');
Handling Recursive Types
The registry safely handles recursive types using a proxy pattern:
// Example: A linked list type that references itself
// The registry detects recursion and uses ProxyCodec
final listCodec = registry.codecFor(linkedListTypeId);
// The codec works correctly despite the recursion
final list = listCodec.decode(input);
Type Lookups
By Type ID
Direct lookup by type ID:
final portableType = registry.typeById(42);
print('Type path: ${portableType.type.path.join("::")}');
print('Type params: ${portableType.type.params.length}');
// Access the type definition
switch (portableType.type.typeDef) {
case TypeDefPrimitive(:final primitive):
print('Primitive: ${primitive.name}');
case TypeDefComposite(:final fields):
print('Composite with ${fields.length} fields');
case TypeDefVariant(:final variants):
print('Enum with ${variants.length} variants');
// ... other cases
}
By Type Path
Lookup by the type's full path (slower, but cached):
// Find type by path
final optionType = registry.typeByPath('Option');
final resultType = registry.typeByPath('Result');
final accountIdType = registry.typeByPath('sp_core::crypto::AccountId32');
// Get codec by path
final codec = registry.codecForPath('Option');
Pallet Access
The registry provides efficient pallet lookups:
By Name (O(1))
final systemPallet = registry.palletByName('System');
final balancesPallet = registry.palletByName('Balances');
if (systemPallet != null) {
print('Index: ${systemPallet.index}');
print('Storage items: ${systemPallet.storage?.entries.length ?? 0}');
}
By Index (O(1))
final pallet = registry.palletByIndex(0); // Usually System
All Pallets
for (final pallet in registry.pallets) {
print('${pallet.name} (${pallet.index})');
}
Pallet Component Types
Get type IDs for pallet components:
// Calls type
final callTypeId = registry.getPalletCallType('Balances');
if (callTypeId != null) {
final callCodec = registry.codecFor(callTypeId);
}
// Events type
final eventTypeId = registry.getPalletEventType('System');
// Errors type
final errorTypeId = registry.getPalletErrorType('Balances');
Storage Access
Storage Metadata
Get metadata for a storage entry:
final storage = registry.getStorageMetadata('System', 'Account');
if (storage != null) {
print('Name: ${storage.name}');
print('Modifier: ${storage.modifier}');
print('Default: ${storage.defaultValue}');
// Check storage type
switch (storage.type) {
case StorageEntryTypePlain(:final valueType):
print('Plain storage, value type: $valueType');
case StorageEntryTypeMap(:final hashers, :final keyType, :final valueType):
print('Map storage');
print('Key type: $keyType');
print('Value type: $valueType');
print('Hashers: $hashers');
}
}
Storage Type ID
Get just the type ID:
final typeId = registry.getStorageType('System', 'Account');
if (typeId != null) {
final codec = registry.codecFor(typeId);
// Use codec to decode storage value
}
Storage Hashers
Get the hashers used for a storage map:
final hashers = registry.getStorageHashers('System', 'Account');
// hashers contains the list of StorageHasherEnum values
Constants Access
Constant Metadata
final constant = registry.getConstantMetadata('System', 'BlockHashCount');
if (constant != null) {
print('Name: ${constant.name}');
print('Type: ${constant.type}');
print('Documentation: ${constant.docs.join(" ")}');
}
Direct Value Access
Get the decoded constant value directly:
final blockHashCount = registry.getConstantValue('System', 'BlockHashCount');
print('BlockHashCount: $blockHashCount');
final existentialDeposit = registry.getConstantValue('Balances', 'ExistentialDeposit');
print('ExistentialDeposit: $existentialDeposit');
Variant and Composite Resolution
Get Variant by Name
For enum types:
final callType = registry.getPalletCallType('Balances');
if (callType != null) {
final transferVariant = registry.getVariant(callType, 'transfer');
if (transferVariant != null) {
print('Variant index: ${transferVariant.index}');
print('Fields: ${transferVariant.fields.length}');
for (final field in transferVariant.fields) {
print('Field: ${field.name ?? "unnamed"}, type: ${field.type}');
}
}
}
Get Variant by Index
final variant = registry.getVariantByIndex(callType, 0);
Get Composite Fields
For struct types:
final fields = registry.getFields(typeId);
if (fields != null) {
for (final field in fields) {
print('Field: ${field.name ?? "unnamed"}');
print('Type: ${field.type}');
print('Type name: ${field.typeName}');
}
}
Extrinsic & Common Types
Extrinsic Metadata
final extrinsic = registry.extrinsic;
print('Version: ${extrinsic.version}');
print('Address type: ${registry.accountIdType}');
print('Signature type: ${registry.signatureType}');
print('Call type: ${registry.callType}');
print('Extra type: ${registry.extraType}');
Signed Extensions
final extensions = registry.signedExtensions;
for (final ext in extensions) {
print('Extension: ${ext.identifier}');
print('Type: ${ext.type}');
print('Additional signed: ${ext.additionalSigned}');
}
V15 Specific Features
When working with V15 metadata:
Outer Enums
final outerEnums = registry.outerEnums;
if (outerEnums != null) {
final callCodec = registry.codecFor(outerEnums.callType);
final eventCodec = registry.codecFor(outerEnums.eventType);
final errorCodec = registry.codecFor(outerEnums.errorType);
}
Runtime APIs
final contractsApi = registry.getRuntimeApi('ContractsApi');
if (contractsApi != null) {
print('Methods: ${contractsApi.methods.length}');
for (final method in contractsApi.methods) {
print('Method: ${method.name}');
}
}
Runtime API Methods
final method = registry.getRuntimeApiMethod('ContractsApi', 'call');
if (method != null) {
print('Inputs: ${method.inputs.length}');
print('Output type: ${method.output}');
// Get output codec
final outputCodec = registry.codecFor(method.output);
}
Or use the convenience method:
final codec = registry.getRuntimeApiOutputCodec('ContractsApi', 'call');
if (codec != null) {
final result = codec.decode(responseBytes);
}
Utility Methods
Type Checking
// Check if a type is Option<T>
if (registry.isOption(typeId)) {
print('This is an Option type');
}
// Check if a type is Result<T, E>
if (registry.isResult(typeId)) {
print('This is a Result type');
}
// Check if a type is Vec<T>
if (registry.isSequence(typeId)) {
print('This is a sequence/Vec type');
}
Cache Management
// Get cache statistics
final stats = registry.cacheStats;
print('Total cached items: ${stats.totalCachedItems}');
print('Codecs: ${stats.codecCacheSize}');
print('Type paths: ${stats.typePathCacheSize}');
print('Proxy codecs: ${stats.proxyCodecCount}');
// Clear all caches (rarely needed)
registry.clearCache();
Complete Example
Here's a comprehensive example using the registry:
import 'package:substrate_metadata/substrate_metadata.dart';
void demonstrateRegistry(Uint8List metadataBytes) {
// Create registry
final chainInfo = ChainInfo.fromMetadata(metadataBytes);
final registry = chainInfo.registry;
print('=== Basic Info ===');
print('Version: ${registry.version}');
print('Pallets: ${registry.pallets.length}');
print('\n=== Decode Account Info ===');
// Get the Account storage type
final accountTypeId = registry.getStorageType('System', 'Account');
if (accountTypeId != null) {
final codec = registry.codecFor(accountTypeId);
final accountInfo = codec.decode(accountDataInput);
print('Account data: $accountInfo');
}
print('\n=== Decode an Event ===');
final eventTypeId = registry.outerEnums?.eventType ??
registry.getPalletEventType('System');
if (eventTypeId != null) {
final eventCodec = registry.codecFor(eventTypeId);
// Use the codec to decode events
}
print('\n=== Access Constants ===');
final ss58Prefix = registry.getConstantValue('System', 'SS58Prefix');
print('SS58 Prefix: $ss58Prefix');
final existentialDeposit = registry.getConstantValue('Balances', 'ExistentialDeposit');
print('Existential Deposit: $existentialDeposit');
print('\n=== Cache Statistics ===');
final stats = registry.cacheStats;
print('Cached codecs: ${stats.codecCacheSize}');
print('Cached paths: ${stats.typePathCacheSize}');
}
Performance Tips
Reuse the Registry
Create the registry once and reuse it. Creating a new registry is expensive.Prefer Type IDs
When possible, use type IDs instead of paths. ID lookups are O(1), path lookups are O(n).Cache Lookup Results
If you're looking up the same pallet/storage/constant repeatedly, cache the results.Use ChainInfo for Common Operations
For standard operations, useChainInfo instead of directly using the registry.