Monad JSON-RPC Overview
JSON-RPC Overview
Available JSON-RPC methods, behavioral differences from Ethereum, rate limits, and error handling.
Monad exposes a JSON-RPC interface for on-chain interaction. The implementation closely follows Geth conventions, though certain behaviors differ due to Monad's distinct consensus and execution architecture.
Supported methods#
Behavioral differences from Ethereum#
Monad maintains Geth compatibility, yet its architecture — featuring asynchronous execution and sub-second block times — results in notable behavioral differences across several RPC methods.
Transaction lifecycle#
Deferred nonce/balance validation. eth_sendRawTransaction does not always reject transactions with a nonce gap or insufficient gas balance on arrival. Because Monad's RPC layer operates asynchronously, the most recent account state may not yet be available when the transaction is received. Such transactions are tentatively accepted because they can become valid by the time the next block is built.
No pending transaction queries. eth_getTransactionByHash only surfaces transactions that have already been included in a block. Looking up a hash that is still in the mempool returns null.
State availability#
eth_call requests that target historical state (by specifying an older block number) may fail, as full nodes do not retain access to arbitrary past state. Refer to Historical Data for information on available state windows and access methods.
Fee estimation#
eth_maxPriorityFeePerGas currently returns a fixed suggestion of 2 gwei. This is a temporary value and will change in a future release.
eth_feeHistory with newest_block = latest: by convention this method returns fee data for the requested range plus a projected fee for the upcoming block. Because Monad cannot yet determine the next block's base fee at query time, the most recent baseFeePerGas is repeated as the projection when latest is requested.
Debug / tracing#
Trace options parameter is required. debug_traceCall, debug_traceTransaction, and other debug_trace* endpoints expect the trace options object to be passed explicitly. In contrast to standard EVM clients, where this parameter can be omitted, Monad RPC responds with -32602 Invalid params when it is missing. Always supply the object, even if empty:
{
"method": "debug_traceCall",
"params": [
{
"to": "0x6b175474e89094c44da98b954eedeac495271d0f"
},
"latest",
{}
]
}
Default tracer is callTracer. When an empty trace options object {} is passed, Monad uses callTracer by default rather than the struct-log tracer common in other EVM clients. Opcode-level struct logs are not currently available on Monad's VM.
Unsupported features#
| Feature | Affected methods | Details |
|---|---|---|
| EIP-4844 (blob transactions) | eth_sendRawTransaction, eth_call, eth_estimateGas | Blob transaction type is rejected |
syncing subscription | eth_subscribe | Not supported |
newPendingTransactions subscription | eth_subscribe | Not supported |
Limits#
eth_call / eth_estimateGas#
Gas limit per call#
| Provider | Public RPC | Gas limit |
|---|---|---|
| QuickNode | rpc.monad.xyz | 200M gas |
| Alchemy | rpc1.monad.xyz | 200M gas |
| Ankr | rpc3.monad.xyz | 1B gas |
| Monad Foundation | rpc-mainnet.monadinfra.com | 200M gas |
Node operators can adjust these caps with --eth-call-provider-gas-limit (default 30M) and --eth-estimate-gas-provider-gas-limit (default 30M).
Gas limit resolution#
If the caller sets a gas price (gasPrice or maxFeePerGas), the effective gas limit becomes min(gas limit allowance, provider gas limit) — the gas limit allowance being the maximum gas affordable given the caller's balance and the specified price. Without a gas price, the provider gas limit is used as-is. This matches Geth's behaviour.
Dual-pool execution model#
Incoming eth_call and eth_estimateGas requests are dispatched to one of two execution pools depending on the gas limit specified by the caller:
| Pool | Gas limit | Purpose |
|---|---|---|
| Low-gas | ≤ 8,100,000 | Most calls; higher throughput |
| High-gas | > 8,100,000 | Large simulations; limited concurrency |
When no gas limit is provided, the request first runs in the low-gas pool. If execution exceeds the pool's gas cap, it is automatically retried in the high-gas pool.
Node operators can tune pool concurrency with --eth-call-max-concurrent-requests (default 1000) and --eth-call-high-max-concurrent-requests (default 20).
eth_getLogs#
Block range limit per call#
| Provider | Public RPC | Block range limit |
|---|---|---|
| QuickNode | rpc.monad.xyz | 100 blocks |
| Alchemy | rpc1.monad.xyz | 1,000 blocks and 10,000 logs (whichever is more constraining) |
| Ankr | rpc3.monad.xyz | 1,000 blocks |
| Monad Foundation | rpc-mainnet.monadinfra.com | 100 blocks |
Node operators can set the range cap with --eth-get-logs-max-block-range.
Why are block range limits low?#
Monad produces a block roughly every 400 ms, each capable of holding up to 5,000 transactions and 200M gas of computation. Because blocks arrive far more frequently and carry substantially more data than Ethereum blocks, per-call range limits are kept intentionally tight.
Errors#
Monad's JSON-RPC error codes follow Ethereum conventions where possible, though some values differ because error codes are not fully standardized across Ethereum clients.
Request-level errors (-32601)#
| Message | Explanation | Common cause |
|---|---|---|
| Parse error | Unable to parse the JSON-RPC request | Malformed JSON |
| Invalid request | The request is structurally invalid | Request exceeds size limit |
| Method not found | The method is not part of the JSON-RPC spec | Typo in method name |
| Method not supported | The method exists in the spec but is not yet supported by Monad | Calling an unimplemented method |
Parameter errors (-32602)#
| Message | Explanation | Common cause |
|---|---|---|
| Invalid block range | The requested eth_getLogs block range exceeds the provider limit | See block range limits |
| Invalid params | Incorrect parameters for the method | Wrong types, missing required fields, omitted trace options |
Execution errors (-32603)#
| Message | Explanation | Common cause |
|---|---|---|
| Internal error | The request could not be fulfilled | Server-side failure |
| Execution reverted | The simulated transaction reverted | Failed eth_call or eth_estimateGas |
| Transaction decoding error | The raw transaction could not be decoded | Invalid RLP in eth_sendRawTransaction |
WebSocket subscriptions#
Monad's RPC server accepts JSON-RPC connections over WebSocket, providing persistent channels for real-time event streaming through eth_subscribe. Refer to the Geth documentation for general eth_subscribe semantics. In addition to the standard types, Monad offers two speculative subscription variants — monadNewHeads and monadLogs — that deliver data roughly one second sooner on average by leveraging speculative execution. See Speculative Real-Time Data for background and Block States for the full block lifecycle.
Subscription types#
| Type | Fires when |
|---|---|
newHeads | A new header is appended, once the block is Voted |
logs | Matching logs appear in a new block, once the block is Voted |
monadNewHeads | A new header is available, once the block is Proposed and speculatively executed |
monadLogs | Matching logs are available, once the block is Proposed and speculatively executed |
The syncing and newPendingTransactions subscription types are not available.
Speculative subscription behavior#
monadNewHeads and monadLogs notifications carry two extra fields that the standard variants do not include:
blockId— a unique identifier for this particular block proposal (separate from the block number, because several proposals may exist at the same height).commitState— the block's current commit state:Proposed,Voted,Finalized, orVerified.
A single block typically generates multiple notifications as its commitState progresses through the lifecycle. A block can jump directly from Proposed to Finalized, bypassing Voted, when consensus runs ahead of execution. If a block ultimately fails to finalize, no explicit cancellation event is emitted — finalization of a different block at the same height implicitly supersedes it.