Problem
On BSC mainnet, a user's ERC20 deposit in block 122770723 was not indexed. A later rescan of the same block found it.
| Run |
Range |
Transfer events |
Matched |
| 2026-09-19 (live) |
122770723 → 122770724 |
394 |
0 |
| 2026-09-21 (rescan) |
122770723 → 122770723 |
391 |
1 |
Transaction https://bscscan.com/tx/0xf78a40c60f15bb20104a8749caa366d77cce390fd3c346abc254f3f144adf523 Block 122770723
If the first result had been complete, block 122770724 would have had only 3 Transfer events. BSC blocks usually have hundreds, so the live query got an incomplete log set. The matching logic ran the same both times; the RPC returned different data.
Root cause
- The regular worker processes right up to the RPC's head block (
internal/worker/regular.go, end := min(..., latest)).
- BSC produces a block about every 0.45s. Nodes often import a block before its log index is complete. During that gap,
eth_getLogs returns partial or empty results with no error.
- ERC20 detection relies only on
eth_getLogs (queryERC20TransfersToMonitoredAddresses in internal/indexer/evm.go). A missing log means the tx is never looked at again.
Solutions
1. Head delay using confirmations
ChainConfig.Confirmations (yaml:"confirmations") is already parsed but nothing reads it. Wire it into RegularWorker.processRegularBlocks so the worker processes up to latest - confirmations:
if latest > rw.config.Confirmations {
latest -= rw.config.Confirmations
}
Then set confirmations: 10 for bsc_mainnet (about 4–5s, which is past BSC fast finality and the log-indexing gap). Leaving it unset means 0, so other chains behave as they do today.
- ✅ Small change, configurable per chain, works on every chain
- ⚠️ Lowers the risk but doesn't remove it if a provider is badly behind
2. Use the finalized block tag as the head (per-chain opt-in)
On chains with fast finality, use eth_getBlockByNumber("finalized") instead of latest as the head. On BSC the finalized head is usually 2–3 blocks behind.
- ✅ Follows real finality instead of a fixed number
- ⚠️ A lagging node also reports a lagging finalized head
- ⚠️ Opt-in only for fast-finality chains. It is far too slow for Ethereum and the L2s, so it will not be used there (see table).
finalized tag support by chain
| Chain |
Supported |
How far behind the head |
Good for deposit indexing? |
| BSC |
Yes |
~2–3 blocks (about 1–2s) |
✅ Yes |
| Avalanche C-Chain |
Yes |
~0, finality is effectively instant |
✅ Yes |
| Polygon PoS |
Yes |
Seconds, since its milestone-based finality upgrade. It used to be ~30 min. |
✅ Probably. Check with your provider. |
| Ethereum mainnet |
Yes |
~64–95 blocks, about 13–19 minutes |
❌ Too slow, won't use |
| Arbitrum / Optimism / Base (L2s) |
Yes |
Waits for Ethereum to finalize the batch, about 15–30+ minutes |
❌ Too slow, won't use |
| TRON |
No, its JSON-RPC doesn't support this tag |
It has its own "solidified" block API instead (about 19 blocks, ~1 min) |
Needs separate handling |
| Other EVM chains (Conflux, Lisk, …) |
Depends on the chain and the node software |
Check each one |
— |
Finality timings change as chains upgrade. Check against current providers before enabling.
Recommendation
Do 1 now with confirmations: 10 for BSC. Add 2 as an optional per-chain head mode for fast-finality chains (BSC, Avalanche) if we want head selection based on real finality.
Recovery
Rescan affected BSC ranges to recover missed deposits. Block 122770723 has been confirmed to recover on rescan.
Problem
On BSC mainnet, a user's ERC20 deposit in block 122770723 was not indexed. A later rescan of the same block found it.
Transaction https://bscscan.com/tx/0xf78a40c60f15bb20104a8749caa366d77cce390fd3c346abc254f3f144adf523 Block 122770723
If the first result had been complete, block 122770724 would have had only 3 Transfer events. BSC blocks usually have hundreds, so the live query got an incomplete log set. The matching logic ran the same both times; the RPC returned different data.
Root cause
internal/worker/regular.go,end := min(..., latest)).eth_getLogsreturns partial or empty results with no error.eth_getLogs(queryERC20TransfersToMonitoredAddressesininternal/indexer/evm.go). A missing log means the tx is never looked at again.Solutions
1. Head delay using
confirmationsChainConfig.Confirmations(yaml:"confirmations") is already parsed but nothing reads it. Wire it intoRegularWorker.processRegularBlocksso the worker processes up tolatest - confirmations:Then set
confirmations: 10forbsc_mainnet(about 4–5s, which is past BSC fast finality and the log-indexing gap). Leaving it unset means 0, so other chains behave as they do today.2. Use the
finalizedblock tag as the head (per-chain opt-in)On chains with fast finality, use
eth_getBlockByNumber("finalized")instead oflatestas the head. On BSC the finalized head is usually 2–3 blocks behind.finalizedtag support by chainFinality timings change as chains upgrade. Check against current providers before enabling.
Recommendation
Do 1 now with
confirmations: 10for BSC. Add 2 as an optional per-chain head mode for fast-finality chains (BSC, Avalanche) if we want head selection based on real finality.Recovery
Rescan affected BSC ranges to recover missed deposits. Block 122770723 has been confirmed to recover on rescan.