Summary
The public /evm endpoint appears to ignore non-latest block numbers passed to eth_call and eth_getCode, evaluating the requests against current state instead. Unsupported historical block numbers — and block numbers above the current head — return well-formed results rather than a JSON-RPC error.
The documentation already states that for these state methods "only the latest block is supported". This report is therefore not a request for archival state support. The issue is that an unsupported block parameter is silently accepted and answered from a different state than the caller asked for.
This report covers only https://rpc.hyperliquid.xyz/evm. I have not tested a locally running hl-node, so I cannot tell whether the behaviour originates in the node implementation or in the hosted RPC layer.
Environment
- Endpoint:
https://rpc.hyperliquid.xyz/evm (no key)
eth_chainId: 0x3e7 (999)
web3_clientVersion: hyperliquid evm Mainnet
- Measured 2026-07-28T14:46:02Z, head block 41,677,284 (
0x27bf1e4)
- Contract used: WHYPE
0x5555555555555555555555555555555555555555 (genesis predeploy), totalSupply() = 0x18160ddd
Controls first
RPC=https://rpc.hyperliquid.xyz/evm
W=0x5555555555555555555555555555555555555555
# positive — the endpoint serves real state
curl -s $RPC -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_getCode","params":["'$W'","latest"]}'
# → 2041 bytes of code
# negative — "0x" is an answer this endpoint can actually produce
curl -s $RPC -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_getCode","params":["0x00000000000000000000000000000000deadbe01","latest"]}'
# → "0x"
1. Same fixed block number, two calls, 30 seconds apart
This is the decisive test. Block 0x26cafa4 (head − 1,000,000), identical request both times:
curl -s $RPC -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_call","params":[{"to":"'$W'","data":"0x18160ddd"},"0x26cafa4"]}'
| read |
raw result |
| t = 0s |
0x00000000000000000000000000000000000000000004cda11f6182186624fe31 |
| t = 30s |
0x00000000000000000000000000000000000000000004cda16e395bf78a8a39b1 |
Δ ≈ +5.68 WHYPE for the same block number. The magnitude varies with wrap/unwrap traffic — a run 20 minutes earlier gave +39.46 — and the magnitude is not the point. A block number that yields two different answers is not being applied.
2. A block number above the current head
curl -s $RPC -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_getCode","params":["'$W'","0x3148864"]}'
0x3148864 is head + 10,000,000. Expected a JSON-RPC error; actual: no error, 2041 bytes, identical to latest.
3. eth_call at head − 1,000,000 matches latest byte for byte
Both latest and 0x26cafa4 return 0x00000000000000000000000000000000000000000004cda12f10131bba55fe31.
Suggestive rather than conclusive on its own — two points in time can coincide — but consistent with (1) and (2).
4. Blocks are retained; state is not
curl -s $RPC -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_getBlockByNumber","params":["0x26cafa4",false]}'
Returns the block, echoing "number":"0x26cafa4". So a caller can fetch a real historical block and a "state at that block" in the same script, and only the second one is wrong.
Why this matters
An explicit unsupported-block error is easy for callers to detect and handle. A valid-looking response from the wrong state is not: standard RPC libraries accept it, and downstream datasets can silently label current state as historical state.
I ran into this while pinning token supply measurements to fixed blocks. The values looked valid and stayed valid across two independent read paths, until repeated calls at the same block number returned different results.
Suggested fix
Reject unsupported non-latest block parameters with a JSON-RPC error, including block numbers above the current head — for example Resource not found, or whatever error the endpoint already uses for an invalid block height. The endpoint should not silently substitute latest state.
As a temporary mitigation the documentation could state explicitly that non-latest block parameters are currently ignored rather than rejected, though rejecting them would be substantially safer.
What I did not test
eth_getBalance, eth_getStorageAt, eth_getTransactionCount; a locally running hl-node; testnet. This is one hosted endpoint, two state methods, one point in time.
Summary
The public
/evmendpoint appears to ignore non-latest block numbers passed toeth_callandeth_getCode, evaluating the requests against current state instead. Unsupported historical block numbers — and block numbers above the current head — return well-formed results rather than a JSON-RPC error.The documentation already states that for these state methods "only the latest block is supported". This report is therefore not a request for archival state support. The issue is that an unsupported block parameter is silently accepted and answered from a different state than the caller asked for.
This report covers only
https://rpc.hyperliquid.xyz/evm. I have not tested a locally runninghl-node, so I cannot tell whether the behaviour originates in the node implementation or in the hosted RPC layer.Environment
https://rpc.hyperliquid.xyz/evm(no key)eth_chainId:0x3e7(999)web3_clientVersion:hyperliquid evm Mainnet0x27bf1e4)0x5555555555555555555555555555555555555555(genesis predeploy),totalSupply()=0x18160dddControls first
1. Same fixed block number, two calls, 30 seconds apart
This is the decisive test. Block
0x26cafa4(head − 1,000,000), identical request both times:result0x00000000000000000000000000000000000000000004cda11f6182186624fe310x00000000000000000000000000000000000000000004cda16e395bf78a8a39b1Δ ≈ +5.68 WHYPE for the same block number. The magnitude varies with wrap/unwrap traffic — a run 20 minutes earlier gave +39.46 — and the magnitude is not the point. A block number that yields two different answers is not being applied.
2. A block number above the current head
0x3148864is head + 10,000,000. Expected a JSON-RPC error; actual: no error, 2041 bytes, identical tolatest.3.
eth_callat head − 1,000,000 matcheslatestbyte for byteBoth
latestand0x26cafa4return0x00000000000000000000000000000000000000000004cda12f10131bba55fe31.Suggestive rather than conclusive on its own — two points in time can coincide — but consistent with (1) and (2).
4. Blocks are retained; state is not
Returns the block, echoing
"number":"0x26cafa4". So a caller can fetch a real historical block and a "state at that block" in the same script, and only the second one is wrong.Why this matters
An explicit unsupported-block error is easy for callers to detect and handle. A valid-looking response from the wrong state is not: standard RPC libraries accept it, and downstream datasets can silently label current state as historical state.
I ran into this while pinning token supply measurements to fixed blocks. The values looked valid and stayed valid across two independent read paths, until repeated calls at the same block number returned different results.
Suggested fix
Reject unsupported non-latest block parameters with a JSON-RPC error, including block numbers above the current head — for example
Resource not found, or whatever error the endpoint already uses for an invalid block height. The endpoint should not silently substitutelateststate.As a temporary mitigation the documentation could state explicitly that non-latest block parameters are currently ignored rather than rejected, though rejecting them would be substantially safer.
What I did not test
eth_getBalance,eth_getStorageAt,eth_getTransactionCount; a locally runninghl-node; testnet. This is one hosted endpoint, two state methods, one point in time.