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