TriportRPC

Why eth_getLogs returns an empty array

Last updated:

At a glance

Symptom details
PropertyValue
What you get back"result": [] — a successful response, not an error
Default block rangefromBlock and toBlock both default to "latest"
Topic 0keccak-256 of the canonical event signature, e.g. Transfer(address,address,uint256)
Filterable parametersOnly indexed ones (topics 1–3); non-indexed values live in data
Proxy contractsLogs carry the proxy's address when code runs via delegatecall

What you see

You call eth_getLogs with an address and a topic for an event you know was emitted — you can see it in a block explorer — and the response is "result": []. There is no error object and no warning. The same filter may even work for one range and return nothing for another.

Why this happens

eth_getLogs returns every log that matches all of the filter at once: the block range, the address (or list of addresses) and each topic position. When any one of them does not match, the result is simply empty. Four mismatches cover most cases. First, fromBlock and toBlock default to "latest" when you omit them, so a filter without a range only searches the newest block. Second, topic 0 is the keccak-256 hash of the canonical event signature — the name and parameter types with no spaces, no parameter names and full type names (uint256, not uint) — and a hash computed from any other spelling matches nothing. Only indexed parameters appear as topics 1–3; a non-indexed parameter is inside the data field and cannot be filtered on. Third, with delegatecall the code at the target address is executed in the context — at the address — of the calling contract, so behind a proxy the log's address is the proxy, not the implementation contract you may have copied from the verified source. Fourth, logs are per chain: the same contract address on another EVM network, or on a testnet, has its own unrelated history. Separately, some node configurations keep a log search index for a limited number of recent blocks only (Geth's --history.logs defaults to about one year of blocks), so very old ranges depend on how the node was run.

What to do

  1. Set fromBlock and toBlock explicitly, as hex block numbers, around the block where you expect the event — never rely on the default.
  2. Recompute topic 0 from the canonical signature, for example keccak256("Transfer(address,address,uint256)"), and compare it with topics[0] of the event as shown in a block explorer or a transaction receipt.
  3. Filter only on indexed parameters in topics 1–3, left-padded to 32 bytes; use null for positions you do not want to constrain.
  4. Use the address that appears in the log itself (the proxy address for upgradeable contracts), which you can read from any receipt that contains the event.
  5. Confirm the chain with eth_chainId before debugging the filter, and narrow the range until one known event is returned — then widen it again in steps.

When this is not our problem

An empty array for a filter that matches nothing is standard eth_getLogs behavior on every Ethereum-compatible node, whoever operates it, Triport included: the filter decides the result, so the fix is in the filter. Block-range caps are a different failure: they return an explicit error, not an empty result.

FAQ

Why does eth_getLogs return an empty array without an error?
Because no log matched the whole filter. eth_getLogs only errors on an invalid request or a range the node refuses; a valid filter that matches nothing is a successful empty result.
What block range does eth_getLogs search by default?
Only the latest block: fromBlock and toBlock both default to "latest" in the Ethereum JSON-RPC specification, so always pass an explicit range.

Sources