How to Index Upgradeable Proxy Contract Events with Subgraphs
A practical guide to indexing upgradeable proxy contract events — handling ABI changes, data sources, and template patterns for UUPS and TransparentProxy contracts.
You are indexing a protocol that uses upgradeable proxy contracts — either OpenZeppelin's TransparentUpgradeableProxy or the UUPS pattern. The proxy address stays the same across upgrades, but the implementation ABI changes. Your subgraph starts producing no events after the upgrade, or worse, it decodes events incorrectly because it is using the pre-upgrade ABI.
Indexing upgradeable proxy contracts is one of the more common sources of production indexing failures, and the fix requires understanding exactly how the EVM routes calls and events through proxies.
How Proxy Contracts Affect Event Emission
In the proxy pattern, the proxy contract holds state and delegates all calls to an implementation contract via delegatecall. The key implication for indexing:
Events are emitted from the proxy address, not the implementation address. When a user calls deposit() on a UUPS proxy, the call is delegated to the implementation, but the Deposit event log is written with the proxy contract's address. This is why your frontend, which knows only the proxy address, sees the events.
This means you always index the proxy address — and that is what you specify in your subgraph.yaml. The complication is that the ABI used to decode the logs must match the implementation that was active at the time each event was emitted.
The Problem with a Single ABI After an Upgrade
If your subgraph uses only the post-upgrade ABI and you are re-indexing from the contract's genesis block, events emitted under the pre-upgrade implementation will fail to decode or will be decoded incorrectly.
The reverse is also true: if you deployed your subgraph before an upgrade and it still uses the old ABI, post-upgrade events will fail to decode.
The correct approach depends on whether the event signatures changed across the upgrade.
Case 1: Event Signatures Did Not Change
If the upgrade changed implementation logic but not the events emitted (no new events, no changed parameters), you do not have an ABI problem. The events decode the same way with both ABIs. Update your subgraph manifest to include any new events added in the upgrade, redeploy from the upgrade block, and verify.
This is the simplest case and applies to many protocol upgrades that refactor internals without touching the public event API.
How to Index Upgradeable Proxy Contract Events: ABI Changes
Case 2: Event Signatures Changed After the Upgrade
This is the hard case. You have two generations of events from the same proxy address, decoded by different ABIs.
The cleanest solution in The Graph's subgraph system uses dataSources with a block range:
Phase 1 data source covers blocks from deployment to the upgrade block, using the pre-upgrade ABI:
dataSources:
- kind: ethereum
name: ProtocolV1
network: mainnet
source:
address: "0xYourProxyAddress"
abi: ProtocolV1
startBlock: 15000000
mapping:
kind: ethereum/events
apiVersion: 0.0.7
language: wasm/assemblyscript
abis:
- name: ProtocolV1
file: ./abis/ProtocolV1.json
eventHandlers:
- event: DepositV1(indexed address,uint256)
handler: handleDepositV1
file: ./src/mappingV1.tsPhase 2 data source covers blocks from the upgrade block onward, using the post-upgrade ABI:
- kind: ethereum
name: ProtocolV2
network: mainnet
source:
address: "0xYourProxyAddress"
abi: ProtocolV2
startBlock: 17500000
mapping:
kind: ethereum/events
apiVersion: 0.0.7
language: wasm/assemblyscript
abis:
- name: ProtocolV2
file: ./abis/ProtocolV2.json
eventHandlers:
- event: DepositV2(indexed address,uint256,bytes32)
handler: handleDepositV2
file: ./src/mappingV2.tsBoth data sources write to the same entity schema. The handlers are separate functions in separate mapping files but operate on the same entities. The indexer applies the correct ABI to each block range.
Find the exact upgrade block on a block explorer by looking for the Upgraded event on the proxy address. Do not estimate — an off-by-one block error means events at the boundary are decoded with the wrong ABI.
Using Dynamic Data Sources for Factory-Pattern Proxies
Some protocols use factory contracts that deploy new proxy instances on demand — a lending pool factory or a DEX pair factory. Each deployed proxy is a separate contract instance at a different address, and you need to index all of them.
The Graph handles this with templates:
templates:
- kind: ethereum
name: Pool
network: mainnet
source:
abi: Pool
mapping:
kind: ethereum/events
apiVersion: 0.0.7
language: wasm/assemblyscript
abis:
- name: Pool
file: ./abis/Pool.json
eventHandlers:
- event: Swap(indexed address,int256,int256,uint160,uint128,int24)
handler: handleSwap
file: ./src/pool.tsIn your factory handler:
import { PoolCreated } from "../generated/Factory/Factory";
import { Pool } from "../generated/templates";
export function handlePoolCreated(event: PoolCreated): void {
Pool.create(event.params.pool);
// also initialize a Pool entity here
}Pool.create(address) tells the indexer to start tracking that address using the Pool template. All subsequent events from that address are handled by the template's event handlers.
Testing Upgrade Scenarios Before Deployment
Before deploying a subgraph that spans an upgrade, test the ABI boundary explicitly:
- Find a block just before the upgrade and verify that events in that block decode correctly with the V1 ABI
- Find a block just after the upgrade and verify that events decode correctly with the V2 ABI
- Query entities that should have been modified by both V1 and V2 events and confirm the values are consistent
The Matchstick testing framework can mock events with specific block numbers to test these boundary conditions without a full sync.
Backfilling After a Missed Upgrade
If your subgraph was live before an upgrade but was not updated to include the post-upgrade ABI, you have a gap. The cleanest fix is to redeploy the subgraph from a block before the contract deployment, with both data sources configured. The sync will be slower but the indexed data will be complete.
Attempting to "patch" the gap by querying the chain directly and writing to the indexed database is fragile — it bypasses the indexer's reorg protection and entity state management.
Proxy contract indexing is a detail that teams routinely underestimate until a production upgrade breaks their data layer. Getting it right requires careful ABI management and testing at the upgrade boundary. If your protocol uses upgradeable contracts and you need reliable indexing across versions, Clixo builds and maintains production subgraph infrastructure.