Skip to content

Chain Data

Introduction

Understanding and leveraging on-chain data is a fundamental aspect of blockchain development. Whether you're building frontend applications or backend systems, accessing and decoding runtime metadata is vital to interacting with the blockchain. This guide introduces you to the tools and processes for generating and retrieving metadata, explains its role in application development, and outlines the additional APIs available for interacting with a Polkadot node. By mastering these components, you can ensure seamless communication between your applications and the blockchain.

Application Development

You might not be directly involved in building frontend applications as a blockchain developer. However, most applications that run on a blockchain require some form of frontend or user-facing client to enable users or other programs to access and modify the data that the blockchain stores. For example, you might develop a browser-based, mobile, or desktop application that allows users to submit transactions, post articles, view their assets, or track previous activity. The backend for that application is configured in the runtime logic for your blockchain, but the frontend client makes the runtime features accessible to your users.

For your custom chain to be useful to others, you'll need to provide a client application that allows users to view, interact with, or update information that the blockchain keeps track of. In this article, you'll learn how to expose information about your runtime so that client applications can use it, see examples of the information exposed, and explore tools and libraries that use it.

Understand Metadata

Polkadot SDK-based blockchain networks are designed to expose their runtime information, allowing developers to learn granular details regarding pallets, RPC calls, and runtime APIs. The metadata also exposes their related documentation. The chain's metadata is SCALE-encoded, allowing for the development of browser-based, mobile, or desktop applications to support the chain's runtime upgrades seamlessly. It is also possible to develop applications compatible with multiple Polkadot SDK-based chains simultaneously.

Expose Runtime Information as Metadata

To interact with a node or the state of the blockchain, you need to know how to connect to the chain and access the exposed runtime features. This interaction involves a Remote Procedure Call (RPC) through a node endpoint address, commonly through a secure web socket connection.

An application developer typically needs to know the contents of the runtime logic, including the following details:

  • Version of the runtime the application is connecting to
  • Supported APIs
  • Implemented pallets
  • Defined functions and corresponding type signatures
  • Defined custom types
  • Exposed parameters users can set

As the Polkadot SDK is modular and provides a composable framework for building blockchains, there are limitless opportunities to customize the schema of properties. Each runtime can be configured with its properties, including function calls and types, which can be changed over time with runtime upgrades.

The Polkadot SDK enables you to generate the runtime metadata schema to capture information unique to a runtime. The metadata for a runtime describes the pallets in use and types defined for a specific runtime version. The metadata includes information about each pallet's storage items, functions, events, errors, and constants. The metadata also provides type definitions for any custom types included in the runtime.

Metadata provides a complete inventory of a chain's runtime. It is key to enabling client applications to interact with the node, parse responses, and correctly format message payloads sent back to that chain.

Generate Metadata

To efficiently use the blockchain's networking resources and minimize the data transmitted over the network, the metadata schema is encoded using the Parity SCALE Codec. This encoding is done automatically through the scale-infocrate.

At a high level, generating the metadata involves the following steps:

  1. The pallets in the runtime logic expose callable functions, types, parameters, and documentation that need to be encoded in the metadata
  2. The scale-info crate collects type information for the pallets in the runtime, builds a registry of the pallets that exist in a particular runtime, and the relevant types for each pallet in the registry. The type information is detailed enough to enable encoding and decoding for every type
  3. The frame-metadata crate describes the structure of the runtime based on the registry provided by the scale-info crate
  4. Nodes provide the RPC method state_getMetadata to return a complete description of all the types in the current runtime as a hex-encoded vector of SCALE-encoded bytes

Retrieve Runtime Metadata

The type information provided by the metadata enables applications to communicate with nodes using different runtime versions and across chains that expose different calls, events, types, and storage items. The metadata also allows libraries to generate a substantial portion of the code needed to communicate with a given node, enabling libraries like subxt to generate frontend interfaces that are specific to a target chain.

Use Polkadot.js

Visit the Polkadot.js Portal and select the Developer dropdown in the top banner. Select RPC Calls to make the call to request metadata. Follow these steps to make the RPC call:

  1. Select state as the endpoint to call
  2. Select getMetadata(at) as the method to call
  3. Click Submit RPC call to submit the call and return the metadata in JSON format

Use Curl

You can fetch the metadata for the network by calling the node's RPC endpoint. This request returns the metadata in bytes rather than human-readable JSON:

curl -H "Content-Type: application/json" \
-d '{"id":1, "jsonrpc":"2.0", "method": "state_getMetadata"}' \
https://rpc.polkadot.io

Use Subxt

subxt may also be used to fetch the metadata of any data in a human-readable JSON format:

subxt metadata  --url wss://rpc.polkadot.io --format json > spec.json

Another option is to use the subxt explorer web UI.

Client Applications and Metadata

The metadata exposes the expected way to decode each type, meaning applications can send, retrieve, and process application information without manual encoding and decoding. Client applications must use the SCALE codec library to encode and decode RPC payloads to use the metadata. Client applications use the metadata to interact with the node, parse responses, and format message payloads sent to the node.

Metadata Format

Although the SCALE-encoded bytes can be decoded using the frame-metadata and parity-scale-codec libraries, there are other tools, such as subxt and the Polkadot-JS API, that can convert the raw data to human-readable JSON format.

The types and type definitions included in the metadata returned by the state_getMetadata RPC call depend on the runtime's metadata version.

In general, the metadata includes the following information:

  • A constant identifying the file as containing metadata
  • The version of the metadata format used in the runtime
  • Type definitions for all types used in the runtime and generated by the scale-info crate
  • Pallet information for the pallets included in the runtime in the order that they are defined in the construct_runtime macro

Metadata formats may vary

Depending on the frontend library used (such as the Polkadot API), they may format the metadata differently than the raw format shown.

The following example illustrates a condensed and annotated section of metadata decoded and converted to JSON:

[
    1635018093,
    {
        "V14": {
            "types": {
                "types": [{}]
            },
            "pallets": [{}],
            "extrinsic": {
                "ty": 126,
                "version": 4,
                "signed_extensions": [{}]
            },
            "ty": 141
        }
    }
]

The constant 1635018093 is a magic number that identifies the file as a metadata file. The rest of the metadata is divided into the types, pallets, and extrinsic sections:

  • The types section contains an index of the types and information about each type's type signature
  • The pallets section contains information about each pallet in the runtime
  • The extrinsic section describes the type identifier and transaction format version that the runtime uses

Different extrinsic versions can have varying formats, especially when considering signed transactions.

Pallets

The following is a condensed and annotated example of metadata for a single element in the pallets array (the sudo pallet):

{
    "name": "Sudo",
    "storage": {
        "prefix": "Sudo",
        "entries": [
            {
                "name": "Key",
                "modifier": "Optional",
                "ty": {
                    "Plain": 0
                },
                "default": [0],
                "docs": ["The `AccountId` of the sudo key."]
            }
        ]
    },
    "calls": {
        "ty": 117
    },
    "event": {
        "ty": 42
    },
    "constants": [],
    "error": {
        "ty": 124
    },
    "index": 8
}

Every element metadata contains the name of the pallet it represents and information about its storage, calls, events, and errors. You can look up details about the definition of the calls, events, and errors by viewing the type index identifier. The type index identifier is the u32 integer used to access the type information for that item. For example, the type index identifier for calls in the Sudo pallet is 117. If you view information for that type identifier in the types section of the metadata, it provides information about the available calls, including the documentation for each call.

For example, the following is a condensed excerpt of the calls for the Sudo pallet:

{
    "id": 117,
    "type": {
        "path": ["pallet_sudo", "pallet", "Call"],
        "params": [
            {
                "name": "T",
                "type": null
            }
        ],
        "def": {
            "variant": {
                "variants": [
                    {
                        "name": "sudo",
                        "fields": [
                            {
                                "name": "call",
                                "type": 114,
                                "typeName": "Box<<T as Config>::RuntimeCall>"
                            }
                        ],
                        "index": 0,
                        "docs": [
                            "Authenticates sudo key, dispatches a function call with `Root` origin"
                        ]
                    },
                    {
                        "name": "sudo_unchecked_weight",
                        "fields": [
                            {
                                "name": "call",
                                "type": 114,
                                "typeName": "Box<<T as Config>::RuntimeCall>"
                            },
                            {
                                "name": "weight",
                                "type": 8,
                                "typeName": "Weight"
                            }
                        ],
                        "index": 1,
                        "docs": [
                            "Authenticates sudo key, dispatches a function call with `Root` origin"
                        ]
                    },
                    {
                        "name": "set_key",
                        "fields": [
                            {
                                "name": "new",
                                "type": 103,
                                "typeName": "AccountIdLookupOf<T>"
                            }
                        ],
                        "index": 2,
                        "docs": [
                            "Authenticates current sudo key, sets the given AccountId (`new`) as the new sudo"
                        ]
                    },
                    {
                        "name": "sudo_as",
                        "fields": [
                            {
                                "name": "who",
                                "type": 103,
                                "typeName": "AccountIdLookupOf<T>"
                            },
                            {
                                "name": "call",
                                "type": 114,
                                "typeName": "Box<<T as Config>::RuntimeCall>"
                            }
                        ],
                        "index": 3,
                        "docs": [
                            "Authenticates sudo key, dispatches a function call with `Signed` origin from a given account"
                        ]
                    }
                ]
            }
        }
    }
}

For each field, you can access type information and metadata for the following:

  • Storage metadata - provides the information required to enable applications to get information for specific storage items
  • Call metadata - includes information about the runtime calls defined by the #[pallet] macro including call names, arguments and documentation
  • Event metadata - provides the metadata generated by the #[pallet::event] macro, including the name, arguments, and documentation for each pallet event
  • Constants metadata - provides metadata generated by the #[pallet::constant] macro, including the name, type, and hex-encoded value of the constant
  • Error metadata - provides metadata generated by the #[pallet::error] macro, including the name and documentation for each pallet error

Note

Type identifiers change from time to time, so you should avoid relying on specific type identifiers in your applications.

Extrinsic

The runtime generates extrinsic metadata and provides useful information about transaction format. When decoded, the metadata contains the transaction version and the list of signed extensions.

For example:

{
    "extrinsic": {
        "ty": 126,
        "version": 4,
        "signed_extensions": [
            {
                "identifier": "CheckNonZeroSender",
                "ty": 132,
                "additional_signed": 41
            },
            {
                "identifier": "CheckSpecVersion",
                "ty": 133,
                "additional_signed": 4
            },
            {
                "identifier": "CheckTxVersion",
                "ty": 134,
                "additional_signed": 4
            },
            {
                "identifier": "CheckGenesis",
                "ty": 135,
                "additional_signed": 11
            },
            {
                "identifier": "CheckMortality",
                "ty": 136,
                "additional_signed": 11
            },
            {
                "identifier": "CheckNonce",
                "ty": 138,
                "additional_signed": 41
            },
            {
                "identifier": "CheckWeight",
                "ty": 139,
                "additional_signed": 41
            },
            {
                "identifier": "ChargeTransactionPayment",
                "ty": 140,
                "additional_signed": 41
            }
        ]
    },
    "ty": 141
}

The type system is composite, meaning each type identifier contains a reference to a specific type or to another type identifier that provides information about the associated primitive types.

For example, you can encode the BitVec<Order, Store> type, but to decode it properly, you must know the types used for the Order and Store types. To find type information for Order and Store, you can use the path in the decoded JSON to locate their type identifiers.

Included RPC APIs

A standard node comes with the following APIs to interact with a node:

  • AuthorApiServer - make calls into a full node, including authoring extrinsics and verifying session keys
  • ChainApiServer - retrieve block header and finality information
  • OffchainApiServer - make RPC calls for off-chain workers
  • StateApiServer - query information about on-chain state such as runtime version, storage items, and proofs
  • SystemApiServer - retrieve information about network state, such as connected peers and node roles

Additional Resources

The following tools can help you locate and decode metadata:

Last update: November 25, 2024
| Created: November 8, 2024