New JSON-RPC API: Q3 2025 Update (introducing legacy-provider and moar)

:waving_hand: Polkadot frens!

It’s been a bit over two years since @tomaka created the New JSON-RPC API mega Q&A forum post. A lot has happened since, and almost a year ago I provided an update, which among other things included a call to action.

I have to be honest: things have not progressed as well as we would have liked, but I’m not here to whine, I’m here to propose solutions.

First things first: if you don’t know why a new JSON-RPC spec was needed, please read this, this, and this.

TL;DR

  • The legacy JSON‑RPC grew organically and chaotically.
  • Without a complete, normative spec (clear types, parameters, errors, and limits), tooling and cross‑chain interoperability suffered.
  • Bad defaults: many legacy RPC methods accept the block-hash as an optional argument, if not provided the block that the node considers to be “best” is used by default. (Also, please know that PJS leaks this implementation detail, which is why with PJS the state is queried against the best-block by default)
  • Some methods are unbounded or resource‑heavy, increasing denial‑of‑service (DoS) risk for nodes.
  • Many endpoints were not load-balancer friendly. The new JSON-RPC spec nicely addresses this.
  • The new spec aims to be versioned, predictable, and resource‑aware so wallets, indexers, and apps “just work” across nodes, safely and consistently.
  • The new spec has groups of functions so that different kinds of nodes (like light-clients) can expose an API that’s optimal to their capabilities.

Why we’re doubling down on the new JSON‑RPC APIs

By now, the goal should be clear: stop relying on the legacy JSON‑RPC APIs and migrate to the new JSON‑RPC APIs wherever possible.

That’s why the PAPI client uses exclusively the new JSON‑RPC APIs. We wanted to help the ecosystem mature, so we didn’t “cheat” by falling back to legacy methods. This choice let us feed a lot of concrete feedback upstream, and both smoldot and the PolkadotSDK node implementations have benefited from that, each is noticeably more mature now.

Fixing bugs: smoldot vs. PolkadotSDK node

There’s a big difference in how fixes land depending on where the bug lives:

  • If the bug is in smoldot: we patch the smoldot library and release. You upgrade your DApp’s dependency, and you’re done.
  • If the bug is in the PolkadotSDK node: JSON‑RPC providers won’t have the fix until the next node release ships and they upgrade. That delay can block users from getting the fix in production.

So how does PAPI mitigate this?

The workaround: a minimal provider interface + middlewares

The public interface of the JSON‑RPC Provider that PAPI uses is intentionally minimal and fully decoupled from PAPI internals. That unlocked a practical approach: middleware.

Whenever we discover an issue in the node’s JSON‑RPC implementation, we ship a middleware to work around it. That’s precisely what the withPolkadotSdkCompat enhancer does: it applies a set of middlewares for each problem we identify so the resulting interface stays 100% compliant with the new JSON‑RPC spec, even when individual nodes lag behind.

The missing piece: a purpose‑built gateway

Despite all of the above, we still lack a proper gateway for the new JSON‑RPC APIs. Something like Subway, but for the new interfaces. Without it, you may hit issues establishing a chainHead_v1_follow subscription (live chain head tracking), unpinning blocks, and similar flows that stress connection management.

This is ironic, because the new JSON‑RPC APIs were designed with load‑balancers and gateways in mind. A dedicated gateway should enable much better throughput and lower cost characteristics. We assumed someone in the ecosystem would seize this opportunity, but that hasn’t happened, so we’re stepping in. We’ve started collaborating with another team to deliver an initial version soon. Stay tuned; we hope to share more in the coming weeks.

Why the gateway matters

Beyond making access to the new JSON‑RPC APIs more reliable, the gateway lets us run our middlewares behind it. That means node providers can upgrade the gateway (and therefore benefit from new workarounds and optimizations) without waiting for a node release. In short: faster iteration, safer rollouts, and more stable, reliable endpoints.

“But what about chains that haven’t upgraded?” (e.g., Interlay)

You might be thinking: “This all sounds great, but what about relevant chains that haven’t upgraded their nodes in years (e.g., Interlay)?”

Remember: our JSON‑RPC provider interface is intentionally minimalistic and decoupled from PAPI internals. That design lets us slip in a middleware that exposes the new JSON‑RPC APIs while translating to the legacy ones behind the scenes :exploding_head:. In practice, that unlocks a few useful scenarios:

  • Pair it with the upcoming gateway. RPC providers can expose the new APIs even for chains whose nodes still only serve legacy RPCs.
  • Connect PAPI directly to a legacy-only node. PAPI continues to speak only the new JSON‑RPC at the client boundary; the middleware handles the translation to legacy under the hood.
  • Work around faulty gateways. While we wait for the modern gateway: iff a node does expose the modern APIs but sits behind a misconfigured gateway, this middleware can serve as a temporary compatibility layer.

Announcing @polkadot-api/legacy-provider

During my vacation, I spent some free time building exactly this “legacy‑provider” middleware , and it’s now released :rocket:!

Why this matters: it lets the ecosystem move forward without waiting on every chain or provider to upgrade nodes or gateways. You get the predictability of the new spec at the client boundary, while we bridge anything legacy that still sits on the path.

Because the provider interface is intentionally minimal, the legacy‑provider can be used by any library or tool, not just PAPI.

Example: using it with PAPI

import { createClient } from "polkadot-api"
import { getWsProvider } from "polkadot-api/ws-provider/web"
import { withLegacy } from "@polkadot-api/legacy-provider"

const client = createClient(
  getWsProvider({
    endpoints: [
      "wss://api.interlay.io/parachain",
      "wss://rpc-interlay.luckyfriday.io/",
    ],
    innerEnhancer: withLegacy(),
  }),
)

Config: withLegacy accepts the chain’s hasher and defaults to Blake2_256.

Important notes

  • Use it as an innerEnhancer. The polkadot-api/ws-provider expects a WebSocket speaking the new JSON‑RPC at the transport boundary. That’s why this enhancer -unlike polkadot-sdk-compat- must be applied at the lowest level via innerEnhancer.
  • Don’t stack with polkadot-sdk-compat. If you use @polkadot-api/legacy-provider, adding the Polkadot SDK compatibility layer is unnecessary. The legacy‑provider already outputs a new‑spec‑compliant surface, so there’s nothing left for that middleware to “fix.”

Closing & call to action

If there’s one takeaway from this post, it’s this: let’s make the new JSON‑RPC the default experience for Polkadot: predictable, resource‑aware, and easy to build on. We’ve shipped the pieces to move now (PAPI on the new spec, middlewares like withPolkadotSdkCompat, and the newly released @polkadot-api/legacy-provider), and we’re actively working on the missing gateway to make it scale cleanly.

Thanks for reading, and for pushing the ecosystem forward with constructive feedback, PRs, and real‑world tests. Let’s ship this together :flexed_biceps:.

10 Likes