> ## Documentation Index
> Fetch the complete documentation index at: https://docs.goldsky.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Use declared eth_calls to increase indexing performance

> Improve your subgraph performance by declaring eth_calls

## 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.

<Tip>
  Declared eth\_calls can reduce indexing time by up to 10x for subgraphs that make frequent contract calls during event processing.
</Tip>

## 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 event 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

<Warning>
  Declared eth\_calls require `specVersion: 1.2.0` or higher in your subgraph manifest.
</Warning>

To use declared eth\_calls, ensure your `subgraph.yaml` specifies the correct version:

```yaml subgraph.yaml theme={null}
specVersion: 1.2.0
schema:
  file: ./schema.graphql
```

## Syntax

Declared eth\_calls are added as a `calls` property on an event handler entry in your subgraph manifest. The `calls` property is an object where each key is an arbitrary name and the value describes the eth\_call to make.

The general syntax is:

```yaml theme={null}
eventHandlers:
  - event: YourEvent(...)
    handler: handleYourEvent
    calls:
      callName: ABI[address].method(arg1, arg2, ...)
```

Breaking this down:

* **`calls:`** — an object of key-value pairs nested under the event handler that triggers the eth\_calls.
* **`callName:`** — an arbitrary name you choose for the declared call. Use something self-documenting, e.g. if your eth\_call is `getLiquidity`, name it `getLiquidity: ...`.
* **`ABI`** — the name of the ABI as defined in the `abis` section of your manifest. This corresponds to how you use `ABI.bind(address)` in your mapping code. You can search your source mapping for `.bind(` calls to identify where declared eth\_calls can be applied.
* **`[address]`** — the address passed to the ABI when binding. This can be `event.address`, `event.params.<name>`, or a constant hex address.
* **`.method(...)`** — the contract method to call. Use the actual method name even if your mapping uses `try_` syntax (e.g. if your code calls `try_getSymbol`, declare it as `.getSymbol()`).
* **`(args)`** — the arguments passed to the contract method. These can be `event.address`, `event.params.<name>`, or constant values.

<Warning>
  Constants like hex addresses are embedded without quotes. Make sure to follow the pattern shown in the examples below.
</Warning>

## Implementation guide

### Step 1: Identify candidate calls

Look for contract calls in your event 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 pool or pair addresses from a factory

### Step 2: Declare the calls in your manifest

Add a `calls` property to the relevant event handler. Here's an example for a Uniswap V3 pool that pre-fetches `token0` and `token1` addresses on every swap:

```yaml subgraph.yaml theme={null}
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
          calls:
            # Pre-fetch token0 and token1 addresses from the pool contract
            token0: UniswapV3Pool[event.address].token0()
            token1: UniswapV3Pool[event.address].token1()
      file: ./src/mapping.ts
```

### Step 3: Use cached results in your event handler

No separate handler functions are needed for declared eth\_calls. The results are automatically cached and returned when your event handler binds to the contract and calls the same method. Your existing mapping code works as-is:

```typescript src/mapping.ts theme={null}
import { Address } from '@graphprotocol/graph-ts'
import {
  Swap,
  UniswapV3Pool
} from '../generated/UniswapV3Pool/UniswapV3Pool'
import { Pool, Token } from '../generated/schema'

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()
    }

    if (!token1Result.reverted) {
      pool.token1 = token1Result.value.toHex()
    }
  }

  // Process swap data
  pool.volume = pool.volume.plus(event.params.amount0)
  pool.save()
}
```

Note that even though the mapping uses `try_token0`, the declared call uses just `token0` — omit the `try_` prefix in the manifest.

## Address and argument patterns

The `calls` syntax supports several ways to specify the contract address and method arguments. You can use `event.address`, `event.params.<name>`, or constant hex addresses in any combination:

```yaml theme={null}
eventHandlers:
  - event: Created(address)
    handler: handleCreated
    calls:
      # Use event.address as the contract address, event param as argument
      fromEventAddr: Factory[event.address].get(event.params.address)

      # Use an event param as the contract address
      fromParam: Factory[event.params.address].get(event.params.address)

      # Use a constant address, pass event.address as argument
      fromConstant: Factory[0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF].get(event.address)

      # Both the address and argument are constants
      allConstants: Factory[0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF].get(0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF)
```

Constants are embedded without quotes — no string wrapping needed.

## Best practices

<AccordionGroup>
  <Accordion title="Only declare calls that are actually needed">
    Each declared call adds overhead during the pre-execution phase. Only declare calls that your event handlers will actually use during event processing.
  </Accordion>

  <Accordion title="Use try_ patterns for safety">
    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. The declared call in the manifest always uses the base method name (without `try_`).

    ```typescript theme={null}
    let result = contract.try_balanceOf(userAddress)
    if (!result.reverted) {
      entity.balance = result.value
    }
    ```
  </Accordion>

  <Accordion title="Consider call frequency vs. overhead">
    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.
  </Accordion>

  <Accordion title="Match the call signature exactly">
    The address and arguments in your declared call must match what your event handler actually passes when binding and calling the contract method. If they don't match, the cache won't be used and a standard RPC call will be made instead.
  </Accordion>
</AccordionGroup>

## 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.

<Check>
  After implementing declared eth\_calls, you should see faster indexing speeds and reduced RPC load in your deployment metrics.
</Check>

## Example implementation

Check out our [complete example implementation](https://github.com/goldsky-io/documentation-examples/tree/main/goldsky-subgraphs/declared-eth-call-subgraph) showing declared eth\_calls in action with an ERC-20 token subgraph on the Taiko network.

## Troubleshooting

<AccordionGroup>
  <Accordion title="Calls still seem slow after declaring">
    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.
  </Accordion>

  <Accordion title="Cache not being used">
    Verify that the address and arguments in your declared call match exactly what your event handler passes. For example, if you declare `Factory[event.address].get(event.params.address)` but your handler binds to a different address, the cached result won't be used.
  </Accordion>

  <Accordion title="Indexing errors after adding declared calls">
    Check that the contract ABI includes all the functions you've declared. Missing function definitions will cause indexing to fail.
  </Accordion>
</AccordionGroup>
