indexing
How to read and query onchain data — events, The Graph, indexing patterns. Why you cannot just loop through blocks, and what to use instead.
Best use case
indexing is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
How to read and query onchain data — events, The Graph, indexing patterns. Why you cannot just loop through blocks, and what to use instead.
Teams using indexing should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/indexing/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How indexing Compares
| Feature / Agent | indexing | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
How to read and query onchain data — events, The Graph, indexing patterns. Why you cannot just loop through blocks, and what to use instead.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Onchain Data & Indexing
## What You Probably Got Wrong
**You try to query historical state via RPC calls.** You can't cheaply read past state. `eth_call` reads current state. Reading state at a historical block requires an archive node (expensive, slow). For historical data, you need an indexer.
**You loop through blocks looking for events.** Scanning millions of blocks with `eth_getLogs` is O(n) — it will timeout, get rate-limited, or cost a fortune in RPC credits. Use an indexer that has already processed every block.
**You store query results onchain.** Leaderboards, activity feeds, analytics — these belong offchain. Compute offchain, index events offchain. If you need an onchain commitment, store a hash.
**You don't know about The Graph.** The Graph turns your contract's events into a queryable GraphQL API. It's how every serious dApp reads historical data. Etherscan uses indexers. Uniswap uses indexers. So should you.
**You treat events as optional.** Events are THE primary way to read historical onchain activity. If your contract doesn't emit events, nobody can build a frontend, dashboard, or analytics on top of it. Design contracts event-first.
---
## Events Are Your API
Solidity events are cheap to emit (~375 gas base + 375 per indexed topic + 8 gas per byte of data) and free to read offchain. They're stored in transaction receipts, not in contract storage, so they don't cost storage gas.
### Design Contracts Event-First
Every state change should emit an event. This isn't just good practice — it's how your frontend, indexer, and block explorer know what happened.
```solidity
// ✅ Good — every action emits a queryable event
contract Marketplace {
event Listed(
uint256 indexed listingId,
address indexed seller,
address indexed tokenContract,
uint256 tokenId,
uint256 price
);
event Sold(uint256 indexed listingId, address indexed buyer, uint256 price);
event Cancelled(uint256 indexed listingId);
function list(address token, uint256 tokenId, uint256 price) external {
uint256 id = nextListingId++;
listings[id] = Listing(msg.sender, token, tokenId, price, true);
emit Listed(id, msg.sender, token, tokenId, price);
}
function buy(uint256 listingId) external payable {
// ... transfer logic ...
emit Sold(listingId, msg.sender, msg.value);
}
}
```
**Index the fields you'll filter by.** You get 3 indexed topics per event. Use them for addresses and IDs that you'll query — `seller`, `buyer`, `tokenContract`, `listingId`. Don't index large values or values you won't filter on.
### Reading Events Directly (Small Scale)
For recent events or low-volume contracts, you can read events directly via RPC:
```typescript
import { createPublicClient, http, parseAbiItem } from 'viem';
const client = createPublicClient({
chain: mainnet,
transport: http(),
});
// Get recent events (last 1000 blocks)
const logs = await client.getLogs({
address: '0xYourContract',
event: parseAbiItem('event Sold(uint256 indexed listingId, address indexed buyer, uint256 price)'),
fromBlock: currentBlock - 1000n,
toBlock: 'latest',
});
```
**This works for:** Last few thousand blocks, low-volume contracts, real-time monitoring.
**This breaks for:** Historical queries, high-volume contracts, anything scanning more than ~10K blocks.
---
## The Graph (Subgraphs)
The Graph is a decentralized indexing protocol. You define how to process events, deploy a subgraph, and get a GraphQL API that serves historical data instantly.
### When to Use The Graph
- Any dApp that needs historical data (activity feeds, transaction history)
- Leaderboards, rankings, analytics dashboards
- NFT collection browsers (who owns what, transfer history)
- DeFi dashboards (position history, PnL tracking)
- Any query that would require scanning more than ~10K blocks
### How It Works
1. **Define a schema** — what entities you want to query
2. **Write mappings** — TypeScript handlers that process events into entities
3. **Deploy** — subgraph indexes all historical events and stays synced
### Example: NFT Collection Subgraph
**schema.graphql:**
```graphql
type Token @entity {
id: ID!
tokenId: BigInt!
owner: Bytes!
mintedAt: BigInt!
transfers: [Transfer!]! @derivedFrom(field: "token")
}
type Transfer @entity {
id: ID!
token: Token!
from: Bytes!
to: Bytes!
timestamp: BigInt!
blockNumber: BigInt!
}
```
**mapping.ts:**
```typescript
import { Transfer as TransferEvent } from './generated/MyNFT/MyNFT';
import { Token, Transfer } from './generated/schema';
export function handleTransfer(event: TransferEvent): void {
let tokenId = event.params.tokenId.toString();
// Create or update token entity
let token = Token.load(tokenId);
if (token == null) {
token = new Token(tokenId);
token.tokenId = event.params.tokenId;
token.mintedAt = event.block.timestamp;
}
token.owner = event.params.to;
token.save();
// Create transfer record
let transfer = new Transfer(
event.transaction.hash.toHex() + '-' + event.logIndex.toString()
);
transfer.token = tokenId;
transfer.from = event.params.from;
transfer.to = event.params.to;
transfer.timestamp = event.block.timestamp;
transfer.blockNumber = event.block.number;
transfer.save();
}
```
**Query the subgraph:**
```graphql
{
tokens(where: { owner: "0xAlice..." }, first: 100) {
tokenId
mintedAt
transfers(orderBy: timestamp, orderDirection: desc, first: 5) {
from
to
timestamp
}
}
}
```
### Deploying a Subgraph
```bash
# Install
npm install -g @graphprotocol/graph-cli
# Initialize from contract ABI
graph init --studio my-subgraph
# Generate types from schema
graph codegen
# Build
graph build
# Deploy to Subgraph Studio
graph deploy --studio my-subgraph
```
**Subgraph Studio** (studio.thegraph.com) — development and testing environment. Free during development. Publish to the decentralized network for production.
---
## Alternative Indexing Solutions
| Solution | Best for | Tradeoffs |
|----------|----------|-----------|
| **The Graph** | Production dApp backends, decentralized | GraphQL API, requires subgraph development |
| **Dune Analytics** | Dashboards, analytics, ad-hoc queries | SQL interface, great visualization, not for app backends |
| **Alchemy/QuickNode APIs** | Quick token/NFT queries | `getTokenBalances`, `getNFTs`, `getAssetTransfers` — fast but centralized |
| **Etherscan/Blockscout APIs** | Simple event log queries | Rate-limited, not for high-volume |
| **Ponder** | TypeScript-first indexing | Local-first, simpler than The Graph for single-app use |
| **Direct RPC** | Real-time current state only | Only for current state reads, not historical |
### Dune Analytics
Write SQL queries over decoded onchain data. Best for analytics and dashboards, not for app backends.
```sql
-- Top 10 buyers on your marketplace (last 30 days)
SELECT
buyer,
COUNT(*) as purchases,
SUM(price / 1e18) as total_eth_spent
FROM mycontract_ethereum.Marketplace_evt_Sold
WHERE evt_block_time > NOW() - INTERVAL '30' DAY
GROUP BY buyer
ORDER BY total_eth_spent DESC
LIMIT 10
```
### Enhanced Provider APIs
For common queries, provider APIs are faster than building a subgraph:
```typescript
// Alchemy: get all tokens held by an address
const balances = await alchemy.core.getTokenBalances(address);
// Alchemy: get all NFTs owned by an address
const nfts = await alchemy.nft.getNftsForOwner(address);
// Alchemy: get transfer history
const transfers = await alchemy.core.getAssetTransfers({
fromAddress: address,
category: ['erc20', 'erc721'],
});
```
---
## Reading Current State (Not Historical)
For current balances, allowances, and contract state, direct RPC reads are fine. No indexer needed.
### Single Reads
```typescript
import { createPublicClient, http } from 'viem';
const client = createPublicClient({ chain: mainnet, transport: http() });
// Read current balance
const balance = await client.readContract({
address: tokenAddress,
abi: erc20Abi,
functionName: 'balanceOf',
args: [userAddress],
});
```
### Batch Reads with Multicall
For multiple reads in one RPC call, use Multicall3 (deployed at the same address on every chain):
```typescript
// Multicall3: 0xcA11bde05977b3631167028862bE2a173976CA11
// Same address on Ethereum, Arbitrum, Optimism, Base, Polygon, and 50+ chains
const results = await client.multicall({
contracts: [
{ address: tokenA, abi: erc20Abi, functionName: 'balanceOf', args: [user] },
{ address: tokenB, abi: erc20Abi, functionName: 'balanceOf', args: [user] },
{ address: tokenC, abi: erc20Abi, functionName: 'balanceOf', args: [user] },
{ address: vault, abi: vaultAbi, functionName: 'totalAssets' },
],
});
// One RPC call instead of four
```
### Real-Time Updates
For live updates, subscribe to new events via WebSocket:
```typescript
import { createPublicClient, webSocket } from 'viem';
const client = createPublicClient({
chain: mainnet,
transport: webSocket('wss://eth-mainnet.g.alchemy.com/v2/YOUR_KEY'),
});
// Watch for new sales in real-time
const unwatch = client.watchContractEvent({
address: marketplaceAddress,
abi: marketplaceAbi,
eventName: 'Sold',
onLogs: (logs) => {
for (const log of logs) {
console.log(`Sale: listing ${log.args.listingId} for ${log.args.price}`);
}
},
});
```
---
## Common Patterns
| What you need | How to get it |
|---------------|---------------|
| Activity feed for a dApp | Emit events → index with The Graph → query via GraphQL |
| Token balances for a user | Alchemy `getTokenBalances` or Multicall |
| NFT collection browser | The Graph subgraph or Alchemy `getNftsForContract` |
| Price history | Dune Analytics or DEX subgraphs |
| Real-time new events | WebSocket subscription via viem |
| Historical transaction list | The Graph or Alchemy `getAssetTransfers` |
| Dashboard / analytics | Dune Analytics (SQL + charts) |
| Protocol TVL tracking | DeFiLlama API or custom subgraph |Related Skills
why-ethereum
Why build on Ethereum specifically. The AI agent angle — ERC-8004, x402, composability, permissionless deployment. Use when a user asks "should I build on Ethereum?", "why blockchain?", or when an agent needs to recommend a chain for a project.
wallets
How to create, manage, and use Ethereum wallets. Covers EOAs, smart contract wallets, multisig (Safe), and account abstraction. Use this skill whenever you are sending transactions, signing messages, or managing funds. Includes guardrails for safe key handling.
tools
Current Ethereum development tools, frameworks, libraries, RPCs, and block explorers. What actually works today for building on Ethereum. Includes tool discovery for AI agents — MCPs, abi.ninja, Foundry, Scaffold-ETH 2, Hardhat, and more. Use when setting up a dev environment, choosing tools, or when an agent needs to discover what's available.
testing
Smart contract testing with Foundry — unit tests, fuzz testing, fork testing, invariant testing. Use when writing tests for a smart contract.
standards
Ethereum token and protocol standards — ERC-20, ERC-721, ERC-1155, ERC-4337, ERC-8004, and newer standards. When to use each, how they work, key interfaces. Use when building tokens, NFTs, or choosing the right standard for a project.
ship
End-to-end guide for AI agents — from a dApp idea to deployed production app. Fetch this FIRST, it routes you through all other skills.
security
Solidity security patterns, common vulnerabilities, and pre-deploy audit checklist. The specific code patterns that prevent real losses — not just warnings, but defensive implementations. Use before deploying any contract, when reviewing code, or when building anything that holds or moves value.
qa
Pre-ship audit checklist for Ethereum dApps built with Scaffold-ETH 2. Give this to a separate reviewer agent (or fresh context) AFTER the build is complete. Use this skill whenever you are finalizing a dApp built with Scaffold-ETH 2.
protocol
How Ethereum evolves — EIP lifecycle, fork process, where decisions happen, and how to track upcoming changes. Use when your human asks about upcoming features, when building for future protocol capabilities, or when they want to propose a change. Also use when YOU need to know if a feature exists yet or when it's coming.
orchestration
How an AI agent plans, builds, and deploys a complete Ethereum dApp. The three-phase build system for Scaffold-ETH 2 projects. Use when building a full application on Ethereum — from contracts to frontend to production deployment on IPFS.
ethskills
Ethereum development knowledge for AI agents — from idea to deployed dApp. Fetch real-time docs on gas costs, Solidity patterns, Scaffold-ETH 2, Layer 2s, DeFi composability, security, testing, and production deployment. Use when: (1) building any Ethereum or EVM dApp, (2) writing or reviewing Solidity contracts, (3) deploying to mainnet or L2s, (4) the user asks about gas, tokens, wallets, or smart contracts, (5) any web3/blockchain/onchain development task. NOT for: trading, price checking, or portfolio management — use a trading skill for those.
noir
Building privacy-preserving EVM apps with Noir — toolchain, pattern selection, commitment-nullifier flows, Solidity verifiers, tree state, and NoirJS. Use when building a Noir-based privacy app on EVM.