Skip to main content
logs and transactions are each a list of filter objects.
  • Within one object, every field you set must match (AND).
  • A row is returned if it matches any object in the list (OR).
  • Within one field, the row’s value must be one of the listed values (IN).
  • An object with no fields set ({}) matches every row of that type in the range.

Log filters

Examples

Every Transfer and Approval of two tokens:
Every Transfer of any token to one wallet (the recipient is topic2):
Transfers from or to a wallet: two objects, because a row matches if either one does:
Topics are 32 bytes. An address in a topic is left-padded with 12 zero bytes: 0x000000000000000000000000 + the 40 hex characters of the address. Passing a 20-byte address as a topic is a 400.
Event signature hashes are keccak256 of the event signature, e.g. keccak256("Transfer(address,address,uint256)"). With viem: toEventSelector("Transfer(address,address,uint256)"); with ethers: ethers.id("Transfer(address,address,uint256)"); with web3.py: Web3.keccak(text="Transfer(address,address,uint256)").hex().

Transaction filters

Examples

Every transfer(address,uint256) call (selector 0xa9059cbb) to USDC:
Everything a wallet sent or received:
Only the reverted calls to a contract:
Every blob transaction, and every legacy transaction to a contract:

How status works

status is applied to the matches, not used to find them: the server finds the transactions matching the other keys, then drops the ones with the wrong status before counting or sending anything. Two consequences:
  • Every transaction object in a request must carry the same status, or none: [{"status": 1}, {"sighash": [...]}] is a 400.
  • A filter with only status reads the status of every transaction in the range. Give it a range or other keys.
Transactions from before Byzantium (Ethereum block 4,370,000) have no receipt status and never match a status filter.

How type works

type is also checked on the matches rather than looked up in an index, but it works like any other key of its object: it is ANDed with that object’s other keys, and different objects can ask for different types, or none, as in the example above. It doesn’t apply to the transactions joinTransactions adds. A filter with only type reads the type of every transaction in the range. That costs one byte per transaction, so it is fast (about 50 ms for 50,000 Robinhood Chain blocks), but other keys narrow it further.

Value rules

  • Hex values are 0x-prefixed, case-insensitive, and must be exactly the field’s width: 40 hex characters for addresses, 64 for topics, 8 for sighashes. A wrong length is a 400 naming the offending value (logs[0].address[0]: …).
  • Duplicates are fine; the server removes them. Order doesn’t matter.
  • An address list and an addressSet in the same object are combined (union), not exclusive. The same goes for every X / XSet pair.
  • Up to 256 filter objects per row type and 100,000 values per field. For larger lists, use a set.

What you can’t filter on

Only the fields above can be filtered on. Everything else (value, gasUsed, input beyond the first four bytes, block miner, log data, …) can be returned but not filtered on. Filter as narrowly as these fields allow, then filter the rest client-side. Blocks can’t be filtered at all, beyond the range. You get every block in the range (no logs/transactions filter) or exactly the blocks that contain a match (with one). See Blocks.