If you come from Ethereum, Polkadot's RPC will look familiar for about ten seconds. It's JSON-RPC over HTTP, you POST a {"jsonrpc":"2.0","method":...} body, you get a result back. Then you try eth_blockNumber and it isn't there — because Polkadot speaks Substrate JSON-RPC (system_*, chain_*, state_*), not eth_*. And the differences run deeper than method names: the relay chain runs no smart contracts at all, DOT has 10 decimals, not 18, and reading storage is a metadata-driven, SCALE-encoded affair with no ABIs in sight. This is the spotlight for treating Polkadot as its own platform. Here's the map.
The essentials
Polkadot's relay chain (system_chain returns "Polkadot") is a Substrate-based Layer 0 with:
-
Substrate JSON-RPC, not
eth_*—system_chain,system_version,chain_getHeader/chain_getBlock,state_getStorage,state_getRuntimeVersion, etc. - DOT as the token — 10 decimals (smallest unit is the Planck: 1 DOT = 10,000,000,000 Planck). Not 18.
- BABE block authoring + GRANDPA finality — ~6-second relay-chain blocks; GRANDPA finalizes in a few blocks once two-thirds of validators attest.
- No smart contracts on the relay chain — application logic lives on parachains, not here (more below).
-
Substrate tooling — the Polkadot.js API /
@polkadot/api, not viem/ethers/foundry.
A liveness check uses Substrate methods:
curl -s -X POST "https://rpc.swiftnodes.io/rpc/polkadot?key=YOUR_API_KEY" \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"system_chain","params":[]}'
# -> "Polkadot"
curl -s -X POST "https://rpc.swiftnodes.io/rpc/polkadot?key=YOUR_API_KEY" \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"chain_getHeader","params":[]}'
# -> { parentHash, number, stateRoot, extrinsicsRoot, digest }
Point viem/ethers at this URL and nothing works — they speak eth_*. Use @polkadot/api instead.
Big difference #1: the relay chain has no smart contracts
This is the mental reset. On Ethereum, the L1 is where contracts live. Polkadot's relay chain is a Layer 0: its job is shared security, consensus, staking, governance, and coordinating other chains — not running application code. There is no eth_sendRawTransaction-to-a-contract here because there are no contracts here.
Application logic lives on parachains and rollups built with Substrate — chains like Astar (which offers both EVM and Wasm), Moonbeam, Hydration, and Asset Hub. So "build on Polkadot" usually means "build on a parachain," and which RPC you talk to depends on what you're doing:
- Relay-chain RPC (this endpoint) — query consensus, validators, staking, governance, DOT balances, and cross-chain (XCM) coordination.
-
A parachain's own RPC — where your dApp actually runs; if that parachain is EVM (e.g. Moonbeam, or Astar's EVM side), that endpoint speaks
eth_*.
Getting this right saves confusion: you don't deploy a Solidity contract "to Polkadot" — you deploy it to an EVM parachain, and use the relay chain for the Layer-0 concerns.
Big difference #2: storage is SCALE-encoded and metadata-driven
Ethereum reads are ABI-driven: you have a contract ABI, you encode a call, you decode the result. Substrate is different. On-chain state is organized into pallets (the runtime's modules — Balances, Staking, System, etc.), and you read it from storage rather than by calling view functions. The values are SCALE-encoded (Substrate's compact binary codec), and the layout is described by the chain's runtime metadata, which you fetch and which tells the library how to build storage keys and decode results.
In practice you don't hand-roll any of this — @polkadot/api reads the metadata at connect time and gives you typed access:
import { ApiPromise, WsProvider } from "@polkadot/api";
const api = await ApiPromise.create({
provider: new WsProvider("wss://rpc.swiftnodes.io/ws/polkadot?key=YOUR_API_KEY"),
});
const { data: balance } = await api.query.system.account(SOME_ADDRESS);
console.log(balance.free.toString()); // in Planck (10 decimals)
Note the shape: api.query.<pallet>.<item>(...), not contract.balanceOf(...). There's no ABI because there are no contracts — there's runtime metadata and pallet storage.
Big difference #3: DOT is 10 decimals, and addresses are SS58
Two encoding traps for Ethereum developers:
-
Decimals. DOT uses 10 decimals, not 18. If you reuse an
18-decimal assumption (the same balance-formatting trap that bites people with USDC's 6), you'll misreport every balance by a factor of 100 million. The base unit is the Planck. -
Addresses. Polkadot uses SS58 addresses (a base58 format with a network prefix), not 20-byte
0xhex. A Polkadot address isn't an Ethereum address, and the same public key renders differently across Substrate networks (Polkadot vs. Kusama use different prefixes). Let the library format and validate them.
Finality: GRANDPA, so track finalized heads
Polkadot separates block production (BABE) from finality (GRANDPA). Blocks are authored roughly every ~6 seconds, and GRANDPA finalizes them — usually within a few blocks — once a supermajority of validators attest. The practical guidance:
-
Best vs. finalized.
chain_getHeadergives you the latest best head; there are separate subscriptions for the finalized head. For anything that must not be reverted (exchange credits, settlement), track the finalized head, not just the best block. - Once GRANDPA-finalized, a block is final — no reorgs past finality. The general "confirm at the right depth" thinking is in handling chain reorgs; on Substrate the clean signal is the finalized subscription.
What does NOT carry over
Be explicit before you start:
-
eth_*methods — not available; usesystem_*/chain_*/state_*. -
viem / ethers / web3.py / foundry / hardhat — they speak EVM JSON-RPC; use
@polkadot/api(Polkadot.js). -
Solidity ABIs, 20-byte hex addresses, 18 decimals, contract calls — replaced by pallet storage, SCALE + metadata, SS58 addresses, 10 decimals, and
api.query/api.tx. - "Deploy a contract to the L1" — the relay chain has none; deploy to a parachain.
What does carry over is the transport and the rhythm: JSON-RPC over HTTP/WebSocket, you read heads and storage and submit signed transactions (extrinsics, via api.tx), and finality is a first-class signal you subscribe to.
The short version
Polkadot (system_chain = "Polkadot") is a Substrate Layer 0: JSON-RPC over HTTP like Ethereum, but the Substrate namespace (system_*/chain_*/state_*), not eth_* — so viem/ethers don't connect and you use @polkadot/api. The relay chain runs no smart contracts (app logic lives on parachains — some EVM, like Moonbeam/Astar, which do speak eth_*), state is SCALE-encoded pallet storage read via runtime metadata (not ABIs), DOT has 10 decimals (base unit Planck), addresses are SS58, and finality comes from GRANDPA (track the finalized head). Treat Polkadot as its own platform that shares Ethereum's RPC transport and nothing else — and remember the relay chain is for Layer-0 concerns, not for deploying your dApp.
Querying Polkadot, Kusama, or Asset Hub over Substrate RPC? A flat-rate Polkadot RPC endpoint gives you the Substrate JSON-RPC over HTTP and WebSocket — Polkadot, Kusama, and Polkadot Asset Hub all under one key, alongside 75+ other chains. Grab a free key and point @polkadot/api at:
https://rpc.swiftnodes.io/rpc/polkadot?key=YOUR_API_KEY
wss://rpc.swiftnodes.io/ws/polkadot?key=YOUR_API_KEY
Originally published on the SwiftNodes blog. SwiftNodes provides flat-rate multi-chain RPC endpoints — HTTP + WebSocket, 75+ chains, no per-request metering. Grab a free key.
Top comments (0)