Rootstock is an EVM chain merge-mined with Bitcoin. Gas is paid in RBTC, a Bitcoin-pegged token, not ETH. The node software is RSKj, not Geth, so the JSON-RPC surface is close to Ethereum but not identical. Wallet builders who copy an Ethereum config, reuse Ethereum checksums, or wait for a finalized tag will hit errors that Ethereum mainnet never shows.
This is not a ranking of providers. Use the Rootstock mainnet hub to compare public endpoints and paid rows, then run the checks below on the URL you configure. The Rootstock mainnet providers page lists the keyed options side by side.
Chain ID 30 is mainnet, 31 is testnet
- Rootstock mainnet: chain ID 30, hex
0x1e. This is whateth_chainIdreturns and what EIP-155 signatures use.net_versionon the official public node returned"30"during the test. - Rootstock testnet: chain ID 31, hex
0x1f, with tRBTC as the gas token. - Ethereum mainnet: chain ID 1. A wallet that already has Ethereum added still needs a separate Rootstock network entry.
Wallet-add fields for mainnet, matching the Rootstock MetaMask guide:
- Network name:
Rootstock Mainnet - Chain ID:
30(or0x1eif the form wants hex) - Currency symbol:
RBTC - RPC URL: a JSON-RPC HTTPS endpoint you have already probed, such as the official
https://public-node.rsk.co - Block explorer:
https://explorer.rootstock.io
RBTC balances use 18 decimals in JSON-RPC, like ETH. The bridge contract's eth_getBalance returned about 20.99 million RBTC during the test, against the 21,000,000 total supply in the rBTC docs. Call eth_chainId on the exact URL you will ship and expect 0x1e. A gateway that returns 0x1f is testnet. A timeout, an HTML page, a paid-plan error, or a rate-limit body is not a working Rootstock mainnet transport.
Public endpoints that answered eth_chainId
We probed every public Rootstock mainnet URL on the hub 8 times, about 55 seconds apart, on 2026-10-08 between 02:11 and 02:18 UTC. Each round called eth_chainId, eth_getBlockByNumber("latest"), eth_getBalance on the PowPeg bridge contract, and an eth_call for the RIF token's totalSupply(), with a curl User-Agent on every request. The hub lists 7 HTTPS and 1 WSS transports. These passed eth_chainId, the latest block, and a balance read in 8 of 8 rounds:
| Endpoint | Transport | Notes |
|---|---|---|
https://public-node.rsk.co | HTTPS | Official RootstockLabs public node. Head 30 to 79 seconds old, never behind the median |
https://mycrypto.rsk.co | HTTPS | Same head as the official node in every round |
https://rootstock.drpc.org / wss://rootstock.drpc.org | HTTPS, WSS | The only WSS row that passed. Returns HTTP 403 (Cloudflare error 1010) to Python urllib's default User-Agent, so set a User-Agent on server-side clients. Node's built-in fetch, Go net/http, requests with no User-Agent, curl, axios, requests, httpx, and a browser User-Agent string worked |
https://30.rpc.thirdweb.com | HTTPS | Same Python urllib HTTP 403 as DRPC. The other clients above worked |
https://rsk-mainnet.gateway.tatum.io | HTTPS | eth_call returned JSON-RPC -16401 "available for paid plans only" in 8 of 8 rounds, and a separate burst hit a 5 requests per minute limit. Fine for a chain ID check, not for token balances |
Two rows failed all 8 rounds, and the directory now hides them. Blast API's Rootstock URL returned -32000 "Blast API is no longer available" on every call. lb.routeme.sh/rpc/evm/30 answered eth_chainId in 3 rounds but returned the -32029 public rate-limit error on the block read in all 8. A row on a directory page is a starting point, not proof it serves Rootstock today.
What the RSKj JSON-RPC does differently
Rootstock nodes run RSKj. The supported methods table covers the usual eth_ reads and writes, plus rsk_, debug_, trace_, and txpool_ modules. web3_clientVersion on the official node returned RskJ/9.0.3/Linux/Java17/VETIVER-c19cf12 during the test. DRPC answered the same method with Geth/v10.0.0/drpc, so do not use that string to detect the backend.
Calls that behaved differently from Ethereum on both public-node.rsk.co and rootstock.drpc.org:
eth_getBlockByNumber("finalized")and("safe")returned JSON-RPC-32602"invalid blocknumber". Libraries or indexers that default to those tags will error.eth_feeHistoryandeth_maxPriorityFeePerGasreturned-32601"method not found".eth_getBlockByNumber("pending")returned a block object with anullnumber. The docs markpendingas not supported on several methods, includingeth_getCodeandeth_getBlockByNumber.
The official public nodes are HTTP only. Rootstock's public nodes page says they do not expose WebSockets and points to your own node, the keyed Rootstock RPC API, or a third-party provider instead. In the probe, wss://rootstock.drpc.org was the only public WSS transport on the hub.
Finality: count confirmations, not tags
Rootstock blocks are merge-mined with Bitcoin under DECOR+, a variant of Nakamoto consensus, per the Rootstock FAQ. Finality is probabilistic. There is no finality gadget behind a finalized tag, which matches the -32602 error above. The FAQ recommends a minimum of 12 confirmations for payments, about 6 minutes, and more for larger amounts.
Measured on the official node during the test, the last 200 blocks spanned 6,292 seconds, about 31.6 seconds per block. 124 of those 200 blocks referenced at least one uncle, 217 uncles in total. Competing blocks at the tip are routine, so treat a latest receipt as pending until it has the confirmation depth your product needs.
PowPeg timing is separate from send finality. Rootstock's conversion guide lists 100 Bitcoin confirmations for a mainnet peg-in and 4,000 Rootstock blocks for a peg-out release. Do not reuse those numbers as the confirmation count for ordinary RBTC or token transfers.
Fees: minimumGasPrice plus a buffer
Rootstock does not use an EIP-1559 base fee. Block headers carry a minimumGasPrice field that miners can move by up to 1% per block, as described in the gas docs. Blocks returned by the official node had no baseFeePerGas field.
During the test on the official node:
- The latest block's
minimumGasPricewas0x1699280(23,696,000 wei, about 0.0237 gwei). eth_gasPricereturned0x18dbac0(26,065,600 wei), about 10% above that minimum.eth_estimateGasfor a plain RBTC transfer returned0x5208(21,000).- The latest block's
gasLimitwas 10,000,000. The FAQ still cites 6.8M, so read the live header if a limit matters.
What that means for a wallet:
- Price from
eth_gasPriceorminimumGasPriceplus about 10%. The gas docs call rawminimumGasPricethe optimistic choice and suggest adding 10%. A transaction priced below the next block's minimum can be rejected. - Do not expect EIP-1559 fields. With no
baseFeePerGasand noeth_feeHistory, fall back to a singlegasPriceinstead ofmaxFeePerGasandmaxPriorityFeePerGasmath. - Peg-out sends are special. The conversion guide asks for a manual 100,000 gas limit on RBTC sent to the bridge contract
0x0000000000000000000000000000000001000006.
Addresses, checksums, and derivation paths
Rootstock implements EIP-1191, a checksum that mixes the chain ID into the uppercase and lowercase pattern. A mixed-case address checksummed for Ethereum can fail validation in a Rootstock-aware wallet, and the reverse. The FAQ notes that the node does not enforce it, so the check lives in your client.
The docs list the mainnet derivation path as m/44'/137'/0'/0/N (coin type 137) and testnet as m/44'/37310'/0'/0/N. Wallets that use Ethereum's m/44'/60' path, such as MetaMask, derive different addresses from the same seed phrase. Whichever path you ship, document it and keep it fixed so users can restore funds in another wallet.
MetaMask, Rabby, and Rainbow
The Rootstock wallet compatibility matrix shows which wallets support the Rootstock checksum and derivation path.
- MetaMask: does not support EIP-1191 checksums and uses the Ethereum derivation path. The docs suggest lowercasing addresses copied from MetaMask and warn that some screens may show
ETHinstead of RBTC. When a dapp callswallet_switchEthereumChainfor an unknown chain, MetaMask returns error4902, so follow up withwallet_addEthereumChain. - Rabby: the matrix shows no Rootstock checksum and no Rootstock derivation path. Check whether Rootstock is already in Rabby's network list before adding a custom entry.
- Rainbow: the matrix shows both the Rootstock checksum and the Rootstock derivation path.
For wallet_addEthereumChain, send a full EIP-3085 payload: chainId: "0x1e", chainName: "Rootstock Mainnet", nativeCurrency: { name: "RBTC", symbol: "RBTC", decimals: 18 }, rpcUrls with your probed HTTPS endpoint, and blockExplorerUrls: ["https://explorer.rootstock.io"]. For wallet_switchEthereumChain, send only { chainId: "0x1e" }.
Wallet-add gotchas that show up in QA
- Chain ID 31 in the mainnet row: the label says Rootstock but signatures target testnet.
- Ethereum checksums on display: EIP-1191 validators reject them, or users paste a lowercase address into a strict field.
- Mixed derivation paths: one build uses coin type 137 and another uses 60, so restored wallets show empty balances.
- Waiting on
finalized: the call errors instead of returning a block. Count confirmations instead. - EIP-1559 fee code paths:
eth_feeHistoryis missing, so fee estimation must fall back togasPrice. - Token balances through Tatum's public URL:
eth_callis paid-only there, so ERC-20 balances come back as errors. - WebSocket on the official node: it is HTTP only. Use a provider WSS or poll.
Public vs dedicated for wallet traffic
RootstockLabs describes its public nodes as being for testing and recommends running your own node or using another provider for production. Its keyed Rootstock RPC API has a daily limit of 25,000 requests per user, with up to 4 API keys.
Public endpoints are fine for:
- Local development and wallet QA
- Low-volume
eth_chainIdandeth_blockNumberprobes - One-off scripts and docs examples
They are a poor primary path when you:
- Run production wallet RPC for many users (balance, nonce, fee estimate, send, receipt polls)
- Need WebSocket subscriptions, since the official nodes are HTTP only
- Depend on
eth_callfor token balances on a gateway that gates it behind a paid plan - Cannot absorb shared-node rate limits during a launch or market spike
Probe the candidate URL before you call it ready:
eth_chainIdreturns0x1e.- The latest block advances over a minute or two, and its timestamp is within about a minute of wall-clock time.
- A plain transfer
eth_estimateGasreturns0x5208(21,000). - An
eth_callon a token contract you care about returns data, not a paid-plan error. - A burst of 20 to 50 quick requests does not return HTTP 429 or a JSON-RPC rate-limit error at the volume you expect.
- If you call from Python's
urllibor another client without a library User-Agent, confirm the URL still answers, or set one.
For production wallet traffic, compare the paid rows on the same hub for method coverage, WebSocket support, and quota. At the time of writing those include Alchemy and DRPC. A cheaper URL that fails the probe above is not a savings.
The public vs paid guide covers when a shared endpoint stops being enough, and the pricing calculator estimates monthly cost for a request mix. Log-heavy indexers should read the eth_getLogs caps guide, and the dead public endpoints guide explains why listed URLs stop answering.
Practical split for a wallet stack
- User-facing Rootstock network row: chain ID 30 /
0x1e, RBTC symbol, explorer.rootstock.io, an RPC you probed today. - Address handling: EIP-1191 checksums with chain ID 30, one documented derivation path, lowercase fallback for MetaMask copies.
- Confirmations: block-number counting, at least 12 for payments per the FAQ, more for large amounts. No
safeorfinalizedtags. - Fee estimation:
eth_gasPriceorminimumGasPriceplus about 10%, refreshed per send. - Backend / indexer: dedicated or paid endpoint with WebSocket and full
eth_callsupport. Do not share the browser wallet's public URL.
Ship the hub comparison and the probe first. A network called "Rootstock" in a dropdown does not replace eth_chainId returning 0x1e.