# Ethereum RPC: The Complete Developer Reference

Every interaction with Ethereum — reading a balance, submitting a transaction, querying event logs — goes through an RPC endpoint. The endpoint is your window into the chain. If it's slow, rate-limited, or returns stale data, your application breaks in ways that are hard to debug because the failure mode is silence: your call just hangs or returns an error that looks like a bug in your code.

This is the practical reference for connecting to Ethereum via JSON-RPC. Not a "what is a blockchain" explainer — just the methods, the setup, and the production concerns that actually matter when you're building against Ethereum mainnet.

## The essentials

Ethereum mainnet is **chain ID 1** (0x1), with:

*   **Block time:** ~12 seconds
    
*   **Gas token:** ETH
    
*   **Consensus:** Proof-of-stake (since The Merge, September 2022)
    
*   **Finality:** ~13 minutes (2 epochs of 32 slots each), with "safe" and "finalized" block tags available sooner
    
*   **State size:** ~1.5 TB for a full node, ~14 TB for archive
    

Any EVM-compatible tool — viem, ethers, Foundry, Hardhat, web3.py — works directly against Ethereum's RPC. The interface is the same one every EVM chain copies.

## Connecting: the setup

### curl (the universal fallback)

```bash
# Chain ID
curl -s -X POST https://rpc.swiftnodes.io/rpc/eth?key=YOUR_API_KEY \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}'
# -> {"jsonrpc":"2.0","id":1,"result":"0x1"}

# Latest block number
curl -s -X POST https://rpc.swiftnodes.io/rpc/eth?key=YOUR_API_KEY \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'
# -> {"jsonrpc":"2.0","id":1,"result":"0x..."}

# ETH balance
curl -s -X POST https://rpc.swiftnodes.io/rpc/eth?key=YOUR_API_KEY \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"eth_getBalance","params":["0xADDRESS","latest"],"id":1}'
```

### viem (recommended for new projects)

```typescript
import { createPublicClient, http } from 'viem';
import { mainnet } from 'viem/chains';

const client = createPublicClient({
  chain: mainnet,
  transport: http('https://rpc.swiftnodes.io/rpc/eth?key=YOUR_API_KEY'),
});

const blockNumber = await client.getBlockNumber();
const balance = await client.getBalance({ address: '0x...' });
const logs = await client.getLogs({
  address: '0x...',
  event: yourAbiEvent,
  fromBlock: 21000000n,
  toBlock: 'latest',
});
```

### ethers v6

```typescript
import { JsonRpcProvider } from 'ethers';

const provider = new JsonRpcProvider(
  'https://rpc.swiftnodes.io/rpc/eth?key=YOUR_API_KEY'
);

const blockNumber = await provider.getBlockNumber();
const balance = await provider.getBalance('0x...');
```

## The methods you'll actually use

Most of Ethereum's RPC surface is niche. Here are the methods that cover 95% of real-world usage:

| Method | What it does | When you need it |
| --- | --- | --- |
| `eth_blockNumber` | Latest block height | Health checks, sync monitoring |
| `eth_getBalance` | ETH balance for an address | Wallet UIs, portfolio trackers |
| `eth_call` | Read-only contract call | Reading contract state (balances, prices, etc.) |
| `eth_sendRawTransaction` | Submit a signed transaction | Sending transactions |
| `eth_getTransactionReceipt` | Transaction receipt + status | Confirming transactions landed |
| `eth_getLogs` | Event logs for a block range | Indexing, monitoring, event-driven apps |
| `eth_getBlockByNumber` | Full block with or without txs | Block explorers, analytics |
| `eth_gasPrice` / `eth_feeHistory` | Current and historical gas prices | Gas estimation, fee UX |
| `eth_estimateGas` | Gas estimate for a transaction | Pre-flight checks before sending |
| `eth_chainId` | Chain identifier | Multi-chain routing, safety check |

### The two methods that cause the most trouble

`eth_getLogs` is the most commonly misused method. It scans event logs across a block range, and on a chain with Ethereum's volume, unbounded range queries will time out or get rejected. Always chunk your queries — a safe default is **2,000 blocks per request** on Ethereum mainnet. If you need to scan a wider range, loop through it:

```javascript
const CHUNK = 2000;
for (let from = startBlock; from <= endBlock; from += CHUNK) {
  const logs = await provider.getLogs({
    address: contractAddress,
    fromBlock: from,
    toBlock: Math.min(from + CHUNK - 1, endBlock),
  });
  // process logs
}
```

`eth_call` at a historical block requires archive access. `eth_call` against `latest` works on any node. `eth_call` against block 15,000,000 requires a node that still has that state — which means an archive node. Not every provider offers archive access, and the ones that do typically charge more or impose stricter rate limits on historical calls.

## Block tags and commitment levels

Ethereum's RPC accepts several block tags, and using the right one matters:

| Tag | Meaning | When to use |
| --- | --- | --- |
| `latest` | The most recent block in the canonical chain | Default for most reads |
| `safe` | A block that's very unlikely to be reorged (~5 minutes behind latest) | When you need high confidence but can't wait for finality |
| `finalized` | A block that's been finalized by the consensus layer (~13 minutes behind) | When reorgs are unacceptable (settlement, accounting) |
| `pending` | The node's view of the next block (includes mempool txs) | Simulating pending state — unreliable, most providers don't support it |
| `earliest` | The genesis block | Rarely useful |

For most applications, `latest` is fine. If you're building something that handles value — a bridge, a settlement system, an accounting tool — use `finalized` for the reads that matter. The 13-minute delay is the cost of reorg safety.

## WebSocket vs HTTP

HTTP is request-response: you send a call, you get a result. WebSocket is a persistent connection that supports subscriptions — the node pushes data to you when something changes.

**Use HTTP when:**

*   You're making occasional reads (balances, contract state)
    
*   You're submitting transactions
    
*   Your usage is bursty or low-frequency
    

**Use WebSocket when:**

*   You need to react to new blocks in real time
    
*   You're monitoring specific accounts or contracts for changes
    
*   You're building a trading bot or indexer that can't afford polling latency
    

```typescript
import { createPublicClient, webSocket } from 'viem';
import { mainnet } from 'viem/chains';

const client = createPublicClient({
  chain: mainnet,
  transport: webSocket('wss://rpc.swiftnodes.io/ws/eth?key=YOUR_API_KEY'),
});

// Subscribe to new blocks
const unwatch = client.watchBlocks({
  onBlock: (block) => {
    console.log(`New block: ${block.number}`);
  },
});

// Subscribe to pending transactions for an address
const unwatchPending = client.watchPendingTransactions({
  onTransactions: (hashes) => {
    // Process pending tx hashes
  },
});
```

The common mistake is polling `eth_blockNumber` over HTTP in a tight loop instead of using WebSocket subscriptions. On Ethereum's 12-second block time, polling every second is wasteful and will eat through your rate limit. Use `eth_subscribe` with `"newHeads"` over WebSocket and let the node push blocks to you.

## Archive access: when you need it and when you don't

A standard full node keeps recent state (typically the last 128 blocks, configurable). It can answer any question about current state. What it *cannot* do is tell you the balance of an address at block 15,000,000 — that state has been pruned.

An **archive node** stores every state snapshot from genesis. You need one when:

*   Querying historical balances or contract state (`eth_getBalance` at an old block)
    
*   Running `eth_call` at a historical block
    
*   Building an indexer that needs to backfill from old blocks
    
*   Debugging a transaction from weeks or months ago
    

You *don't* need archive access for:

*   Reading current balances or contract state
    
*   Submitting transactions
    
*   Querying recent logs (within the last few thousand blocks)
    
*   Monitoring new blocks or events
    

Archive nodes are expensive to run — the state dataset is ~14 TB and growing. Most providers charge a premium for archive access, or restrict it to specific plans. If your application needs historical state queries, confirm archive support before choosing an endpoint.

## Gas estimation in practice

`eth_estimateGas` gives you a gas estimate for a transaction, but it's not always accurate. The estimate is based on the current state, and if the state changes between your estimate and your actual submission, the real gas cost can differ.

The practical approach:

```typescript
// Get the estimate
const estimated = await client.estimateGas({
  to: '0x...',
  data: calldata,
  account: '0x...',
});

// Add a buffer (10-20% is standard)
const gasLimit = (estimated * 120n) / 100n;

// Get current fee data
const { maxFeePerGas, maxPriorityFeePerGas } = await client.estimateFeesPerGas();

// Submit with the buffered limit
const hash = await client.sendTransaction({
  to: '0x...',
  data: calldata,
  gas: gasLimit,
  maxFeePerGas,
  maxPriorityFeePerGas,
});
```

The 20% buffer accounts for state changes between estimation and execution. Without it, transactions occasionally revert with "out of gas" when the execution path is slightly more expensive than the estimate predicted.

## Rate limits and production concerns

Every RPC provider has rate limits. The ones that matter:

*   **Requests per second (RPS)** — how many calls you can make per second
    
*   **Requests per day** — total daily budget (common on free tiers)
    
*   **Method-specific limits** — heavy methods like `eth_getLogs` or `trace_block` may have separate, lower limits
    
*   **Concurrent connections** — WebSocket connection limits
    

The failure mode is usually HTTP 429 (Too Many Requests). The fix is:

1.  **Batch calls** — JSON-RPC batching lets you send multiple calls in a single HTTP request. Most providers support this and it counts as one request against your rate limit.
    
2.  **Cache aggressively** — block data doesn't change. If you've fetched block 21,000,000, cache it. Don't re-fetch it.
    
3.  **Use WebSocket subscriptions** instead of polling — dramatically reduces request volume.
    
4.  **Implement exponential backoff** — when you hit a 429, wait and retry with increasing delays.
    

```javascript
async function callWithRetry(fn, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (err) {
      if (err.code === 429 && i < maxRetries - 1) {
        await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i)));
        continue;
      }
      throw err;
    }
  }
}
```

## Choosing an Ethereum RPC endpoint

The decision comes down to three things:

**1\. Do you need archive access?** If your application queries historical state, you need a provider that offers it. Not all do, and the ones that do typically restrict it to paid plans.

**2\. What's your request volume?** Low-traffic dApps can use free tiers. Anything with real users will hit free-tier limits quickly. Calculate your expected requests-per-day: a moderately active dApp with 1,000 daily users making ~10 reads each = 10,000 requests/day.

**3\. Do you need WebSocket subscriptions?** If you're building anything real-time — a trading interface, a monitoring dashboard, an indexer — WebSocket support is non-negotiable. Not all providers offer it, and the ones that do may limit concurrent connections.

Other factors — latency, geographic distribution, uptime guarantees — matter but are secondary to the three above. An endpoint with 50ms latency that doesn't support archive when you need archive is useless.

## The short version

Ethereum mainnet is **chain ID 1**, ~12-second blocks, ETH gas, proof-of-stake. The RPC interface is standard JSON-RPC — `eth_call`, `eth_getLogs`, `eth_sendRawTransaction`, and the rest. Use `latest` for most reads, `finalized` when reorgs are unacceptable. Chunk `eth_getLogs` into 2,000-block ranges. Use WebSocket subscriptions instead of polling. Add a 20% buffer to gas estimates.

For Ethereum RPC access across load-balanced nodes with archive support and WebSocket subscriptions, [grab a free API key](https://swiftnodes.io/) and point your app at:

```plaintext
https://rpc.swiftnodes.io/rpc/eth?key=YOUR_API_KEY
```
