Motivation
eth_call answers "what does this return"; it does not answer "what would actually happen". Every serious integrator -- a wallet showing a transaction preview, a searcher validating a bundle, an indexer reconstructing internal transfers -- needs simulation and tracing, and today eth.zig offers neither. This is the largest remaining gap in the RPC surface now that the read methods are complete.
eth_simulateV1 is the modern answer and it subsumes a lot: multiple calls across multiple simulated blocks, state and block overrides, per-call return data, logs, and gas used, all in one round trip. We already have the two hard prerequisites -- state_overrides.zig from #12 and the batching machinery in multicall.zig -- so this is mostly serialization and result parsing.
Scope
eth_simulateV1 on Provider:
- A
SimulatePayload mirroring the spec: blockStateCalls (an array of block objects, each with blockOverrides, stateOverrides, and calls), plus validation and traceTransfers flags
- A
SimulateResult per block with per-call status, returnData, gasUsed, logs, and the error object on failure
- Reuse the existing
state_overrides.StateOverride serialization rather than introducing a parallel type
Trace namespace (separate PR, same module):
debug_traceTransaction(hash, tracer) and debug_traceCall(call, block, tracer) with first-class support for callTracer and prestateTracer -- these two cover the overwhelming majority of real use, and returning a typed CallFrame tree for callTracer is worth far more than a generic JSON blob
trace_call / trace_transaction (the Erigon/Nethermind flavor) as a thin second surface, since a meaningful share of providers expose only one of the two families
- A raw escape hatch that returns the tracer JSON unparsed, so a custom JS tracer is still usable
Capability handling: these methods are frequently unavailable on managed RPC plans. A method not found error must surface as a distinct, documented error rather than a generic error.RpcError, so callers can fall back cleanly. The FallbackProvider failover rule (#70) already draws the transport-vs-RPC-error line correctly -- keep it that way; an unsupported method is an answer, not a transport failure.
Testing
All parsing is offline-testable against captured responses, which is how provider.zig already tests its parsers. Add fixtures for a successful multi-call simulation, a reverting call inside an otherwise successful block, and a callTracer tree at least three frames deep. Live coverage goes in the Anvil integration suite -- Anvil supports both eth_simulateV1 and debug_traceTransaction.
Pointers
src/provider.zig (method surface and the existing response parsers), src/state_overrides.zig, src/json_rpc.zig (add the method-name constants), src/multicall.zig for the batching precedent. Spec: the eth_simulateV1 entry in https://github.com/ethereum/execution-apis and the go-ethereum tracer docs for the callTracer / prestateTracer result shapes.
Motivation
eth_callanswers "what does this return"; it does not answer "what would actually happen". Every serious integrator -- a wallet showing a transaction preview, a searcher validating a bundle, an indexer reconstructing internal transfers -- needs simulation and tracing, and today eth.zig offers neither. This is the largest remaining gap in the RPC surface now that the read methods are complete.eth_simulateV1is the modern answer and it subsumes a lot: multiple calls across multiple simulated blocks, state and block overrides, per-call return data, logs, and gas used, all in one round trip. We already have the two hard prerequisites --state_overrides.zigfrom #12 and the batching machinery inmulticall.zig-- so this is mostly serialization and result parsing.Scope
eth_simulateV1onProvider:SimulatePayloadmirroring the spec:blockStateCalls(an array of block objects, each withblockOverrides,stateOverrides, andcalls), plusvalidationandtraceTransfersflagsSimulateResultper block with per-callstatus,returnData,gasUsed,logs, and the error object on failurestate_overrides.StateOverrideserialization rather than introducing a parallel typeTrace namespace (separate PR, same module):
debug_traceTransaction(hash, tracer)anddebug_traceCall(call, block, tracer)with first-class support forcallTracerandprestateTracer-- these two cover the overwhelming majority of real use, and returning a typedCallFrametree forcallTraceris worth far more than a generic JSON blobtrace_call/trace_transaction(the Erigon/Nethermind flavor) as a thin second surface, since a meaningful share of providers expose only one of the two familiesCapability handling: these methods are frequently unavailable on managed RPC plans. A
method not founderror must surface as a distinct, documented error rather than a genericerror.RpcError, so callers can fall back cleanly. TheFallbackProviderfailover rule (#70) already draws the transport-vs-RPC-error line correctly -- keep it that way; an unsupported method is an answer, not a transport failure.Testing
All parsing is offline-testable against captured responses, which is how
provider.zigalready tests its parsers. Add fixtures for a successful multi-call simulation, a reverting call inside an otherwise successful block, and acallTracertree at least three frames deep. Live coverage goes in the Anvil integration suite -- Anvil supports botheth_simulateV1anddebug_traceTransaction.Pointers
src/provider.zig(method surface and the existing response parsers),src/state_overrides.zig,src/json_rpc.zig(add the method-name constants),src/multicall.zigfor the batching precedent. Spec: theeth_simulateV1entry in https://github.com/ethereum/execution-apis and the go-ethereum tracer docs for thecallTracer/prestateTracerresult shapes.