← Ethereum (ethskills)CONTENT HISTORY
WHAT CHANGED
Update to Ethereum (ethskills)
Snapshot Oct 24, 2026 · 13:33 UTC · version 0.1.1
Collection source: downloaded plugin package.
First saved snapshot
No earlier snapshot is available to establish a change.
Save Ethereum (ethskills) and check future changes in Following.
Up to 10 plugins free · no card required.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"description": "How to read and query onchain data — events, The Graph, indexing patterns. Why you cannot just loop through blocks, and what to use instead.",
"included_files": [],
"name": "indexing",
"skill_md_contents": "---\nname: indexing\ndescription: How to read and query onchain data — events, The Graph, indexing patterns. Why you cannot just loop through blocks, and what to use instead.\n---\n\n# Onchain Data & Indexing\n\n## What You Probably Got Wrong\n\n**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.\n\n**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.\n\n**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.\n\n**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.\n\n**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.\n\n---\n\n## Events Are Your API\n\nSolidity 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.\n\n### Design Contracts Event-First\n\nEvery state change should emit an event. This isn't just good practice — it's how your frontend, indexer, and block explorer know what happened.\n\n```solidity\n// ✅ Good — every action emits a queryable event\ncontract Marketplace {\n event Listed(\n uint256 indexed listingId,\n address indexed seller,\n address indexed tokenContract,\n uint256 tokenId,\n uint256 price\n );\n event Sold(uint256 indexed listingId, address indexed buyer, uint256 price);\n event Cancelled(uint256 indexed listingId);\n\n function list(address token, uint256 tokenId, uint256 price) external {\n uint256 id = nextListingId++;\n listings[id] = Listing(msg.sender, token, tokenId, price, true);\n emit Listed(id, msg.sender, token, tokenId, price);\n }\n\n function buy(uint256 listingId) external payable {\n // ... transfer logic ...\n emit Sold(listingId, msg.sender, msg.value);\n }\n}\n```\n\n**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.\n\n### Reading Events Directly (Small Scale)\n\nFor recent events or low-volume contracts, you can read events directly via RPC:\n\n```typescript\nimport { createPublicClient, http, parseAbiItem } from 'viem';\n\nconst client = createPublicClient({\n chain: mainnet,\n transport: http(),\n});\n\n// Get recent events (last 1000 blocks)\nconst logs = await client.getLogs({\n address: '0xYourContract',\n event: parseAbiItem('event Sold(uint256 indexed listingId, address indexed buyer, uint256 price)'),\n fromBlock: currentBlock - 1000n,\n toBlock: 'latest',\n});\n```\n\n**This works for:** Last few thousand blocks, low-volume contracts, real-time monitoring.\n**This breaks for:** Historical queries, high-volume contracts, anything scanning more than ~10K blocks.\n\n---\n\n## The Graph (Subgraphs)\n\nThe 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.\n\n### When to Use The Graph\n\n- Any dApp that needs historical data (activity feeds, transaction history)\n- Leaderboards, rankings, analytics dashboards\n- NFT collection browsers (who owns what, transfer history)\n- DeFi dashboards (position history, PnL tracking)\n- Any query that would require scanning more than ~10K blocks\n\n### How It Works\n\n1. **Define a schema** — what entities you want to query\n2. **Write mappings** — TypeScript handlers that process events into entities\n3. **Deploy** — subgraph indexes all historical events and stays synced\n\n### Example: NFT Collection Subgraph\n\n**schema.graphql:**\n```graphql\ntype Token @entity {\n id: ID!\n tokenId: BigInt!\n owner: Bytes!\n mintedAt: BigInt!\n transfers: [Transfer!]! @derivedFrom(field: \"token\")\n}\n\ntype Transfer @entity {\n id: ID!\n token: Token!\n from: Bytes!\n to: Bytes!\n timestamp: BigInt!\n blockNumber: BigInt!\n}\n```\n\n**mapping.ts:**\n```typescript\nimport { Transfer as TransferEvent } from './generated/MyNFT/MyNFT';\nimport { Token, Transfer } from './generated/schema';\n\nexport function handleTransfer(event: TransferEvent): void {\n let tokenId = event.params.tokenId.toString();\n\n // Create or update token entity\n let token = Token.load(tokenId);\n if (token == null) {\n token = new Token(tokenId);\n token.tokenId = event.params.tokenId;\n token.mintedAt = event.block.timestamp;\n }\n token.owner = event.params.to;\n token.save();\n\n // Create transfer record\n let transfer = new Transfer(\n event.transaction.hash.toHex() + '-' + event.logIndex.toString()\n );\n transfer.token = tokenId;\n transfer.from = event.params.from;\n transfer.to = event.params.to;\n transfer.timestamp = event.block.timestamp;\n transfer.blockNumber = event.block.number;\n transfer.save();\n}\n```\n\n**Query the subgraph:**\n```graphql\n{\n tokens(where: { owner: \"0xAlice...\" }, first: 100) {\n tokenId\n mintedAt\n transfers(orderBy: timestamp, orderDirection: desc, first: 5) {\n from\n to\n timestamp\n }\n }\n}\n```\n\n### Deploying a Subgraph\n\n```bash\n# Install\nnpm install -g @graphprotocol/graph-cli\n\n# Initialize from contract ABI\ngraph init --studio my-subgraph\n\n# Generate types from schema\ngraph codegen\n\n# Build\ngraph build\n\n# Deploy to Subgraph Studio\ngraph deploy --studio my-subgraph\n```\n\n**Subgraph Studio** (studio.thegraph.com) — development and testing environment. Free during development. Publish to the decentralized network for production.\n\n---\n\n## Alternative Indexing Solutions\n\n| Solution | Best for | Tradeoffs |\n|----------|----------|-----------|\n| **The Graph** | Production dApp backends, decentralized | GraphQL API, requires subgraph development |\n| **Dune Analytics** | Dashboards, analytics, ad-hoc queries | SQL interface, great visualization, not for app backends |\n| **Alchemy/QuickNode APIs** | Quick token/NFT queries | `getTokenBalances`, `getNFTs`, `getAssetTransfers` — fast but centralized |\n| **Etherscan/Blockscout APIs** | Simple event log queries | Rate-limited, not for high-volume |\n| **Ponder** | TypeScript-first indexing | Local-first, simpler than The Graph for single-app use |\n| **Direct RPC** | Real-time current state only | Only for current state reads, not historical |\n\n### Dune Analytics\n\nWrite SQL queries over decoded onchain data. Best for analytics and dashboards, not for app backends.\n\n```sql\n-- Top 10 buyers on your marketplace (last 30 days)\nSELECT\n buyer,\n COUNT(*) as purchases,\n SUM(price / 1e18) as total_eth_spent\nFROM mycontract_ethereum.Marketplace_evt_Sold\nWHERE evt_block_time > NOW() - INTERVAL '30' DAY\nGROUP BY buyer\nORDER BY total_eth_spent DESC\nLIMIT 10\n```\n\n### Enhanced Provider APIs\n\nFor common queries, provider APIs are faster than building a subgraph:\n\n```typescript\n// Alchemy: get all tokens held by an address\nconst balances = await alchemy.core.getTokenBalances(address);\n\n// Alchemy: get all NFTs owned by an address\nconst nfts = await alchemy.nft.getNftsForOwner(address);\n\n// Alchemy: get transfer history\nconst transfers = await alchemy.core.getAssetTransfers({\n fromAddress: address,\n category: ['erc20', 'erc721'],\n});\n```\n\n---\n\n## Reading Current State (Not Historical)\n\nFor current balances, allowances, and contract state, direct RPC reads are fine. No indexer needed.\n\n### Single Reads\n\n```typescript\nimport { createPublicClient, http } from 'viem';\n\nconst client = createPublicClient({ chain: mainnet, transport: http() });\n\n// Read current balance\nconst balance = await client.readContract({\n address: tokenAddress,\n abi: erc20Abi,\n functionName: 'balanceOf',\n args: [userAddress],\n});\n```\n\n### Batch Reads with Multicall\n\nFor multiple reads in one RPC call, use Multicall3 (deployed at the same address on every chain):\n\n```typescript\n// Multicall3: 0xcA11bde05977b3631167028862bE2a173976CA11\n// Same address on Ethereum, Arbitrum, Optimism, Base, Polygon, and 50+ chains\n\nconst results = await client.multicall({\n contracts: [\n { address: tokenA, abi: erc20Abi, functionName: 'balanceOf', args: [user] },\n { address: tokenB, abi: erc20Abi, functionName: 'balanceOf', args: [user] },\n { address: tokenC, abi: erc20Abi, functionName: 'balanceOf', args: [user] },\n { address: vault, abi: vaultAbi, functionName: 'totalAssets' },\n ],\n});\n// One RPC call instead of four\n```\n\n### Real-Time Updates\n\nFor live updates, subscribe to new events via WebSocket:\n\n```typescript\nimport { createPublicClient, webSocket } from 'viem';\n\nconst client = createPublicClient({\n chain: mainnet,\n transport: webSocket('wss://eth-mainnet.g.alchemy.com/v2/YOUR_KEY'),\n});\n\n// Watch for new sales in real-time\nconst unwatch = client.watchContractEvent({\n address: marketplaceAddress,\n abi: marketplaceAbi,\n eventName: 'Sold',\n onLogs: (logs) => {\n for (const log of logs) {\n console.log(`Sale: listing ${log.args.listingId} for ${log.args.price}`);\n }\n },\n});\n```\n\n---\n\n## Common Patterns\n\n| What you need | How to get it |\n|---------------|---------------|\n| Activity feed for a dApp | Emit events → index with The Graph → query via GraphQL |\n| Token balances for a user | Alchemy `getTokenBalances` or Multicall |\n| NFT collection browser | The Graph subgraph or Alchemy `getNftsForContract` |\n| Price history | Dune Analytics or DEX subgraphs |\n| Real-time new events | WebSocket subscription via viem |\n| Historical transaction list | The Graph or Alchemy `getAssetTransfers` |\n| Dashboard / analytics | Dune Analytics (SQL + charts) |\n| Protocol TVL tracking | DeFiLlama API or custom subgraph |\n"
}SHA-256 of public snapshot: 9f16c64dfc8a88c76c371d51d4e86e78e2db24a2018f6750f1c85452ed2074ec