Introducing `@polkadot-api/check-runtime`: quick checks for runtime upgrades (library + CLI)

Runtime upgrades are powerful, and easy to get subtly wrong. The PAPI team is used to deal with issues that arise due to faulty compiled runtimes, which is why we decided to create a tool to help everyone check for these common mistakes.

We are happy to introduce @polkadot-api/check-runtime, a tiny library and CLI that detects a handful of common, high-impact mistakes before (or after) you roll out a runtime upgrade.

  • Library: call getProblems(uri, { wasm, block, token }) and get back a list of issues.
  • CLI: run check-runtime problems <uri> locally or in your CI; it prints problems and exits with 1 if anything is wrong.

If you want just the TL;DR:

# check a live chain at head
npx @polkadot-api/check-runtime problems wss://your.chain.rpc

# check a specific height
npx @polkadot-api/check-runtime problems wss://your.chain.rpc --at 123456

# check using a local or remote WASM (pre-deploy sanity check)
npx @polkadot-api/check-runtime problems wss://your.chain.rpc --wasm ./runtime.wasm
npx @polkadot-api/check-runtime problems wss://your.chain.rpc --wasm https://example.com/runtime.wasm

Why this exists

A few incidents keep recurring across Polkadot SDK chains:

  • Missing Runtime APIs → clients can’t query/derive critical info.
  • Mismatched/incorrect CheckMetadataHash signed extension → offline or strict signers produce transactions that the runtime rejects.
  • Inconsistent metadata hashes between v15 and v16 → subtle client misbehavior.

These are the kind of issues you want your tooling to scream about immediately -ideally in CI- so you never ship a runtime that looks fine locally but fails in the wild.


What it checks (Problem reference)

The tool returns a list of problem identifiers. Here’s what each one means:

Problem What it means Why it matters
ANCIENT_METADATA The runtime doesn’t expose modern (≥ v14) metadata. Many SDKs/wallets rely on modern metadata; you’ll hit compatibility issues.
MISSING_MODERN_METADATA The runtime only exposes metadata v14 (no v15/v16). Certain functionalities on modern libraries are only available with Metadata v15+.
MISSING_RUNTIME_APIS Required Runtime APIs are missing. A common issue that’s very painful for DApp developers and fairly easy to solve.
MISSING_CHECK_METADATA_HASH_EXTENSION The extrinsic format lacks the CheckMetadataHash signed extension. Especially problematic for signing with hardware wallets.
WRONG_OR_MISSING_METADATA_HASH The runtime wasn’t compiled with the correct metadata hash. Transactions that correctly use the CheckMetadataHash will be invalid. E.g: transactions created with Ledger.
DIFFERENT_METADATA_HASHES The metadata hash differs between v15 and v16. Indicates an internal inconsistency. There is no easy fix for this problem yet, it’s being worked on, but it’s a problem nonetheless.

The CLI prints human-readable messages for each and exits with code 1 if anything’s off.


Installation

Library + CLI (recommended in repos/CI):

npm i -D @polkadot-api/check-runtime
# or
pnpm add -D @polkadot-api/check-runtime
# or
yarn add -D @polkadot-api/check-runtime

One-off via npx:

npx @polkadot-api/check-runtime problems wss://your.chain.rpc

Requires Node.js 22+.


Using the CLI

check-runtime problems <uri> [options]
  • <uri>: WebSocket RPC, e.g. wss://rpc.polkadot.io.

Options

  • --wasm <filenameOrUrl>: local path or URL to a runtime WASM to check against.
  • --at <block>: height or block hash to evaluate at (applies/inspects the WASM in that context if provided).
  • --symbol <symbol>: override token symbol (defaults to RPC chain spec).
  • --decimals <decimals>: override token decimals (defaults to RPC chain spec).

Examples

# check live chain at head
npx @polkadot-api/check-runtime problems wss://rpc.polkadot.io

# check a specific height
npx @polkadot-api/check-runtime problems wss://rpc.polkadot.io --at 19000000

# check using a locally built WASM
npx @polkadot-api/check-runtime problems ws://localhost:9944 \
  --wasm ./artifacts/runtime.compact.compressed.wasm \
  --at 123456

# check against a WASM from a URL
npx @polkadot-api/check-runtime problems wss://rpc.my-chain.io \
  --wasm https://example.com/my-runtime.wasm

# override token info (e.g. dev chains)
npx @polkadot-api/check-runtime problems ws://localhost:9944 \
  --symbol TST --decimals 12

Exit codes

  • 0 – no problems found (“Everything looks great!”)
  • 1 – one or more problems were detected (messages printed to stderr)

This makes it straightforward to block a CI/CD stage if a runtime is unsafe to ship.


Programmatic API

The library exposes a single function:

import { HexString } from 'polkadot-api';

type Problem =
  | 'ANCIENT_METADATA'
  | 'MISSING_MODERN_METADATA'
  | 'MISSING_RUNTIME_APIS'
  | 'MISSING_CHECK_METADATA_HASH_EXTENSION'
  | 'DIFFERENT_METADATA_HASHES'
  | 'WRONG_OR_MISSING_METADATA_HASH';

declare function getProblems(
  uri: string,
  options?: Partial<{
    wasm: HexString;
    block: HexString | number;
    token: Partial<{ symbol: string; decimals: number }>;
  }>
): Promise<Array<Problem>>;

CI integration

GitHub Actions

name: Runtime checks
on: [push, pull_request]
jobs:
  check-runtime:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - run: npx @polkadot-api/check-runtime problems ${{ secrets.RPC_URI }} --at 123456 --wasm ./dist/runtime.wasm

GitLab CI

runtime_check:
  image: node:22
  script:
    - npx @polkadot-api/check-runtime problems $RPC_URI --at 123456 --wasm ./dist/runtime.wasm

Notes & limitations

  • The tool does not submit extrinsics and does not mutate on‑chain state; it uses Chopsticks behind the scenes.
  • --at / block accepts either a height (number) or a block hash (hex string).
  • When --wasm/wasm is provided together with --at/block, the height is used as the context at which to check and/or apply that specific code.
  • If symbol/decimals are not provided, they are read from the chain-spec via RPC. Keep in mind that’s not “on-chain” data, ie: it’s safer to provide these values.

Call for feedback

If this saves you from a messy rollback, or if you need another check added: please comment here or file an issue.

Happy shipping!

3 Likes

Looks useful!

There are also a few more easy but critical things to verify:

2 Likes

Absolutely! How do you envision this, though? Should we just use a hardcoded value and if so… what should that value be?

Yes! But, to be fair, as of right now: if creating a new-block failed, then that would be conflated with the ANCIENT_METADATA error. So, we can improve this. For sure!

Nice one! Yep, I will add it in the next release, thanks!

:thinking: Is there a runtime-api that I’m not aware off that can facilitate this? Because the TransactionPaymentApi_* and TransactionPaymentCallApi_* APIs are not really suitable for this, correct? I guess that I should have a look at how the ChargeTransactionPayment signed-extension subtracts the initial amount, and then see if I can do the same from the “outside” for each tx? I will check. Although, please let me know how you would tackle this one :folded_hands:

Thanks a lot for the feedback! Much, much appreciated!

1 Like