Skip to main content

Overview

Declared eth_calls are a performance optimization technique that allows you to pre-declare contract calls in your subgraph manifest. Instead of making synchronous RPC calls during event processing, the indexer executes these calls ahead of time and caches the results in memory. This dramatically improves indexing performance by eliminating RPC latency from your handler execution.
Declared eth_calls can reduce indexing time by up to 10x for subgraphs that make frequent contract calls during event processing.

How it works

When you declare an eth_call in your subgraph manifest, the indexing engine follows this optimized workflow:
  1. Pre-execution: Before processing events, the indexer identifies all declared eth_calls for the current block
  2. Batch fetching: All declared calls are executed in parallel against the blockchain
  3. In-memory caching: Results are stored in an efficient in-memory cache
  4. Handler access: When your handler code binds to a contract and makes a call, it retrieves the cached result instantly instead of making an actual RPC call
This approach transforms slow, synchronous RPC calls into fast memory lookups, significantly reducing indexing time.

Requirements

Declared eth_calls require specVersion: 1.2.0 or higher in your subgraph manifest.
To use declared eth_calls, ensure your subgraph.yaml specifies the correct version:
subgraph.yaml
specVersion: 1.2.0
schema:
  file: ./schema.graphql

Implementation guide

Step 1: Identify candidate calls

Look for contract calls in your handlers that:
  • Are called frequently during event processing
  • Access the same contract method repeatedly
  • Contribute to slow indexing performance
Common examples include:
  • Getting token metadata (name, symbol, decimals)
  • Checking user balances or allowances
  • Reading contract configuration values
  • Fetching owner or admin addresses

Step 2: Declare the call in your manifest

Add an ethereum/call handler for each contract call you want to optimize. Here’s an example for tracking token prices from a Uniswap V3 pool:
subgraph.yaml
dataSources:
  - kind: ethereum/contract
    name: UniswapV3Pool
    network: mainnet
    source:
      address: "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"
      abi: UniswapV3Pool
      startBlock: 12369621
    mapping:
      kind: ethereum/events
      apiVersion: 0.0.7
      language: wasm/assemblyscript
      entities:
        - Swap
        - Pool
      abis:
        - name: UniswapV3Pool
          file: ./abis/UniswapV3Pool.json
        - name: ERC20
          file: ./abis/ERC20.json
      eventHandlers:
        - event: Swap(indexed address,indexed address,int256,int256,uint160,uint128,int24)
          handler: handleSwap
      # Declare eth_call to pre-fetch token0 address
      callHandlers:
        - function: token0()
          handler: handleToken0Call
        - function: token1()
          handler: handleToken1Call
      file: ./src/mapping.ts

Step 3: Create handler functions

Your handler functions should follow this pattern. Note that the handlers themselves don’t need to perform complex logic - they primarily serve to register the calls:
src/mapping.ts
import { ethereum, Address } from '@graphprotocol/graph-ts'
import { 
  Swap,
  UniswapV3Pool 
} from '../generated/UniswapV3Pool/UniswapV3Pool'
import { Pool, Token } from '../generated/schema'

// Declared call handlers
export function handleToken0Call(call: ethereum.Call): void {
  // This handler is called when the declared eth_call is executed
  // The result is automatically cached
}

export function handleToken1Call(call: ethereum.Call): void {
  // This handler is called when the declared eth_call is executed
  // The result is automatically cached
}

// Event handler that benefits from cached calls
export function handleSwap(event: Swap): void {
  let poolAddress = event.address
  let pool = Pool.load(poolAddress.toHex())
  
  if (!pool) {
    pool = new Pool(poolAddress.toHex())
    
    // Bind to contract and make calls
    // These will use the cached results from declared eth_calls
    let poolContract = UniswapV3Pool.bind(poolAddress)
    
    let token0Result = poolContract.try_token0()
    let token1Result = poolContract.try_token1()
    
    if (!token0Result.reverted) {
      pool.token0 = token0Result.value.toHex()
      loadOrCreateToken(token0Result.value)
    }
    
    if (!token1Result.reverted) {
      pool.token1 = token1Result.value.toHex()
      loadOrCreateToken(token1Result.value)
    }
  }
  
  // Process swap data
  pool.volume = pool.volume.plus(event.params.amount0)
  pool.save()
}

function loadOrCreateToken(address: Address): Token {
  let token = Token.load(address.toHex())
  
  if (!token) {
    token = new Token(address.toHex())
    // Additional token initialization
    token.save()
  }
  
  return token
}

Best practices

Each declared call adds overhead during the pre-execution phase. Only declare calls that your handlers will actually use during event processing.
Always use the try_ prefix when calling contract methods in your handlers. This ensures your subgraph continues indexing even if a call fails or reverts.
let result = contract.try_balanceOf(userAddress)
if (!result.reverted) {
  entity.balance = result.value
}
Declared eth_calls are most effective for calls that happen frequently. If a call only happens once per block or less, the overhead of declaring it may outweigh the benefits.
Use Goldsky’s dashboard to compare indexing speeds before and after implementing declared eth_calls. You should see significant improvements in blocks per second.

Performance impact

The performance benefits of declared eth_calls depend on several factors:
  • Call frequency: More frequent calls see greater benefits
  • RPC latency: Higher latency networks benefit more from caching
  • Handler complexity: Handlers with multiple contract calls see the most improvement
In practice, subgraphs with heavy RPC usage can see indexing speed improvements of 5-10x when properly implementing declared eth_calls.
After implementing declared eth_calls, you should see faster indexing speeds and reduced RPC load in your deployment metrics.

Example implementation

Check out our complete example implementation showing declared eth_calls in action with an ERC-20 token subgraph on the Taiko network.

Troubleshooting

Ensure your specVersion is set to 1.2.0 or higher. Older spec versions don’t support declared eth_calls and will fall back to standard synchronous calls.
Verify that your call handler function signature matches the declared function in your manifest. Mismatched signatures prevent the caching mechanism from working correctly.
Check that the contract ABI includes all the functions you’ve declared. Missing function definitions will cause indexing to fail.