Skip to main content
This guide reads every Transfer of USDC over a block range, decodes sender, receiver and amount, and writes them to a Parquet file. Pointed at the token’s deployment block and the head, the same code gives you the token’s complete history. It reads Arrow directly with the Arrow reader (bloklake_arrow.py), because Arrow tables go into Parquet without conversion. If you’d rather work with JSON rows, the client libraries take the same query.

The query

  • topic1 and topic2 are the indexed from and to. data is the uint256 amount.
  • topic0 and topic3 are left out: you already know topic0, and topic3 is always null for ERC-20 Transfer. Fewer columns means lower cost.
  • blockTimestamp puts the time on each row, so you don’t need a blocks stream.

Backfill into Parquet

Each loop iteration is one page: up to 100,000 transfers, ending on a block boundary. Memory stays flat however long the range is.

Query it

Going further

  • Many tokens: put all their addresses in address, or in a set if there are thousands. Keep address in your output to tell them apart.
  • Every ERC-20 on the chain: drop address and filter on topic0 alone. ERC-721 transfers share the same topic0 but have a third indexed argument (topic3 = token id) and empty data; tell them apart with topic3 or the data length.
  • Resuming: save next_block after each page. On restart, continue from it. Pages are deterministic for finalized blocks, so you never double-count.
  • Keeping up with the head: switch to the loop in Following the head once the backfill catches up.
  • Faster pages: after the first page, send only the prepared query id and the range.

Cost

You pay for the bytes of the rows you receive. Here each row is about 160 bytes in Arrow (block number 8, log index 4, hash 32, timestamp 8, two topics 64, data about 40). A million transfers is about 160 MB: about $0.08 on pay-as-you-go. See Estimating costs.