Skip to main content
In this guide, you will learn how to efficiently compute token ownership for ERC-721, ERC-20, and ERC-1155 contracts on Monad, using Envio HyperSync to accelerate the process.

Background

A common problem for EVM developers is the “historical balance problem” - recovering the full mapping of accounts to balances for an ERC-20, ERC-721, or ERC-1155 token. Solidity doesn’t keep track of the keys in a mapping; values are stored in the appropriate storage slot after hashing the key. Therefore, to recover the balances, a typical strategy is to replay all transfer events for that token and compute the rolling sum. Envio HyperSync is an indexer that allows developers to query millions of blockchain events in seconds. In this guide, we’ll use data from HyperSync to reconstruct the historical balances for a token.

Prerequisites

HyperSync Endpoints

NetworkURL
Testnet
Mainnet

Ingredients

This guide surveys how to reconstruct balances for all three of the most popular token standards - ERC-20, ERC-721, and ERC-1155. Each uses some common ingredients, while requiring different logic to assemble the end reuslt.

Querying Transfer Events

First, let’s write some code to query all of the Transfer events for our contract, filtering for the appropriate signature. ERC-20 and ERC-721 use the Transfer event below, while ERC-1155 uses the TransferSingle and TransferBatch events:
lib/signatures.ts
Here is code for querying the events for an ERC-20 contract:
lib/hypersync.ts

Field Selection

To optimize our queries, we should only request the fields we actually need. This reduces response size and speeds up queries significantly. Different token standards encode data in different topics: ERC-721 (tokenId in topic3):
ERC-20 (value in data):
ERC-1155 (id and value in data):

Parsing Log Data

Now that we have the raw event data, we need to parse it into usable values. Topics are 32-byte hex strings where addresses are left-padded with zeros, occupying the last 20 bytes:
lib/parse.ts
Note: parseValue returns the raw token value as a bigint. ERC-20 tokens have a decimals property (typically 18) — divide by 10n ** BigInt(decimals) to convert to a human-readable amount.

Pagination

HyperSync returns paginated results to handle large datasets efficiently. We need to continue querying until next_block is undefined to get all transfers:
lib/paginate.ts
Now we can start putting it all together.

ERC-721 Balance Snapshot

For ERC-721, we will reconstruct the current ownership state by replaying all Transfer events. For NFTs, the last transfer for each token ID determines the current owner:
app/api/snapshot/route.ts

ERC-20 Balance Snapshot

For ERC-20 tokens, we need to sum all transfers to calculate current balances. Unlike NFTs where we track ownership, ERC-20 balances require summing all incoming and outgoing transfers for each address. First, query ERC-20 transfers with the right field selection (topic1 for from, topic2 for to, and data for the value):
lib/erc20.ts
Then reconstruct balances by summing all transfers:
lib/erc20.ts

ERC-1155 Balance Snapshot

ERC-1155 tokens work differently from ERC-20 and ERC-721. They store id and value in the data field rather than topics, requiring more complex parsing:
lib/erc1155.ts

Querying Multiple Event Types

When working with ERC-1155 tokens, we can optimize by querying both TransferSingle and TransferBatch events in a single request:
lib/multi-event.ts
Then route based on topic0:

Get Latest Block

To verify you’ve synced all available data, you can check the current chain height:
lib/height.ts

Common Mistakes

1. Forgetting pagination

HyperSync returns partial results. Always check next_block:

2. Requesting unused fields

Every field adds to response size. Be explicit:

3. Using libraries for simple parsing

HyperSync returns raw hex. Native BigInt handles it:

4. Not filtering burned tokens

Address 0x000...000 means burned:

API Reference

POST /query

Query event logs. Request body: Response:

GET /height

Get current chain height. Response:

Summary

Envio HyperSync lets you query any contract’s event history across Monad with a single paginated API — no node to run, no indexer to maintain. The /query endpoint accepts topic and address filters, and /height gives you the current chain tip. Check out the full HyperSync documentation for additional query types and options. From here, you could extend this pattern to build airdrop eligibility checkers, governance voting power snapshots, or multi-token portfolio trackers.

Next Steps