← Back to the library
Web3Deep dive · 10 min

A deposit was credited, then vanished after a reorg

“The indexer saw a deposit and credited the user, but the block was reorganized and the transfer no longer exists on the canonical chain.”

THE PRINCIPLE

A block at the tip is a proposal, not a fact. Finality is a property you query, not a confirmation count you guess.

FIRST MOVES

  1. On Ethereum mainnet, read the finalized block tag (typically about two epochs, ~13 minutes) instead of hard-coding N confirmations; safe is a weaker, faster head.
  2. Show deposits at latest as pending; move funds to available only once their block is at or below finalized.
  3. Key credits by (chain_id, tx_hash, log_index) with a unique constraint so replays and re-scans cannot double credit.
  4. Store block number and hash per processed block; on parent-hash mismatch, roll back to the fork point and re-scan.
  5. Handle removed: true logs from filters and subscriptions; they mean a previously delivered log was reorged out.
  6. On L2s, learn the chain's own labels: OP Stack unsafe, safe, and finalized differ, and the 7-day bridge window is not transaction finality.

TOOLS: THEN → NOW

Fixed confirmation countssafe and finalized block tags (post-Merge), with confirmations still fine for low-value UX
Append-only log scannersReorg-aware indexers that track block hashes and roll back

PATTERN SNAPSHOT

const final = await client.getBlock({ blockTag: "finalized" });
const logs = await client.getContractEvents({
  address: token, abi: erc20Abi, eventName: "Transfer",
  args: { to: depositAddress },
  fromBlock: cursor + 1n, toBlock: final.number,
});
for (const log of logs) {
  await tx.query(
    `INSERT INTO deposits (chain_id, tx_hash, log_index, amount)
     VALUES ($1, $2, $3, $4) ON CONFLICT DO NOTHING`,
    [chainId, log.transactionHash, log.logIndex, log.args.value],
  );
}
await tx.query("UPDATE cursors SET block = $1", [final.number]);

CLOSE THE AI. EXPLAIN THIS.

Why is (tx_hash, log_index) a better idempotency key than tx_hash alone for token deposits?

HOW IT WORKS UNDERNEATH

SOURCES

Guide reviewed