Blockchain RPC is the remote procedure call interface that lets an application ask a blockchain node to read state, simulate execution, or broadcast a signed transaction. The app sends a JSON-RPC request to an endpoint URL; the node executes it against its local copy of chain state and returns a structured result or error. This article explains the request lifecycle, shows runnable calls you can adapt to any EVM-compatible network, provides a verification and troubleshooting procedure, and separates documented protocol behavior from the latency and reliability numbers you must measure yourself. It closes with the tradeoffs between self-hosted, public, and managed endpoints so you can choose a setup that matches your workload.
What blockchain RPC actually is
Remote procedure call (RPC) is a general computing pattern: one program invokes a procedure on another machine and receives the result as if the call were local. In blockchain, the client is your application and the server is a node running network client software. The app does not need to know how the node stores state or which peers it syncs from; it only needs a request format and an address to send it to.
Most EVM networks speak JSON-RPC 2.0 over HTTP or WebSocket. A request carries a method name, a params array, an id, and a jsonrpc version string. The node replies with the same id and either a result or an error object. Non-EVM networks expose their own RPC surfaces, but the mental model is identical: a transport, a method namespace, and a node that answers from chain state.
Three terms are often used interchangeably and are worth separating. RPC is the protocol. The node is the machine that answers. The endpoint is the URL your app dials. With a managed provider, one endpoint URL can front a fleet of nodes, so a single address does not map to a single machine.
- RPC: the request/response protocol and method contract.
- Node: client software holding chain state and peer connections.
- Endpoint: the network address (HTTP or WSS) your application calls.
Scope, assumptions, and what this guide covers
This guide assumes you have a basic HTTP client, a terminal, and access to an RPC endpoint for an EVM-compatible network. Examples use placeholders such as RPC_URL and PRIVATE_KEY; substitute your own values and never commit secrets to source control. The procedures apply to any JSON-RPC network, though method names differ outside the EVM.
The goal is operational understanding: what a call does, how to prove it works, and how to interpret failures. Provider selection is a separate decision. If you want a concrete recommendation for a specific chain, the RPC Assistant answers that directly; this page gives you the framework to evaluate whatever endpoint you land on.
The request lifecycle from app to chain state
A read call such as eth_getBalance travels from your app over HTTPS to the endpoint, which routes it to a node. The node looks up the account in its state trie at the requested block height and returns a hex-encoded balance. No transaction is created and no gas is spent. Read calls are idempotent and safe to retry.
A write path is different. Your app constructs a transaction, signs it locally with a private key, and sends the raw signed bytes via eth_sendRawTransaction. The node validates the signature and nonce, adds the transaction to its mempool, and returns a transaction hash. That hash is a receipt for submission, not proof of inclusion. Confirmation only arrives when a later block includes the transaction, which you observe by polling eth_getTransactionReceipt.
This split matters for debugging. A failed read usually means a bad parameter, an unsupported method, or a node that is behind the chain head. A failed write usually means a nonce conflict, insufficient funds, a gas estimation problem, or a node that rejected the transaction before it reached the mempool.
Making your first verified RPC call with curl
Start with a read-only call so you can confirm connectivity and response shape without risking funds. The following request asks for the latest block number. Replace RPC_URL with your endpoint.
A healthy response returns a JSON object with jsonrpc, id, and a hex result. Convert the hex to decimal to compare against a block explorer for the same network. If the number is far behind the explorer, the node is lagging; if the request times out, the endpoint is unreachable or rate-limited.
curl -s -X POST "$RPC_URL" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}'Implementing reads and signed writes in JavaScript
Production clients usually use a library rather than hand-built JSON. The example below uses ethers to read a balance and send a value transfer. It separates the read, the gas estimate, and the broadcast so each step can fail independently and be logged.
Notice that the private key never leaves the process; the provider only ever sees the signed raw transaction. Also note the explicit nonce and gas settings. Letting the library infer them is fine for scripts, but explicit values make retries deterministic when a previous attempt may have partially succeeded.
import { JsonRpcProvider, Wallet, parseEther } from "ethers";
const provider = new JsonRpcProvider(process.env.RPC_URL);
const wallet = new Wallet(process.env.PRIVATE_KEY, provider);
async function demo() {
const balance = await provider.getBalance(wallet.address);
console.log("balance:", balance.toString());
const nonce = await provider.getTransactionCount(wallet.address, "pending");
const fee = await provider.getFeeData();
const tx = await wallet.sendTransaction({
to: "0x000000000000000000000000000000000000dEaD",
value: parseEther("0.0001"),
nonce,
maxFeePerGas: fee.maxFeePerGas,
maxPriorityFeePerGas: fee.maxPriorityFeePerGas,
gasLimit: 21000n,
});
console.log("submitted:", tx.hash);
const receipt = await tx.wait(1);
console.log("included in block:", receipt.blockNumber);
}
demo().catch(console.error);A repeatable verification and latency measurement procedure
Do not trust a provider's marketing latency figure; measure the path your users will actually take. Run the procedure below from the same region and network conditions as your production workload, and repeat it at several times of day. Record every run in a results table so you can compare endpoints on equal footing.
The procedure: (1) send eth_blockNumber 50 times sequentially and record round-trip time for each call; (2) send the same call 20 times concurrently and record p50 and p95; (3) call eth_getBalance for a known address at a fixed historical block and confirm the value matches an explorer; (4) call an unsupported method and confirm the error shape is a proper JSON-RPC error rather than an HTML page; (5) repeat the whole sequence against a second endpoint.
Fill in a table like the one below with your own measurements. Leave cells blank until you have real data; do not estimate.
| Endpoint label | Region | Sequential p50 (ms) | Sequential p95 (ms) | Concurrent p95 (ms) | Historical read matches explorer | Error shape valid |
| -------------- | ------ | ------------------- | ------------------- | ------------------- | ------------------------------- | ----------------- |
| RPC_URL_A | | | | | | |
| RPC_URL_B | | | | | | |Reading the results and knowing when a call is healthy
A healthy endpoint returns a valid JSON-RPC envelope for every request, including errors. The id in the response must match the id you sent; mismatches indicate a proxy or cache in the path. Historical reads must agree with an independent source, because a node that is syncing or pruned will return null or an error for old blocks rather than a wrong value.
Latency distributions matter more than averages. A p50 of a few hundred milliseconds with a p95 several times higher suggests queueing under load. If concurrent p95 is much worse than sequential p95, the endpoint is throttling or the node is saturated. These are the numbers to compare when you evaluate a managed service, and they are the numbers to re-run after any infrastructure change.
Troubleshooting common RPC failures
Most production incidents fall into a small set of categories. Work through them in order, because a transport failure masks every layer above it.
Connection refused or TLS errors mean the endpoint is wrong, the host is down, or a firewall is blocking egress. HTTP 429 means you exceeded a rate limit; back off with jitter and consider a plan with higher limits. JSON-RPC error -32000 with a message about nonce means your transaction conflicts with one already pending; re-read the pending nonce and rebuild. Error -32000 mentioning insufficient funds means the account cannot cover value plus gas at the current fee. A null result for a receipt means the transaction is not yet mined; keep polling with a bounded timeout. A result that lags the chain head means the node is behind; fail over to another endpoint.
Log the full request id, method, params, and response body on failure. Without the id and params, you cannot tell a client bug from a node problem.
- 429 or 5xx: transport or quota problem, retry with backoff.
- Nonce errors: re-read pending nonce before resubmitting.
- Null receipts: transaction not yet included, poll with a deadline.
- Stale block numbers: node lagging, fail over or alert.
Public, self-hosted, and managed endpoints compared
Public endpoints are free and convenient for prototyping, but they are shared, often rate-limited, and may not guarantee archive data or WebSocket subscriptions. Self-hosting a node gives you full control and privacy, but you own sync time, disk growth, upgrades, and uptime. A managed provider sits between the two: you get a stable URL and operational support, and you trade some control and cost for reliability.
The right choice depends on your read/write mix, your tolerance for downtime, and whether you need historical state. A wallet that only reads recent blocks has different needs from an indexer replaying years of logs. If you need dedicated capacity and predictable performance, a dedicated node is the relevant option; if you want a managed multi-chain setup, review the network coverage and pricing before committing.
- Public: lowest cost, shared capacity, best for tests and prototypes.
- Self-hosted: maximum control, highest operational burden.
- Managed: stable endpoint and support, usage-based or plan-based cost.
- Dedicated: reserved capacity for high-throughput or latency-sensitive workloads.
Limitations and tradeoffs to plan for
RPC is a request/response interface, not a streaming index. If you need every event in order, you will build a log-polling or subscription pipeline and handle reorgs yourself. Archive queries are expensive and not every endpoint serves them. WebSocket subscriptions can drop silently, so production clients need reconnect logic and a fallback to polling.
No single endpoint is a complete reliability strategy. Use at least two independent endpoints, health-check them, and route around failures. Treat rate limits as a design constraint, not an afterthought, and cache immutable data such as confirmed historical blocks where your application allows it.
Next steps for a production-ready RPC setup
Move from a single hard-coded URL to a configuration layer with primary and fallback endpoints, per-method timeouts, and structured logging. Add a synthetic check that runs the verification procedure on a schedule and alerts when p95 or error rate crosses a threshold you set from your own baseline. Document which methods your app depends on so a provider change can be validated quickly.
When you are ready to choose concrete endpoints, start with the RPC endpoint guide for the general model, then use the RPC Assistant for chain-specific recommendations. Review supported networks and pricing to match coverage and budget, and consider a dedicated node if your measurements show shared capacity is the bottleneck.