What Can Subgraphs Actually Index? Events, State, Contract Calls, and Traces Explained

Subgraphs can index events, read contract state, and handle selected function calls. Learn their limitations and when you need RPC, traces, or custom indexing.

What Can Subgraphs Actually Index? Events, State, Contract Calls, and Traces Explained

Subgraphs primarily index smart contract events, but they can also read contract state, track certain function calls, and process blocks.

The main limitation is how the data is made available. A subgraph can easily index a Transfer or Swap event. Tracking a function that emits no events requires a different approach. Getting complete execution traces or pending transactions requires additional infrastructure.

Here's what standard EVM subgraphs support and where the limitations are.

What data can a subgraph index?

DataSupported?Method
Smart contract eventsYesEvent handlers
Transaction metadataYes, for matched events and callsevent.transaction
Contract state variablesYes, through readable functionseth_call
Executed contract function callsDepends on chain supportCall handlers
Block metadataYesBlock handlers
Historical application stateYes, with retention limitationsIndexed entities and historical queries
Complete transaction tracesNo, not through standard handlersTracing infrastructure
All transactions and native ETH transfersNot automaticallyTransaction indexing pipeline
Pending transactionsNoMempool feed
IPFS and Arweave filesYes, with restrictionsFile Data Sources

#1: Events - the most straightforward data to index

Event handlers process logs emitted by smart contracts.

For example, an ERC-20 token emits a Transfer event:

event Transfer(
    address indexed from,
    address indexed to,
    uint256 value
);

Your subgraph.yaml defines which events to process:

eventHandlers:
  - event: Transfer(indexed address,indexed address,uint256)
    handler: handleTransfer

Each matching event triggers handleTransfer. The mapping can save a transfer entity, update balances, or maintain aggregated metrics.

Alongside the event parameters, the handler can access transaction and block data, including the transaction hash, sender, input data, block number, and timestamp. Transaction receipts are also available when explicitly enabled in the manifest.

This covers most common subgraph use cases: token transfers, DEX swaps, liquidity changes, lending activity, and NFT transactions.

The main requirement is that the contracts emit enough information to reconstruct the data you need. An event may indicate that something changed without including the resulting contract state.

#2: Contract state - reading data with eth_call

Sometimes an event doesn't include the information you need.

Suppose you're indexing token transfers and want to record the token's total supply. You can read the contract's totalSupply() function from a mapping:

let token = ERC20.bind(event.address);
let result = token.try_totalSupply();

if (!result.reverted) {
  let supply = result.value;
  // Store the supply in an entity
}

Graph Node executes the contract read using the historical block being indexed.

There are two important limitations.

Contract calls read block-level state. They don't reconstruct the state immediately after each individual event. If several relevant transactions occur in one block, calls made while processing those events can return the same end-of-block value.

Historical reads require appropriate RPC infrastructure. Graph Node requires an archive-capable endpoint with EIP-1898 support for indexing-time eth_call operations. These reads can also slow down indexing, particularly when mappings repeatedly call contracts.

If the required data can be calculated from events, maintaining it in subgraph entities is often more efficient.

#3: Call handlers- tracking functions without events

An eth_call from a mapping reads contract state. A call handler does something different: it detects when a specified function is executed in an on-chain transaction.

Suppose a contract has a deposit function:

function deposit(uint256 amount) external;

If it doesn't emit an event containing the deposit information, you can configure a call handler:

callHandlers:
  - function: deposit(uint256)
    handler: handleDeposit

The handler receives the function's decoded inputs and outputs. Supported nested calls can also trigger the handler, including calls routed through other contracts.

The limitation is RPC trace support.

Graph Node's call handlers require trace_filter support. Many EVM networks and RPC providers don't expose the tracing interface needed for this feature.

Support for debug_traceTransaction alone doesn't guarantee compatibility.

Call handlers can also be more expensive to process than events. Check network support before designing a subgraph around them.

#4: Block handlers - processing data at block intervals

Block handlers run mappings as blocks are indexed.

You can run a handler on every block or configure it to run at specific intervals.

For example, a polling handler that executes every 10 blocks:

blockHandlers:
  - handler: handleBlock
    filter:
      kind: polling
      every: 10

Polling handlers require specVersion 0.0.8 or later and a compatible data source configuration.

This is useful for sampling contract state periodically.

For example, if a vault's exchange rate changes without emitting an event for every update, a block handler can read the exchange rate and store periodic snapshots.

Block handlers receive block metadata. They don't automatically expose every transaction, internal call, or storage change within that block.

#5: Execution traces - where subgraphs have limits

Execution traces provide details about what happened during a transaction, including nested contract calls and, depending on the tracing method, internal value transfers and state changes.

Consider a transaction that interacts with a DEX router. It might execute calls across several pools and tokens before completing.

A subgraph can index the events those contracts emit. If call handlers are supported, it can also capture selected function executions.

However, standard subgraph handlers don't provide complete execution traces.

If you need to analyze all internal transfers, inspect call trees, investigate reverted execution, or track detailed state changes, you'll generally need tracing infrastructure.

Common options include:

  • debug_traceTransaction or other supported tracing RPC methods
  • Substreams with EVM trace-level data
  • Custom indexing pipelines that process execution traces

Call handlers are useful for targeted function-level indexing. They aren't a replacement for a complete trace dataset.

#6: Can subgraphs index transactions without events?

Only through other supported triggers, and with limitations.

When an event handler runs, it can access metadata about the transaction containing that event. But if no relevant event occurs, that event handler won't run.

For example, a normal ETH transfer between two externally owned accounts doesn't emit a smart contract event.

A standard event-based subgraph therefore won't automatically capture it.

Call handlers can help with certain contract interactions, but they don't provide a complete chain-wide transaction history.

For applications that need every transaction, native transfer, or account-level transaction history, a general-purpose transaction indexer is usually a better fit.

Pending transactions are another separate case. Subgraphs process indexed blocks, so mempool monitoring requires a different data source.

#7: Can subgraphs query historical contract state?

Yes, but it's important to distinguish two things.

Indexed historical state: A subgraph maintains entities as it processes blockchain activity. Depending on the indexing platform and pruning configuration, GraphQL queries can retrieve earlier versions of those entities.

Historical on-chain state: A mapping can call a contract at the historical block being indexed, provided the RPC supports the required state reads.

These are different datasets.

A subgraph's historical entities contain whatever its mappings previously stored. They don't automatically expose every historical storage value from the underlying contracts.

If older entity versions have been pruned, historical GraphQL queries may no longer be able to retrieve them.

For long-term analytics, storing explicit historical snapshots can be useful when you don't want to depend entirely on retained entity versions.

#8: Can subgraphs index off-chain data?

Subgraphs also support File Data Sources for content-addressed files stored on IPFS and Arweave.

A common example is NFT metadata. The contract provides an IPFS content identifier, and the subgraph uses a File Data Source to retrieve and index the associated metadata.

File Data Sources have additional restrictions. Their handlers operate on separate entities and cannot freely access or update entities created by on-chain handlers.

Standard mappings also don't support arbitrary HTTP requests to external services. If your application needs exchange prices, proprietary APIs, or other external datasets, those usually belong in a separate data pipeline.

When should you use a subgraph instead of RPC or a custom indexer?

For most protocol-specific applications, start by inspecting the contract events.

A Uniswap subgraph, for example, can use swap events to calculate trading volume, liquidity events to track LP activity, and pool state reads where additional information is required.

That will cover a large portion of a typical DEX dashboard's data requirements.

Use RPC directly when you need an immediate contract read or access to data that doesn't need to be persistently indexed.

Use tracing infrastructure when execution details matter.

Use a broader transaction indexing pipeline when you need complete chain activity rather than data from a particular set of contracts.

These approaches can also be combined. Many applications use subgraphs for historical and application-specific data while keeping RPC access for direct contract reads and transaction execution.

Frequently asked questions

Can subgraphs index data without events?

Yes. Call handlers can capture selected function executions on networks with compatible tracing infrastructure. Block handlers can also read contract state at block intervals. Neither provides a complete record of every state change automatically.

Can subgraphs read smart contract storage?

They can read values exposed through compatible contract functions and supported state-reading APIs. Arbitrary storage-slot reads and storage-diff analysis generally require additional RPC or tracing tools.

Do subgraphs require archive nodes?

Event-only subgraphs can index historical logs without archive state if the RPC provides the necessary log and block history. Subgraphs that perform historical eth_call operations generally require archive-capable RPC infrastructure.

Can one subgraph index multiple contracts or chains?

A standard EVM subgraph can index multiple contracts on one network, including contracts discovered dynamically through factory events. Indexing several networks typically requires separate subgraph deployments or another data aggregation layer.

Conclusion

For any applications building on-chain, smart contract events are the best starting point. They're efficient to process and usually contain enough information to build useful application-specific datasets.

When events are insufficient, subgraphs can read contract state or use call and block handlers, but those features require RPC and indexing overhead.

Complete execution traces, pending transactions, and chain-wide transaction histories generally need separate infrastructure.

The optimal method is to identify where the required data comes from before building the subgraph. That determines which handlers you need, what RPC capabilities are required, and whether another indexing pipeline belongs in the architecture.

About Ormi

Ormi is the next-generation data layer for Web3, purpose-built for real-time, high-throughput applications like DeFi, gaming, wallets, and on-chain infrastructure. Its hybrid architecture ensures sub-30ms latency and up to 4,000 RPS for live subgraph indexing.

With 99.9% uptime and deployments across ecosystems representing $50B+ in TVL and $100B+ in annual transaction volume, Ormi is trusted to power the most demanding production environments without throttling or delay.