Skip to main content

Question it answers

“Give me every raw event log emitted by contract 0x…, in on-chain order.” Mirrors Moralis GET /{address}/logs.
This is the raw event log, topic0…topic3 plus the ABI-encoded data payload, keyed by the emitting contract. Unlike the decoded recipes (Token Transfers, Token Approvals, Swaps), it does not decode event names or arguments; you decode data in your application against the contract’s ABI. Use it to index any contract’s events, including custom or non-standard ones the decoded recipes don’t cover.

What you get

One row per log, keyed by the emitting contract:

On-chain ordering

A log’s canonical position is the tuple (block_number, transaction_index, log_index), block height, then the transaction’s position in the block, then the log’s index within the transaction. Every destination is keyed contract_address first, then that tuple, so “all logs for a contract, in order” is a sort-free range scan:
  • ClickHouse: ORDER BY (chain_id, contract_address, block_number, transaction_index, log_index, …); a prefix scan returns rows already ordered.
  • Postgres / MySQL: a composite index on (contract_address, block_number, transaction_index, log_index) answers the read with no filesort step.

Optional: group by event signature (topic0)

The primary key already leads with contract_address, so “all Transfer logs for contract X” prefix-scans to the contract and filters topic0 within it. On a high-volume contract that emits many event types, an opt-in (contract_address, topic0, …) structure makes that read a tight (contract, topic0) range that stays sort-free and skips the contract’s other event types. These are shipped commented-out in each schema (extra write cost + storage), uncomment to enable:

Source

Raw logs aren’t a decoded entity array, they live in the flattened block raw-passthrough array, where each block is expanded into items discriminated by itemType (log / transaction / …). The transform ARRAY JOINs block, keeps itemType = 'log', and lands one row per log. A log row carries no transaction hash, so tx_hash is recovered by joining each log to its sibling itemType = 'transaction' row on (block_number, transaction_index).

Destination

Logs are immutable append-only events, so the ClickHouse fact table is a plain collapsing table, each log is one row, and a chain reorganization cancels it via the companion log table. There’s no latest-wins logic; every log is a distinct row.

Full schema

The complete read table. Keep the columns you need; topic1…topic3 and data are nullable raw fields you decode in your app (see Schema & flexibility).
Postgres / MySQL mirror this as a flat contract_logs table with a composite index (contract_address, block_number, transaction_index, log_index) and the optional (contract_address, topic0, …) index commented out.

Example reads

All logs emitted by a contract, in on-chain order (ClickHouse):
Only a specific event type (0xddf2… = keccak("Transfer(address,address,uint256)")):

Modes

Shipped defaults: ClickHouse hybrid, Postgres / MySQL historical. For live/reorg-safe ingestion use ClickHouse, see the overview.
This recipe reads the wide raw block column (every log and transaction per block), so per-block bytes are higher than the decoded recipes. The transaction rows are read only to recover tx_hash, drop that join if you don’t need it.

EVM only

Contract Logs extracts EVM event logs (topic0…3 + data). Solana program logs are a different model and aren’t emitted into this array.

Fidelity gaps

Raw on-chain log primitives, emitter, all topics, raw data, block, tx hash, on-chain ordering, are fully covered. Fields with no onchain source are omitted: decoded_event (needs the contract ABI, decode in your app) and transaction receipt fields beyond tx_hash.

Migrating from the REST API

This recipe is the landing point for the two Blockchain API REST endpoints. Neither maps 1:1 to a single recipe: both responses are assembled from the raw block array, the flattened header / transaction / log rows this recipe already reads (see The block array). This recipe lands the log rows in a queryable table; the transaction and header fields come from the sibling rows in the same array. The subsections below cover each endpoint: what replaces it, the field mapping (exact = straight from the chain, decoded = you decode from the ABI or a typed recipe covers it, gap = not delivered today, off-chain = add yourself), and the gotchas specific to that endpoint.

GET /transaction/:transaction_hash/verbose

The decoded-transaction-by-hash endpoint. Replaced by reading the transaction’s logs from this recipe’s table and its raw fields from the transaction row of the block array, the “verbose” ABI decoding becomes a step you own.
This is not a 1:1 table swap, it’s a three-part workflow. Logs come from this recipe’s table, keyed by tx_hash. The transaction’s own fields (from, to, value, input, gas, receipt) come from the sibling itemType = 'transaction' row in the block array, the matching log rows give you its block_number. Decoding is yours: standard events are already typed rows in the decoded recipes (Token Transfers, NFT Transfers, Swaps by Pair); custom events and decoded_call decode from the contract ABI.
A transaction’s logs by hash (Postgres, same shape on ClickHouse with FINAL):
Gotchas
  • By-hash is a point lookup, but the shipped keys are contract-first. Add an index on tx_hash (or keep your own hash-to-block map), the shipped (contract_address, block_number, …) ordering doesn’t serve it.
  • Index the history you’ll look up. Arbitrary historical hashes need a historical/hybrid backfill across the range; recent-only lookups need only a recent window, see History & backfill.
  • internal_transactions is a gap today, it isn’t carried in the block array.
  • The *_entity / *_label / *_entity_logo fields are off-chain address labels; bring your own directory or drop them.

GET /block/:block_number_or_hash

The whole-block endpoint. Replaced by reading the block directly from the feed, a feed row’s native unit is a block, so the header, transactions, and logs arrive together in the block array and a by-number read is a direct positional read (a block’s position is its block number).
This recipe’s table alone does not reconstruct the response, it holds only the log rows. The header and transaction rows come from the block array itself (itemType = 'block' / 'transaction' / 'log'): read the block’s row from the feed, split the array by itemType, and join each log to its transaction on transaction_index. Keep this recipe running alongside to make logs queryable by block without re-reading whole blocks, and to resolve block_hash → block_number for by-hash reads.
Resolve a by-hash read to a position, then all of that block’s logs (ClickHouse):
Gotchas
  • By-hash resolves to a number first: the feed is positioned by block number; this recipe’s block_hash column (or your own index) does the resolve.
  • Raw log rows carry no tx hash: they join to their transaction on (block_number, transaction_index); this recipe already attaches tx_hash for you.
  • Busy blocks are large (a megabyte or two of JSON), read by position and process the array.
  • The REST responses return raw logs, matching this recipe exactly; if you also want decoded events, add the decoded recipes or decode from the ABI.
  • internal_transactions and address labels: same gaps as above.

Logs by Event Signature

The sibling, index one event type across all contracts.

Onchain Event Indexing

The use case these raw-log recipes power.