Build a transfer history, audit trail, or analytics table from contract events. This guide reads
the USDC Transfer logs emitted in Ethereum mainnet block 18000000, then shows how to keep larger
log scans bounded and safe to resume.
Prerequisites
- A GraphOps RPC API key.
- Ethereum mainnet endpoint access.
- An application path for storing or processing returned logs.
Request
curl "https://rpc.graphops.xyz/v1/evm/1" \
-H "Authorization: Bearer <GRAPHOPS_API_KEY>" \
-H "Content-Type: application/json" \
--data '{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_getLogs",
"params": [
{
"fromBlock": "0x112a880",
"toBlock": "0x112a880",
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
]
}
]
}'import { createPublicClient, http, parseAbiItem } from "viem";
import { mainnet } from "viem/chains";
const client = createPublicClient({
chain: mainnet,
transport: http("https://rpc.graphops.xyz/v1/evm/1", {
fetchOptions: {
headers: {
Authorization: "Bearer <GRAPHOPS_API_KEY>",
},
},
}),
});
const logs = await client.getLogs({
address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
event: parseAbiItem(
"event Transfer(address indexed from, address indexed to, uint256 value)",
),
fromBlock: 18_000_000n,
toBlock: 18_000_000n,
});
console.log(logs.length);import { ethers } from "ethers";
const request = new ethers.FetchRequest("https://rpc.graphops.xyz/v1/evm/1");
request.setHeader("Authorization", "Bearer <GRAPHOPS_API_KEY>");
const provider = new ethers.JsonRpcProvider(request);
const logs = await provider.getLogs({
address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
topics: [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
],
fromBlock: 18_000_000,
toBlock: 18_000_000,
});
console.log(logs.length);from web3 import Web3
w3 = Web3(
Web3.HTTPProvider(
"https://rpc.graphops.xyz/v1/evm/1",
request_kwargs={
"headers": {
"Authorization": "Bearer <GRAPHOPS_API_KEY>",
},
},
)
)
logs = w3.eth.get_logs(
{
"fromBlock": 18_000_000,
"toBlock": 18_000_000,
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
],
}
)
print(len(logs))Response
The request returns every USDC Transfer log in the block. Each log looks like this one:
{ "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",The USDC contract "topics": [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",Transfer event signature "0x00000000000000000000000098c23e9d8f34fefb1b7bd6a91b7ff122f4e16f5c",Indexed from address "0x000000000000000000000000df455882303fa4fa16bd96da7f0ae3b13983cc4c"Indexed to address ], "data": "0x00000000000000000000000000000000000000000000000000000001dcd65000",Amount: 8,000,000,000 base units, 8,000 USDC "blockNumber": "0x112a880",Block 18,000,000 "transactionHash": "0xde0ca4c3db355459e8573d7d6f6373256ef1e883e331f3aaf9e4cb8bd668138d", "transactionIndex": "0xd", "blockHash": "0x95b198e154acbfc64109dfd22d8224fe927fd8dfdedfae01587674482ba4baf3", "logIndex": "0x3d",With blockHash and transactionHash, a unique key "removed": false,Set to true when a reorg removes the log "blockTimestamp": "0x64ea268f"}Production notes
- Always bound historical
eth_getLogsrequests by block range. - Filter by contract address and topic when possible.
- Store the last completed block range for backfills.
- Deduplicate logs by
(blockHash, transactionHash, logIndex). - If a range times out or hits a response-size limit, retry smaller ranges.