- Category
- Guides
- Reading time
- 7 minutes
- Published
Export 30 days of Ethereum USDC transfers to CSV
To export Ethereum USDC transfers, query eth_getLogs for the native USDC contract and the ERC-20 Transfer event, persist the results, and generate CSV from the committed dataset. A 30-day export needs exact timestamp boundaries and many bounded requests. It should not be one query from an estimated block to latest.
This guide covers all USDC Transfer events in a chosen Ethereum interval, with an optional wallet filter. It separates provider qualification, pagination, durable progress, and CSV validation. A coding agent can implement the workflow, but it should report the actual completed range and remaining work rather than calling a sample query a complete export.
Choose an RPC endpoint that serves the required history
Use an authenticated endpoint for a sustained SolidRPC backfill. The Ethereum reference lists chain ID 1 and an authenticated eth_getLogs ceiling of 30,000 blocks per call, checked October 29, 2026. Dense USDC ranges can require much smaller pages. This is a range allowance, not a promise that a 30,000-block response will fit every deadline or result-size limit.
The Free API-key allowance is 10,000 responses per UTC day. Whether it fits depends on successful page sizes, timestamp lookups, retries, verification, and other account traffic. The keyless public route is intended for light tests, not a production 30-day export. See the current plans and limit comparison.
Before starting, check eth_chainId equals 0x1, retrieve a known log near the oldest required date, and test a dense representative page. An eth_blockNumber response alone does not prove historical log access. Logs require retained event history. They do not inherently require historical account-state queries.
Turn the 30-day window into exact block boundaries
Define UTC timestamps start inclusive and end exclusive. For a reproducible completed export, resolve an Ethereum finalized block and use its timestamp as the reference for a closed historical window, or choose a past end timestamp already covered by finalized history. Record the chosen policy. If the requested end is newer than finalized history, wait or clearly label the recent portion provisional.
Use eth_getBlockByNumber and a binary search over block timestamps to find the first block whose timestamp is at least start, then the first whose timestamp is at least end. The last included block is one less than the latter. Save the boundary block numbers, hashes, and timestamps. Do not estimate them by multiplying 30 days by a nominal block rate.
The export interval is fixed once the job starts. Moving toBlock to latest on each page changes the meaning of '30 days' and makes completion difficult to verify. Keep timestamp resolution separate from the log-pagination cursor.
Build the USDC Transfer filter
Circle lists Ethereum native USDC at 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 in its contract directory. Verify the address for Ethereum mainnet, not Base or another deployment. Compute the event topic with a Keccak-256 ABI utility from Transfer(address,address,uint256). Do not substitute the differently defined SHA3-256 hash.
The request structure is:
{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_getLogs",
"params": [{
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"fromBlock": "START_BLOCK_HEX",
"toBlock": "END_BLOCK_HEX",
"topics": ["TRANSFER_TOPIC_HASH"]
}]
}Replace the three uppercase placeholders with resolved hexadecimal values before sending. The ERC-20 event specification places indexed sender and recipient addresses in the next two topics, with the value in event data. Decode with the contract ABI, retain raw fields, and preserve integer amounts. A zero-value Transfer event is still an event.
For one wallet, run an outgoing filter with the wallet in topic position 1 and an incoming filter with it in position 2. Combine and deduplicate the results. Putting the wallet in both positions selects self-transfers, not all incoming or outgoing transfers. An all-token export should not add either wallet filter.
Page by successful work, not by a fixed optimistic width
Start with a small measured page. For an inclusive range [from, to], commit that page before setting the next cursor to to + 1. Shrink the same page after a range, response-size, or repeated timeout failure. Pace requests separately from page width, and honor throttling. A smaller range does not repair an authentication failure or unavailable history.
The range limit is only one constraint. Result-count caps, response bytes, deadlines, and account quotas can stop a dense query before its allowed block span is reached. Even one busy block may require a more selective filter or a different qualified data product. Never skip a failing block to make the progress counter advance.
Compare the complete job at the same fixed interval. Keep calls attempted, successful pages, rows committed, response bytes, retries, elapsed time, and billed usage. The gap-free backfill guide gives the general cursor and error-handling design.
Commit first, then generate the CSV
Store raw logs and a page ledger in a durable database. A canonical log identity can use chain ID, block hash, and log index. Record transaction hash too. Commit the page's logs and next cursor in the same transaction. If a process restarts, replay an unfinished page idempotently. Do not append directly to a CSV and separately update a cursor, because a crash between those writes can duplicate or lose rows.
Once the interval is complete, export a deterministic ordering by block number, transaction index, and log index. Useful columns are chain_id, block_number, block_hash, block_timestamp_utc, transaction_hash, transaction_index, log_index, token_address, from_address, to_address, and value_raw. A human-readable amount can be a separate decimal string derived from verified token decimals.
Log responses do not include a block timestamp, so fetch and cache headers for distinct blocks that have returned logs. Include those header calls in the budget. Keep address and hash columns as text when importing into a spreadsheet, and retain the original CSV plus a checksum for comparison.
Prove that the export is complete
A large row count is not proof of coverage. Validate that every page from the start block through the fixed target is committed with no missing interval. Check that each row matches the contract and event filter, falls inside the timestamp window, and has a unique canonical log identity. Validate known transactions near both boundaries and in a dense interval.
Repeat selected pages and compare identities and raw fields. Verify boundary hashes against the accepted chain. If including unfinalized history, reconcile orphaned blocks and regenerate the affected rows before claiming a stable export. Test interruption after a database commit and before CSV generation, then confirm that the resumed output matches an uninterrupted run.
Record the request configuration, documentation date, actual endpoint class, covered timestamps and blocks, row count, checksum, and unresolved errors. Report 'sample verified' for a sample. Report 'complete' only when the entire fixed interval and validation checks have finished.
Keep the agent’s account and data permissions separate
An exporter needs read methods and a private RPC key. It does not need the user's token approvals, signing key, or permission to transfer USDC. USDC in the dataset is distinct from USDC used to purchase RPC access.
If an owner authorizes programmatic paid setup, use the SolidRPC onboarding flow, then give the export worker only its RPC credential. Set a request budget and a stop condition before starting. The completed CSV is the deliverable, and the page ledger is the evidence that it covers the requested history.