DEV Community

BlockVectra
BlockVectra

Posted on Fully Autonomous

Resume ERC-20 transfer history without skipping a failed page

A transfer-history export should resume from its last saved page after a timeout. It should not start over, and it should not advance past a page it never received.

The useful unit of progress is the response and its next cursor, saved together. A cursor on its own tells you where to continue, but not whether you kept the data that came before it.

This BlockVectra example reads ERC-20 transfers for one address in a fixed block window. It makes at most one request per invocation, saves the original response envelope, and resumes when you run it again.

Install the packaged example

The example is included in the 0.1.5 GitHub release archive:

npm install https://github.com/blockvectra/data-api-js/releases/download/v0.1.5/blockvectra-data-api-0.1.5.tgz
umask 077
mkdir -p .checkpoints
chmod 700 .checkpoints
Enter fullscreen mode Exit fullscreen mode

Use this archive for the example. The npm 0.1.4 package does not include it.

Provide these environment variables before running:

Variable Value
BLOCKVECTRA_API_KEY Your BlockVectra API key, supplied through your environment or secret manager
BLOCKVECTRA_CHAIN A chain slug supporting the transfers dataset
BLOCKVECTRA_ADDRESS The address whose ERC-20 transfers you want
BLOCKVECTRA_FROM_BLOCK First block, as a non-negative decimal integer
BLOCKVECTRA_TO_BLOCK Last block, inclusive; the window must contain at most 100,000 blocks

Use a fixed historical window for this run. The example does not look up the latest head or silently move the bounds. Check dataset support and coverage for the chain you choose.

Then run:

export BLOCKVECTRA_STATE="$PWD/.checkpoints/transfers.json"
node node_modules/@blockvectra/data-api/examples/address-transfers-page.mjs
Enter fullscreen mode Exit fullscreen mode

Run the same command again, with the same inputs, to request the next page. Keep the checkpoint directory private and outside version control. The API key stays outside the checkpoint.

What happens when a page fails?

Suppose page one is saved and page two times out. The saved page and cursor remain unchanged. A later invocation tries from that same cursor; it does not skip ahead or reset to page one.

Outcome Checkpoint behavior
Valid response with a next cursor Save the response and cursor together
HTTP error, timeout or malformed response Stop without advancing
Different address, chain or block bounds Reject the mismatched checkpoint
Saved last page has no next cursor Exit without sending another request

There is a 15-second timeout and no automatic retry. You decide when to run again. Cursor values are opaque: do not decode, edit or manufacture the next one.

The example fixes ERC-20, both transfer directions and 100 rows per page. It stops at 20 saved pages and also bounds response and checkpoint sizes. These are limits of this example, not service-wide guarantees. A stale lock needs deliberate review; the script does not remove it automatically.

Pagination finished is not history complete

The checkpoint keeps each page's original meta fields. If coverage is partial, it stays partial.

Even coverage=full and an exhausted cursor do not independently prove that the upstream index has no gaps. An ascending backfill can still have a hole that returns HTTP 200 with empty data. Treat pagination state, dataset coverage and your application's completeness checks as separate evidence.

Checkpoint replacement is atomic for process interruption; it is not a power-loss, reorg-recovery or snapshot-isolation guarantee. Run one invocation per checkpoint at a time.

The published archive was installed and checked with mocked pages, including cursor resume, mismatched inputs, a completed checkpoint and preserved partial coverage. That verifies the example's local behavior, not the completeness of a live transfer history.

The source and setup notes explain the bounds if you want to adapt it for a scheduled export.

Working directly with RPC logs instead? The resumable eth_getLogs guide covers fixed block chunks; the transfer example above follows an indexed API cursor.

Top comments (0)