eth_call: Simulate Contract Calls on Ethereum
eth_call•Read method•Ethereum JSON-RPCeth_call executes a message call against the EVM without creating a transaction. Nothing is signed, nothing is broadcast and no gas is paid; the node runs your call and hands back the raw return data. It is the most used method in the Ethereum JSON-RPC API: every balanceOf, every price quote and every view function in ethers.js or web3.js compiles down to an eth_call.
Try eth_call
curl -s https://ethereum-rpc.publicnode.com \
-X POST \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_call",
"params": [
{
"to": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"data": "0x70a08231000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045"
},
"latest"
]
}'Runs the example straight from your browser against the endpoint above. The prefilled URL is a third-party public endpoint, not run by BLAZED.sh.
What eth_call does
When a node receives eth_call it loads the state at the block you specify, runs the EVM as if your call object were a transaction included in that block, and returns whatever the callee returns. All state changes made during execution are discarded afterwards, which makes the method safe to hammer: you can quote a DEX price ten times a second without touching the chain or spending a wei.
The call object has the same shape as a transaction. The field that matters is data (calldata): the 4-byte function selector followed by the ABI-encoded arguments. In the example below, 0x70a08231 is the selector for balanceOf(address) and the rest is the queried address left-padded to 32 bytes. Libraries build this encoding for you; on the wire it is always this one hex string.
A third, optional parameter takes Geth-style state overrides: for the duration of the call you can give any address a fake balance, replace deployed code or set individual storage slots. That turns eth_call into a lightweight simulator, covered in the gotchas below.
Parameters
| # | Name | Type | Description |
|---|---|---|---|
| 1 | callObject | object | Transaction-shaped call: to (contract address) plus optional from, gas, value, fee fields, and data (or input) holding the ABI-encoded calldata. |
| 2 | blockParameter | string | Hex block number or one of latest, pending, safe, finalized, earliest. Also accepts an EIP-1898 object with blockHash or blockNumber. |
| 3 | stateOverridesoptional | object | Geth-style per-address overrides applied only for this call: balance, nonce, code, and storage slots (state / stateDiff). |
What it returns
A hex string with the raw return data of the call. For balanceOf that is a single 32-byte uint256; functions returning multiple values come back ABI-packed in the same string, and functions returning nothing yield 0x.
Note that 0x is also what you get when calling an address that has no code at all, which is a classic silent failure covered in the gotchas.
Example response
{
"jsonrpc": "2.0",
"id": 1,
"result": "0x00000000000000000000000000000000000000000000000000000045d964b800"
}One ABI-encoded uint256: 0x45d964b800 is 300,000,000,000 raw units, which at USDC's 6 decimals reads as 300,000 USDC (illustrative value; the live call returns the current balance).
eth_call with ethers.js
import { JsonRpcProvider, Contract } from "ethers";
const provider = new JsonRpcProvider("https://ethereum-rpc.publicnode.com");
const usdc = new Contract(
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
["function balanceOf(address) view returns (uint256)"],
provider,
);
// Every view call goes over the wire as eth_call
const balance = await usdc.balanceOf("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045");
console.log(balance); // raw uint256, 6 decimals for USDCGotchas and common errors
A revert is an error, not a result
When the callee reverts, the node returns a JSON-RPC error instead of a result, with the revert data attached to the error's data field. A require(false, "reason") encodes as Error(string): the data starts with the selector 0x08c379a0 followed by the ABI-encoded message. ethers.js decodes this and throws a CALL_EXCEPTION with the reason set; if you speak raw JSON-RPC you decode it yourself. Custom errors (Solidity 0.8.4 and later) revert with their own 4-byte selectors, so an unrecognized selector in revert data usually means a custom error from the contract's ABI.
The from field defaults to the zero address
Leaving from unset runs the call as 0x0000000000000000000000000000000000000000. Most view functions do not care, but anything that reads msg.sender does: allowance checks, onlyOwner views, token-gated getters. If a call works in your app but returns nonsense over curl, set from to the account your app signs with.
0x usually means you called the wrong address
Calling a function on an address with no deployed code does not fail; the EVM treats it as a trivially successful call and returns empty data. A typo in the contract address, the wrong network, or an uninitialized proxy all present as 0x rather than an error. When your library blows up with "could not decode result data", check eth_getCode for the address before debugging anything else.
The node caps your gas
eth_call charges no gas but still meters it, and clients cap simulation gas: Geth's --rpc.gascap defaults to 50 million. Heavy multi-hop quoter calls can hit the cap and fail with "gas required exceeds allowance". On shared endpoints the cap is whatever the provider set; on your own node it is your configuration.
Historical calls need historical state
Passing an old block number only works if the node still holds that block's state. Full nodes keep recent state (in the order of the last 128 blocks for Geth) plus whatever pruning retains; beyond that you get "missing trie node" or "required historical state unavailable" errors and need an archive node. On BLAZED.sh the standard plans run tip-of-chain nodes and archive access is an Enterprise feature.
State overrides are free simulations
The optional third parameter patches state for the duration of the call: give your address a fake million-ETH balance, swap in modified contract code, or set a storage slot directly. This is how you simulate an approve-then-swap flow without an on-chain approval, or test contract behavior at balances you do not have. It costs the same 1 credit as a plain eth_call.
What eth_call costs on BLAZED.sh
eth_call costs 1 credit on BLAZED.sh, the standard rate covering most Ethereum RPC methods; responses over 100KB add 50 credits per MB, which a 32-byte balance result never triggers. The bigger factor for eth_call workloads is latency: quote loops and simulation-heavy bots issue thousands of calls per minute, and on a co-located node each one is a sub-10ms local round-trip instead of a 50-500ms trip through a shared gateway.
See the full credit price listeth_call: frequently asked questions
Does eth_call cost gas?
No. Gas is metered during execution only to bound the work; nothing is paid and nothing lands on chain. Providers still bill the request itself, and on BLAZED.sh it costs 1 credit per call.
What is the difference between eth_call and eth_estimateGas?
Both simulate your transaction against current state. eth_call returns the function's return data, while eth_estimateGas returns the amount of gas the execution used. Use eth_call to read results and eth_estimateGas to fill in the gas limit before sending.
Why does eth_call return 0x?
Either the function genuinely returns nothing, or you called an address without code: wrong address, wrong network, or a contract that is not deployed yet. The EVM treats calls to codeless addresses as successful, so verify with eth_getCode.
Can I run eth_call against a past block?
Yes, pass a hex block number instead of latest. It works as far back as your node keeps state; older blocks need an archive node, which on BLAZED.sh is available on Enterprise plans.
Is eth_call rate limited?
On shared providers, yes, and quote-heavy bots feel it first. On a dedicated co-located node there are no shared rate limits; usage is metered in per-request credits instead.
Call eth_call from the node itself
Deploy your container or script next to a synced Ethereum node. No rate limits, no compute units, sub-10ms local RPC.