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
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
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)