No More Dashes: How We Fixed the On-Chain Token-Bound Account Lookup at Re-Mint
After a re-mint, the HeLa Citizen re-mint success card was showing a dash (-) where the Token-Bound Account (TBA) address should appear. The TBA was real and on-chain — the UI just didn't know where to look for it.
Here's what was happening and how we fixed it.
Why the Dash Appeared
A HeLa Citizen ID is an ERC-721 NFT. Every Citizen also gets an ERC-6551 Token-Bound Account — a smart contract wallet whose ownership is encoded in the NFT itself. When you hold the token, you control the TBA.
The problem sits in how re-mint works. Re-mint is a relay operation: a relay contract calls the registry on your behalf and replays your original mint transaction. The relay's internal state gets populated for the replay path — but the TBA address we were displaying came from that relay-side state, which doesn't carry the original on-chain data from the first mint. For a fresh first-time mint, this worked fine. For a re-mint, we were reading a field that hadn't been set, so the card rendered -.
The Fix: resolveCitizenOnChain()
Devon added a new function in src/lib/citizen_lookup.ts — resolveCitizenOnChain() — that reads the TBA and soulOwner directly from the chain by scanning the original mint event log.
The lookup uses eth_getLogs with backward paging:
- Start from the current block
- Step backward in 100-block windows toward the factory deploy block
- Stop on the first
CitizenMintedevent that matches the Citizen ID - Extract the TBA address and
soulOwnerfrom the event payload
This is an RPC-safe pattern: 100-block windows stay within typical provider rate-limit budgets. For recently minted Citizens the scan terminates in the first or second window; only Citizens minted near genesis walk the full block range.
Both the relay-mint replay branches and the status endpoint null-TBA self-heal path now call resolveCitizenOnChain() instead of relying on relay-side cached state.
Result
The re-mint success card now shows the real TBA address. Verified against a testnet Citizen ID. Commit 767b42a is live on testnet.
Related Reading
- ERC-6551: Token-Bound Accounts, Explained — background on how TBAs work and why every Citizen gets one
- From 504 to 202: How We Fixed the Async Mint Timeout — the relay architecture that surfaced this lookup gap