Polkadart Logo
Substrate Metadata

Working with Metadata

Understanding and working with Substrate runtime metadata

Understanding Runtime Metadata

Runtime metadata is a comprehensive description of a blockchain's capabilities. It contains everything your application needs to know to interact with the chain, including:

  • Available pallets (modules)
  • Storage structure and types
  • Callable functions (extrinsics)
  • Events that can be emitted
  • Constants defined in the runtime
  • Type definitions for all data structures

Metadata Versions

The substrate_metadata package supports two metadata versions:

Metadata V14

The original production-ready metadata format:

import 'package:substrate_metadata/metadata/metadata.dart';

// Decode V14 metadata
final prefixed = RuntimeMetadataPrefixed.codec.decode(input);

if (prefixed.metadata is RuntimeMetadataV14) {
  final v14 = prefixed.metadata as RuntimeMetadataV14;
  print('Pallets: ${v14.pallets.length}');
  print('Types: ${v14.types.length}');
}

Metadata V15

Enhanced metadata format with additional features:

  • Runtime APIs metadata
  • Custom metadata section
  • Outer enums for better type organization
if (prefixed.metadata is RuntimeMetadataV15) {
  final v15 = prefixed.metadata as RuntimeMetadataV15;
  print('Runtime APIs: ${v15.apis.length}');
  print('Outer enums: ${v15.outerEnums.callType}');
}

Metadata Structure

RuntimeMetadataPrefixed

The top-level container for all metadata:

class RuntimeMetadataPrefixed {
  final int magicNumber;     // Should be 0x6d657461 ('meta')
  final RuntimeMetadata metadata;  // V14 or V15

  bool get isValidMagicNumber => magicNumber == 0x6d657461;
}

Example usage:

// Decode metadata
final input = Input.fromBytes(metadataBytes);
final prefixed = RuntimeMetadataPrefixed.codec.decode(input);

// Validate
if (!prefixed.isValidMagicNumber) {
  throw Exception('Invalid metadata magic number');
}

// Get version
print('Metadata version: ${prefixed.metadata.version}');

Pallet Metadata

Each pallet in the runtime has its own metadata:

// Get pallet by name
final systemPallet = metadata.pallets.firstWhere(
  (p) => p.name == 'System',
);

print('Pallet index: ${systemPallet.index}');
print('Has storage: ${systemPallet.storage != null}');
print('Has calls: ${systemPallet.calls != null}');
print('Has events: ${systemPallet.event != null}');
print('Number of constants: ${systemPallet.constants.length}');

Storage Metadata

Storage items define how data is stored on-chain:

final storage = systemPallet.storage;

for (final entry in storage.entries) {
  print('Storage name: ${entry.name}');
  print('Modifier: ${entry.modifier}'); // Optional, Default, or Required

  // Check storage type
  switch (entry.type) {
    case StorageEntryTypePlain(:final valueType):
      print('Plain storage, value type ID: $valueType');
      break;
    case StorageEntryTypeMap(:final hashers, :final keyType, :final valueType):
      print('Map storage');
      print('Key type ID: $keyType');
      print('Value type ID: $valueType');
      print('Hashers: ${hashers.map((h) => h.name).join(", ")}');
      break;
  }
}

Event Metadata

Events that can be emitted by pallets:

final eventMetadata = systemPallet.event;

if (eventMetadata != null) {
  print('Event type ID: ${eventMetadata.type}');

  // Get the actual event variants from the type registry
  final eventType = registry.typeById(eventMetadata.type);

  if (eventType.type.typeDef is TypeDefVariant) {
    final variants = (eventType.type.typeDef as TypeDefVariant).variants;
    for (final variant in variants) {
      print('Event: ${variant.name}');
      print('Fields: ${variant.fields.length}');
    }
  }
}

Call Metadata

Callable functions (extrinsics) defined by pallets:

final callMetadata = systemPallet.calls;

if (callMetadata != null) {
  print('Call type ID: ${callMetadata.type}');

  // Get the actual call variants
  final callType = registry.typeById(callMetadata.type);

  if (callType.type.typeDef is TypeDefVariant) {
    final variants = (callType.type.typeDef as TypeDefVariant).variants;
    for (final variant in variants) {
      print('Call: ${variant.name}');
      print('Parameters: ${variant.fields.length}');
    }
  }
}

Constants Metadata

Runtime constants defined by pallets:

for (final constant in systemPallet.constants) {
  print('Constant: ${constant.name}');
  print('Type ID: ${constant.type}');
  print('Docs: ${constant.docs.join(" ")}');

  // Decode the constant value
  final codec = registry.codecFor(constant.type);
  final value = codec.decode(Input.fromBytes(constant.value));
  print('Value: $value');
}

Extrinsic Metadata

Metadata about the transaction format:

final extrinsicMetadata = metadata.extrinsic;

print('Version: ${extrinsicMetadata.version}');
print('Address type: ${extrinsicMetadata.addressType}');
print('Call type: ${extrinsicMetadata.callType}');
print('Signature type: ${extrinsicMetadata.signatureType}');

// Signed extensions (transaction extras)
for (final ext in extrinsicMetadata.signedExtensions) {
  print('Extension: ${ext.identifier}');
  print('Type: ${ext.type}');
  print('Additional signed: ${ext.additionalSigned}');
}

V15 Specific Features

Runtime APIs

V15 metadata includes information about runtime APIs:

if (metadata is RuntimeMetadataV15) {
  for (final api in metadata.apis) {
    print('API: ${api.name}');

    for (final method in api.methods) {
      print('  Method: ${method.name}');
      print('  Inputs: ${method.inputs.length}');
      print('  Output type: ${method.output}');
    }
  }
}

Outer Enums

V15 provides top-level enum types for better organization:

if (metadata is RuntimeMetadataV15) {
  final outerEnums = metadata.outerEnums;

  print('Call type: ${outerEnums.callType}');
  print('Event type: ${outerEnums.eventType}');
  print('Error type: ${outerEnums.errorType}');
}

Complete Example

Here's a comprehensive example that explores all metadata:

import 'package:substrate_metadata/substrate_metadata.dart';

void exploreMetadata(Uint8List metadataBytes) {
  // Decode metadata
  final input = Input.fromBytes(metadataBytes);
  final prefixed = RuntimeMetadataPrefixed.codec.decode(input);
  final registry = MetadataTypeRegistry(prefixed);

  print('=== Metadata Information ===');
  print('Version: ${registry.version}');
  print('Magic number valid: ${prefixed.isValidMagicNumber}');
  print('');

  print('=== Pallets ===');
  for (final pallet in registry.pallets) {
    print('Pallet: ${pallet.name} (index: ${pallet.index})');

    // Storage
    if (pallet.storage != null) {
      print('  Storage items: ${pallet.storage!.entries.length}');
    }

    // Calls
    if (pallet.calls != null) {
      final callType = registry.typeById(pallet.calls!.type);
      if (callType.type.typeDef is TypeDefVariant) {
        final variants = (callType.type.typeDef as TypeDefVariant).variants;
        print('  Calls: ${variants.length}');
      }
    }

    // Events
    if (pallet.event != null) {
      final eventType = registry.typeById(pallet.event!.type);
      if (eventType.type.typeDef is TypeDefVariant) {
        final variants = (eventType.type.typeDef as TypeDefVariant).variants;
        print('  Events: ${variants.length}');
      }
    }

    // Constants
    print('  Constants: ${pallet.constants.length}');
    print('');
  }

  print('=== Extrinsic Format ===');
  final extrinsic = registry.extrinsic;
  print('Version: ${extrinsic.version}');
  print('Signed extensions: ${extrinsic.signedExtensions.length}');
  print('');

  // V15 specific
  if (registry.version == 15) {
    final metadata = prefixed.metadata as RuntimeMetadataV15;
    print('=== Runtime APIs (V15) ===');
    for (final api in metadata.apis) {
      print('${api.name}: ${api.methods.length} methods');
    }
  }
}

Best Practices

Cache the Registry

Creating a MetadataTypeRegistry is expensive. Create it once and reuse it for all operations.

Validate Metadata

Always check the magic number and version before using metadata.

Handle Version Differences

Not all chains use V15. Always check the version and handle both V14 and V15.

Use ChainInfo

For common operations, use the ChainInfo facade instead of working with raw metadata directly.

Next Steps