Ethereum RPC Methods: A Practical Guide for dApp Developers
The full Ethereum JSON-RPC specification has dozens of methods. In practice, most dApps use around 10 of them regularly. This guide focuses on the methods you will actually use, what they do, when to use each, and what to watch out for.
For the full official specification, see ethereum.org/developers/docs/apis/json-rpc.
How Ethereum RPC Methods Work
Every Ethereum node exposes a JSON-RPC API. You call it by sending a POST request with a JSON body specifying the method and parameters:
curl -X POST https://eu.endpoints.matrixed.link/rpc/ethereum?auth=YOUR_KEY \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "eth_blockNumber",
"params": [],
"id": 1
}'
In practice, you will use ethers.js, viem, or web3.py to make these calls rather than raw curl. Those libraries translate their methods into the underlying JSON-RPC calls automatically.
Reading State: The Methods You Use Most
eth_getBalance
Returns the ETH balance of an address at a given block.
// ethers.js
const balance = await provider.getBalance('0xYourAddress');
console.log(ethers.formatEther(balance)); // "1.234567890"
// Raw JSON-RPC
{
"method": "eth_getBalance",
"params": ["0xYourAddress", "latest"]
}
When to use: Any time you need to display or check a wallet’s ETH balance.
The block parameter: "latest" returns the current balance. Pass a specific block number (hex) to get the historical balance at that block, which requires archive access.
eth_call
Executes a read-only call to a smart contract without submitting a transaction. Does not cost gas. Returns the contract’s response.
// ethers.js — reading a contract
const contract = new ethers.Contract(contractAddress, abi, provider);
const result = await contract.balanceOf('0xSomeAddress');
// Raw JSON-RPC
{
"method": "eth_call",
"params": [
{
"to": "0xContractAddress",
"data": "0x70a08231000000000000000000000000SomeAddress"
},
"latest"
]
}
When to use: Reading any contract state: token balances, pool prices, governance votes, NFT ownership. This is the most-called method in most dApps.
Gas-free: eth_call simulates the transaction locally on the node. It does not broadcast to the network and costs no gas.
eth_getTransactionReceipt
Returns the receipt for a completed transaction: status (success/failure), gas used, logs emitted, block number.
// ethers.js
const receipt = await provider.getTransactionReceipt(txHash);
console.log(receipt.status); // 1 = success, 0 = failure
console.log(receipt.gasUsed);
console.log(receipt.logs); // Events emitted by the transaction
When to use: Confirming that a transaction succeeded. Checking which events were emitted. Reading return values from state-changing transactions.
Note: Returns null if the transaction has not been mined yet. Poll until you get a result, or use provider.waitForTransaction(txHash).
eth_getLogs
Queries event logs emitted by contracts matching a filter. The most powerful (and most expensive) read method.
// ethers.js — get all Transfer events from an ERC-20 contract
const filter = {
address: tokenContractAddress,
topics: [ethers.id("Transfer(address,address,uint256)")],
fromBlock: 19000000,
toBlock: 'latest'
};
const logs = await provider.getLogs(filter);
When to use: Indexing contract events. Fetching historical token transfers. Reading DeFi protocol activity. Building dashboards that need on-chain event history.
Cost note: eth_getLogs is one of the more resource-intensive RPC methods. Providers using compute unit weighting assign it a significantly higher cost multiplier than basic queries. Check your provider’s method weighting documentation before building applications that rely heavily on this method. BoltRPC pricing is straightforward — fixed monthly plans with no surprise bills.
Block range: Querying a wide block range can be slow or rejected by some providers. For large historical queries, break them into chunks (e.g., 2,000 blocks at a time) and concatenate results.
// Chunked eth_getLogs for large ranges
async function getLogsInChunks(provider, filter, chunkSize = 2000) {
const logs = [];
let fromBlock = filter.fromBlock;
const toBlock = filter.toBlock === 'latest'
? await provider.getBlockNumber()
: filter.toBlock;
while (fromBlock <= toBlock) {
const chunkEnd = Math.min(fromBlock + chunkSize - 1, toBlock);
const chunk = await provider.getLogs({
...filter,
fromBlock,
toBlock: chunkEnd
});
logs.push(...chunk);
fromBlock = chunkEnd + 1;
}
return logs;
}
eth_getCode
Returns the bytecode deployed at a contract address. Returns 0x for externally-owned accounts (wallets).
const code = await provider.getCode('0xContractAddress');
const isContract = code !== '0x';
When to use: Checking whether an address is a contract or a wallet before sending a transaction. Verifying that a contract is deployed.
Sending Transactions
eth_sendRawTransaction
Broadcasts a signed transaction to the network. This is the method used for all on-chain writes: token transfers, contract interactions, contract deployments.
// ethers.js handles signing and broadcasting
const signer = new ethers.Wallet(privateKey, provider);
const tx = await signer.sendTransaction({
to: '0xRecipientAddress',
value: ethers.parseEther('0.01')
});
const receipt = await tx.wait(); // Wait for mining
Under the hood: ethers.js signs the transaction locally, serializes it, and calls eth_sendRawTransaction with the signed bytes. Your private key never leaves your application.
Important: eth_sendRawTransaction broadcasts to the mempool. It does not guarantee inclusion. The transaction may be stuck if gas price is too low. Use eth_getTransactionReceipt or provider.waitForTransaction() to confirm.
eth_estimateGas
Estimates the gas required for a transaction before sending it.
const gasEstimate = await provider.estimateGas({
to: contractAddress,
data: encodedFunctionCall
});
// Add a buffer to avoid out-of-gas failures
const gasLimit = gasEstimate * 120n / 100n; // 20% buffer
When to use: Before every state-changing transaction. Setting gasLimit too low causes the transaction to fail (and still costs gas). Setting it too high wastes nothing, but users may hesitate at inflated gas estimates.
Block and Network Queries
eth_blockNumber
Returns the current block height.
const blockNumber = await provider.getBlockNumber();
When to use: Knowing the current block for relative block calculations. Confirming how many confirmations a transaction has. Polling for new blocks (though WebSocket subscriptions are more efficient).
eth_getBlockByNumber
Returns full block data: timestamp, miner/validator, transaction list, gas used.
// ethers.js
const block = await provider.getBlock('latest');
console.log(block.timestamp); // Unix timestamp
console.log(block.transactions); // Array of tx hashes
// With full transaction objects
const blockWithTxs = await provider.getBlock('latest', true);
When to use: Getting block timestamps for time-based logic. Fetching all transactions in a block. Calculating block-based metrics.
eth_chainId
Returns the chain ID of the connected network.
const network = await provider.getNetwork();
console.log(network.chainId); // 1n for Ethereum mainnet
When to use: Verifying you are connected to the right network before signing transactions. Essential in multi-chain applications where a wrong-chain transaction can cause real damage.
WebSocket-Only: Subscriptions
These methods are only available over WebSocket. They push data to your application as events occur, rather than requiring polling.
eth_subscribe: newHeads
Subscribe to new block headers. Fires every time a new block is produced.
const wsProvider = new ethers.WebSocketProvider(
'wss://eu.endpoints.matrixed.link/ws/ethereum?auth=YOUR_KEY'
);
wsProvider.on('block', (blockNumber) => {
console.log('New block:', blockNumber);
});
When to use: Real-time UIs that display the latest block. Any application that needs to react to new blocks without polling.
eth_subscribe: logs
Subscribe to contract event logs matching a filter. Fires in real time as matching events are emitted.
const filter = {
address: uniswapPoolAddress,
topics: [ethers.id("Swap(address,address,int256,int256,uint160,uint128,int24)")]
};
wsProvider.on(filter, (log) => {
console.log('New swap event:', log);
});
When to use: Real-time event monitoring. Trading dashboards. Liquidation bots. Price feeds that react to on-chain swaps.
Quick Reference
| Method | Type | Use Case |
|---|---|---|
eth_getBalance | Read | ETH balance of address |
eth_call | Read | Read contract state (gas-free) |
eth_getTransactionReceipt | Read | Confirm transaction + get logs |
eth_getLogs | Read | Historical event queries |
eth_getCode | Read | Check if address is a contract |
eth_sendRawTransaction | Write | Broadcast signed transaction |
eth_estimateGas | Utility | Pre-estimate gas for transaction |
eth_blockNumber | Utility | Current block height |
eth_getBlockByNumber | Utility | Block data + transactions |
eth_chainId | Utility | Verify connected network |
eth_subscribe (newHeads) | WSS | Real-time block notifications |
eth_subscribe (logs) | WSS | Real-time contract event stream |
One RPC Endpoint for All EVM Methods
BoltRPC supports the full Ethereum JSON-RPC method set across all 22 supported networks. Every method in this guide works on Ethereum, Arbitrum, Base, Polygon, Optimism and all other supported EVM chains. Same method names, same format, just a different endpoint URL.
Trusted by Chainlink, Tiingo, Gains Network, Enjin. Built on ISO/IEC 27001:2022 certified infrastructure via Matrixed.Link. Flat monthly plan pricing — predictable costs with no surprise bills.
Start your free 2-week trial: trial.boltrpc.io
See all supported networks: boltrpc.io/networks
FAQ
Do all EVM chains support the same RPC methods?
The core methods (eth_getBalance, eth_call, eth_sendRawTransaction, etc.) are supported on all EVM-compatible chains. Some chains add additional methods or modify response fields. Arbitrum, for example, adds arb_ prefixed methods. Base and Optimism add optimism_ methods. Always check the chain-specific documentation for anything beyond the core spec.
Why does eth_getLogs cost more with some providers?
Providers using compute unit pricing apply a multiplier to eth_getLogs because it is resource-intensive on the node. The multiplier can be large relative to simple queries — often 75× or more compared to a basic balance check. BoltRPC uses flat-rate monthly plans, so your costs remain predictable regardless of which methods you call most heavily.
What is the difference between eth_call and eth_sendRawTransaction?
eth_call executes a read-only simulation locally on the node: no gas, no state change, not broadcast to the network. eth_sendRawTransaction broadcasts a signed transaction to the network and causes a real state change. Use eth_call to read; use eth_sendRawTransaction to write.
How do I decode event logs from eth_getLogs?
Event logs are ABI-encoded. You need the contract ABI to decode them. ethers.js does this automatically when you use a Contract instance with the ABI. For raw logs, use ethers.Interface to decode:
const iface = new ethers.Interface(abi);
const decoded = iface.parseLog(log);
Can I use WebSocket for all methods, not just subscriptions?
Yes. WebSocket supports all standard JSON-RPC methods plus subscriptions. However, for request-response queries, HTTP is simpler to manage (no persistent connection, no reconnection logic). Use WebSocket when you need subscriptions; use HTTP for everything else.