Smart Contract Event Indexing with The Graph Protocol

Overview
Learn how to index Ethereum smart contract events using The Graph Protocol and query them in a React application. This guide provides a complete walkthrough from contract deployment to frontend integration.
What You'll Learn
The Graph Protocol: A decentralized protocol for indexing and querying blockchain data
Subgraphs: Open APIs that allow anyone to create or query indexed blockchain data
React Integration: How to fetch indexed data in your frontend applications
Step 1: Create the Smart Contract
We'll use a simple counter contract that emits events for demonstration:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.28;
contract Counter {
uint256 public counter = 0;
// Events to be indexed
event CounterIncrement(uint256 newValue, address indexed caller);
event CounterDecrement(uint256 newValue, address indexed caller);
event CounterReset(address indexed caller);
function increment() public {
counter++;
emit CounterIncrement(counter, msg.sender);
}
function decrement() public {
require(counter > 0, "Counter cannot go below zero");
counter--;
emit CounterDecrement(counter, msg.sender);
}
function reset() public {
counter = 0;
emit CounterReset(msg.sender);
}
function getCounter() public view returns (uint256) {
return counter;
}
}
Deploy the Contract
Compile and test your contract using Hardhat
Deploy to Sepolia testnet
Interact with the contract to generate some events:
npx hardhat run scripts/interactSepolia.js --network sepolia
Step 2: Set Up The Graph Subgraph
Prerequisites
Create an account at The Graph Studio
Create a new subgraph project
Install The Graph CLI:
npm install -g @graphprotocol/graph-cli@latest
# or
yarn global add @graphprotocol/graph-cli
Initialize Your Subgraph
Important: Save your contract's ABI as counter.json
before initialization (required if your contract isn't verified on Etherscan).
❯ graph init counter
✔ Network · Ethereum Sepolia Testnet · sepolia · https://sepolia.etherscan.io
✔ Source · Smart Contract · ethereum
✔ Subgraph slug · counter
✔ Directory to create the subgraph in · counter
✔ Contract address · 0xA1BE4e668B740D4E99d58654a92D66961b44b5Be
✔ Fetching ABI from Sourcify API...
✖ Failed to fetch ABI: Failed to fetch ABI: Error: NOTOK - Contract source code not verified
✔ Do you want to retry? (Y/n) · false
✔ Fetching start block from Contract API...
✖ Failed to fetch contract name: Name not found
✔ Do you want to retry? (Y/n) · false
✔ ABI file (path) · counter.json
✔ Start block · 8474094
✔ Contract name · Counter
✔ Index contract events as entities (Y/n) · true
Generate subgraph
Write subgraph to directory
✔ Create subgraph scaffold
✔ Initialize networks config
✔ Initialize subgraph repository
✔ Install dependencies with yarn
✔ Generate ABI and schema types with yarn codegen
✔ Add another contract? (y/N) · false
Subgraph counter created in counter
Next steps:
1. Run `graph auth` to authenticate with your deploy key.
2. Type `cd counter` to enter the subgraph.
3. Run `yarn deploy` to deploy the subgraph.
Make sure to visit the documentation on https://thegraph.com/docs/ for further information.
Follow the prompts:
Network: Ethereum Sepolia Testnet
Source: Smart Contract
Contract Address: Your deployed contract address
ABI File:
counter.json
(if contract isn't verified)Start Block: Block number when your contract was deployed
Contract Name: Counter
Index Events: Yes
Deploy Your Subgraph
After initialization, follow these steps:
# 1. Authenticate with your deploy key (found in Graph Studio dashboard)
graph auth YOUR_DEPLOY_KEY
# 2. Navigate to subgraph directory
cd counter
# 3. Deploy the subgraph
yarn deploy
Generated GraphQL Schema
The Graph automatically generates this schema based on your contract events:
type CounterDecrement @entity(immutable: true) {
id: Bytes!
newValue: BigInt! # uint256
caller: Bytes! # address
blockNumber: BigInt!
blockTimestamp: BigInt!
transactionHash: Bytes!
}
type CounterIncrement @entity(immutable: true) {
id: Bytes!
newValue: BigInt! # uint256
caller: Bytes! # address
blockNumber: BigInt!
blockTimestamp: BigInt!
transactionHash: Bytes!
}
type CounterReset @entity(immutable: true) {
id: Bytes!
caller: Bytes! # address
blockNumber: BigInt!
blockTimestamp: BigInt!
transactionHash: Bytes!
}
Step 3: Integrate with React App
Install Dependencies
npm install @apollo/client graphql
Create the Query Component
'use client'
import React, { useEffect, useState } from 'react';
import { ApolloClient, InMemoryCache, gql } from '@apollo/client';
// Apollo Client setup
const client = new ApolloClient({
uri: 'https://api.studio.thegraph.com/query/YOUR_SUBGRAPH_ID/counter/version/latest',
cache: new InMemoryCache(),
headers: {
Authorization: 'Bearer YOUR_API_KEY' // Get from Graph Studio API tab
}
});
// GraphQL query
const COUNTER_QUERY = gql`
query GetCounterData {
counterDecrements(first: 5, orderBy: blockTimestamp, orderDirection: desc) {
id
newValue
caller
blockNumber
blockTimestamp
}
counterIncrements(first: 5, orderBy: blockTimestamp, orderDirection: desc) {
id
newValue
caller
blockNumber
blockTimestamp
}
}
`;
export default function CounterSubgraphComponent() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchData() {
try {
setLoading(true);
const result = await client.query({
query: COUNTER_QUERY,
fetchPolicy: 'cache-first'
});
setData(result.data);
} catch (err) {
setError(err.message);
console.error('Error fetching data:', err);
} finally {
setLoading(false);
}
}
fetchData();
}, []);
if (loading) return <div>Loading indexed events...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div className="max-w-4xl mx-auto p-6">
<h1 className="text-2xl font-bold mb-6">Counter Contract Events</h1>
<div className="grid md:grid-cols-2 gap-6">
{/* Increments */}
<div className="bg-green-50 p-4 rounded-lg">
<h2 className="text-lg font-semibold mb-3 text-green-700">Recent Increments</h2>
{data?.counterIncrements?.map((event) => (
<div key={event.id} className="bg-white p-3 rounded mb-2">
<div><strong>New Value:</strong> {event.newValue}</div>
<div><strong>Caller:</strong> {event.caller}</div>
<div><strong>Block:</strong> {event.blockNumber}</div>
</div>
))}
</div>
{/* Decrements */}
<div className="bg-red-50 p-4 rounded-lg">
<h2 className="text-lg font-semibold mb-3 text-red-700">Recent Decrements</h2>
{data?.counterDecrements?.map((event) => (
<div key={event.id} className="bg-white p-3 rounded mb-2">
<div><strong>New Value:</strong> {event.newValue}</div>
<div><strong>Caller:</strong> {event.caller}</div>
<div><strong>Block:</strong> {event.blockNumber}</div>
</div>
))}
</div>
</div>
</div>
);
}
Usage in Next.js
// pages/index.js or app/page.js
import CounterSubgraphComponent from './components/CounterSubgraphComponent';
export default function Home() {
return (
<div>
<CounterSubgraphComponent />
</div>
);
}
Key Benefits
✅ Real-time Data: Events are indexed automatically as they occur
✅ Fast Queries: Pre-indexed data means lightning-fast GraphQL queries
✅ Decentralized: No single point of failure
✅ Developer Friendly: Familiar GraphQL syntax
✅ Cost Effective: Query multiple events in a single request
Quick Tips
API Keys: Generate your API key from the Graph Studio dashboard
Query Optimization: Use
first
,orderBy
, andorderDirection
parameters for better performanceError Handling: Always implement proper error handling for network requests
Caching: Apollo Client provides built-in caching for better UX
Complete Example
The full working React application with all components is available at: subgraph-frontend repository
Complete Program can be found at counter-subgraph
Subscribe to my newsletter
Read articles from Arjun C directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Arjun C
Arjun C
i read and write often