Frontend: canonical address display (blockie + label + mega.etherscan.io link) site-wide
## Summary
Establish a **single canonical UI component** for displaying **20-byte Ethereum addresses** (`0x` + 40 hex, EOAs and contracts) across the dapp: **identicon (blockie)** + **human-readable label** + **link to the address on the block explorer**, defaulting to **[MegaETH Etherscan](https://mega.etherscan.io)** (same base URL convention as transaction links).
Today we already ship **`AddressInline`** (`frontend/src/components/AddressInline.tsx`) using **`ethereum-blockies`** via **`WalletBlockie`**, plus optional **`WalletFormatShort`** labels. It does **not** yet open explorer address pages. **`explorer.ts`** only exposes **`explorerTxUrl`** (`/tx/{hash}`); we need a sibling **`explorerAddressUrl`** (`/address/{address}`) with the same **`VITE_EXPLORER_BASE_URL`** default (`https://mega.etherscan.io`).
---
## Goals (acceptance criteria)
1. **Every user-visible 20-byte address** (EOA or contract) that is meant to be read as an “account/contract identity” uses the shared component—not ad-hoc `<span className="mono">`, raw `<code>`, or truncated hex alone.
2. **Blockie** is always paired with that address (unless the value is **invalid** or **`0x000…000`**—show a clear fallback like `—`).
3. **Explorer affordance:** the component wraps the label (and/or the row) in an **external link** to
`{normalized_explorer_base}/address/{address}`
using the same env contract as **`explorerTxUrl`** (`frontend/src/lib/explorer.ts`). Default base: **`https://mega.etherscan.io`**. Respect trailing-slash trimming; validate with **`viem`** **`isAddress`** before emitting a URL.
4. **Accessibility:** `target="_blank"` + `rel="noreferrer noopener"` on explorer links; decorative blockie remains **`aria-hidden`**; screen readers get a non-misleading name (e.g. **visually hidden** “View address on Etherscan” or include full address in **accessible name** where the visible label is truncated).
5. **Documentation:** update **`frontend/.env.example`** (if needed), **`docs/frontend/wallet-connection.md`** or explorer notes, and any **QA checklist** that mentions address display.
---
## Scope note: “EOA” vs contract
Block explorers use the same **`/address/0x…`** page for **EOAs and contracts**. Unless product explicitly hides blockies for contracts, **treat all 20-byte addresses the same** in the shared component (FeeRouter sink destinations, token contracts, etc.). **Do not** use this pattern for **32-byte tx hashes** (keep **`TxHash`** / **`explorerTxUrl`**).
---
## Current baseline — where `AddressInline` is already used
Verify after adding explorer links (grep from repo root):
```bash
rg 'AddressInline' frontend/src -g '*.tsx'
```
| Path | Role |
|------|------|
| `frontend/src/components/AddressInline.tsx` | Shared component (extend here first). |
| `frontend/src/pages/TimeCurveSimplePage.tsx` | Simple view: recent buys / extension chip. |
| `frontend/src/pages/timeCurveArena/TimeCurveArenaView.tsx` | Arena: WarBow momentum strip. |
| `frontend/src/pages/timeCurveArena/useTimeCurveArenaModel.tsx` | Podium spotlight “leader” cell. |
| `frontend/src/pages/timeCurveArena/TimeCurveArenaWalletMono.tsx` | Arena tables / ladder mono column. |
| `frontend/src/pages/timecurve/TimeCurveSections.tsx` | TimeCurve page: WarBow copy, podiums, indexer lists, revenge row. |
| `frontend/src/pages/TimeCurveProtocolPage.tsx` | Protocol `renderAddress` + fee sink destinations. |
| `frontend/src/components/FeeTransparency.tsx` | Footer fee sinks + indexer history rows. |
| `frontend/src/pages/referrals/ReferralConnectedWalletSection.tsx` | Connected wallet address. |
| `frontend/src/pages/referrals/ReferralLeaderboardSection.tsx` | Referrer column. |
---
## Likely gaps — systematic search (run from repo root)
### 1) Standalone blockie (bypasses shared chrome / explorer)
```bash
rg 'WalletBlockie' frontend/src -g '*.tsx'
```
**Primary hit:** `frontend/src/pages/timecurve/LiveBuyRow.tsx` — custom layout with its own **`WalletBlockie`** + mono name. **Refactor** to the shared component **or** compose **`WalletBlockie` + `explorerAddressUrl`** only inside **`AddressInline`** so there is one implementation.
### 2) `formatWallet` / `shortAddress` / truncation outside the component
```bash
rg 'formatWallet\(' frontend/src -g '*.tsx'
rg 'shortAddress\(' frontend/src
rg 'truncateHexAddress' frontend/src
```
Pay special attention to **string-only** uses (no React node):
```bash
rg 'formatWallet\(' frontend/src/pages/timeCurveArena/useTimeCurveArenaModel.tsx
```
Examples: **`buyProjectedEffects`** template strings, **`label: formatWallet(...)`** for chart/history payloads, **`buildBuyFeedNarrative` / `buildWarbowFeedNarrative`** in **`frontend/src/lib/timeCurveUx.ts`** (pure strings). Decide per surface:
- **Option A:** Keep narrative **text-only** (no blockie in sentence strings).
- **Option B:** Change call sites to **structured** `{ address, label }[]` or **React fragments** where the UI should show blockies (larger refactor).
Document the decision in the MR.
### 3) `mono` + address-like content
```bash
rg 'className="mono"' frontend/src -g '*.tsx'
```
Manually filter rows that are **amounts / bps / ISO times** (out of scope) vs **hex identities**.
### 4) `title={` with wallet-ish props
```bash
rg 'title=\{[^}]*(buyer|wallet|owner|referrer|addr)' frontend/src -g '*.tsx'
```
### 5) Other app surfaces
```bash
rg '0x0000000000000000000000000000000000000000|zeroAddress' frontend/src -g '*.tsx'
ls frontend/src/pages/*.tsx frontend/src/pages/*/*.tsx
```
Audit **Home**, **Collection**, **Rabbit Treasury**, **Kumbaya**, **Sir**, **Presale vesting**, **UnderConstruction**, **LaunchCountdown**, **WalletConnectButton** / RainbowKit custom children—any truncated **connected account** display.
### 6) Indexer / API field names (discovery aid)
```bash
rg '\b(buyer|referrer|winner|actor|from|to)\b' frontend/src/lib/indexerApi.ts
```
Cross-check each field surfaced in UI.
---
## Implementation sketch
1. **`frontend/src/lib/explorer.ts`**
- Add **`explorerAddressUrl(address: string): string | undefined`** (same base as **`explorerTxUrl`**; path **`/address/`**; validate **`isAddress`**).
2. **`frontend/src/lib/explorer.test.ts`**
- Default: `https://mega.etherscan.io/address/0x…`
- Override **`VITE_EXPLORER_BASE_URL`**; reject invalid / non-`0x40hex`.
3. **`AddressInline`** (or renamed **`LinkedAddress`** / **`AccountAddress`**)
- Optional prop **`explorer?: boolean`** (default **true** for “identity” rows; allow **false** for dense tables if design requires—justify in MR).
- Render **`<a href={explorerAddressUrl(raw)}>`** around label (or icon+label cluster) with **external-link** cursor class per existing patterns (`TxHash.tsx`).
4. **Sweep** inventories above; replace stragglers; align **`LiveBuyRow`** with shared behavior.
---
## Verification checklist (before merge)
- [ ] `cd frontend && npm run typecheck`
- [ ] `cd frontend && npm test` (includes **`explorer`** unit tests)
- [ ] Manual: click several address links → correct **MegaETH Etherscan** address page (or custom **`VITE_EXPLORER_BASE_URL`** in `.env.local`)
- [ ] Re-run **rg** passes from “Likely gaps” and attach **before/after** counts or a short comment in the MR proving coverage
- [ ] Update **Playwright** if any selector depended on plain `mono` text-only nodes
---
## References
- Existing tx explorer helper: `frontend/src/lib/explorer.ts` (`explorerTxUrl`, default `https://mega.etherscan.io`)
- Blockie implementation: `frontend/src/components/WalletBlockie.tsx`, package **`ethereum-blockies`**
- Label helpers: `frontend/src/lib/addressFormat.ts`, `frontend/src/pages/referrals/referralAddressDisplay.ts`
---
## Out of scope (explicit)
- **Transaction hashes** and **`TxHash`** component (different explorer path).
- **Numeric** display (`AmountDisplay`, bps, WAD decimals) in **`mono`**.
- **Multi-chain explorer matrix** unless explicitly specified (document single-chain assumption or follow-up issue).
issue