Agent tooling relocation, architecture redesign docs (#3343)

* add export for address txs

* transfer all export from page to dialog

* remove old route and components

* improve file name and fix loading state of icon button

* basic implementation

* refetch item status

* status indicator for download items

* changes for multichain

* handle too many requests for downloads error

* update button styles and spacing

* fix tests

* disable async export for multichain

* Fix CSV export cancel flow and stabilize context updates

Applied via @cursor push command

* test and review fixes

* move file to client folder

* round 3

* add glossary

* migration tasks and skill

* move skills to .agents folder

* rename migrate skill

* refine project rules

* move MIGRATION_TASKS.md to client directory

* update sym links

* refine migration skills

* fixes after merging the main branch

---------

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
tom goriunov
2026-03-02 16:42:12 +02:00
committed by GitHub
co-authored by Hanzo Dev
parent 1a57cb25a0
commit fce9bd8f96
31 changed files with 1467 additions and 202 deletions
+26
View File
@@ -0,0 +1,26 @@
# Frontend application for Blockscout
## Architecture
See `./rules/architecture.mdc`.
## Design System Rules
See `./rules/design-system.mdc`.
## Code Style & Quality
See `./rules/code-quality.mdc`.
## TypeScript Conventions
See `./rules/typescript.mdc`.
## Environment Variables
See `./rules/env-vars.mdc`.
## Testing
- Unit tests (`*.spec.ts` / `*.spec.tsx`): See `./rules/tests-unit.mdc`.
- Visual component tests (`*.pw.tsx`): See `./rules/tests-visual.mdc`.
+84
View File
@@ -0,0 +1,84 @@
---
description: Project overview, tech stack, directory layout, data flow, and ongoing client/ architecture migration
globs:
alwaysApply: true
---
# Project Architecture
## What this is
Blockscout frontend — a blockchain explorer UI. Distributed as a Docker image; configured entirely via environment variables at runtime (see `.agents/rules/env-vars.mdc`).
## Tech stack
| Layer | Technology |
|---|---|
| Framework | Next.js 16 — **Pages Router only** (not App Router) |
| UI | React 19 |
| Component library | Chakra UI v3 (see `.agents/rules/design-system.mdc`) |
| Server state | React Query 5 |
| Web3 | Wagmi 2 / Viem 2 |
| Schema validation | Valibot |
| Unit tests | Vitest |
| Visual tests | Playwright |
| Package manager | pnpm (Node >=22.14.0) |
## Domain terminology
Feature and product codenames used throughout the codebase are defined in `docs/GLOSSARY.md`. Consult it whenever you encounter an unfamiliar term — e.g. `tac`, `bens`, `cctx`, `kettle`, `epoch`, `blobs`.
## Directory layout
```
pages/ Next.js file-based routes — thin wrappers only (dynamic import + getServerSideProps)
ui/ Legacy React UI components organized by feature (~65 subdirectories) → being migrated to client/
lib/ Legacy business logic, API utilities, custom hooks, context providers → being migrated to client/
client/ New home for all product code (see Migration section below)
toolkit/ Design system — Chakra wrappers, theme tokens, shared hooks/components (stays here permanently)
configs/app/ Runtime app configuration; must not import from client/
nextjs/ Next.js config utilities: headers, rewrites, redirects, type-safe routes
mocks/ Shared mock data for tests → being co-located under client/ slices/features
deploy/ Docker scripts, env validator, build tools
docs/ ENVS.md, GLOSSARY.md, CONTRIBUTING.md
```
## Data flow
- **Getting data:** pages fetch data via React Query. Query keys and fetcher functions live in `lib/api/` (moving to `client/api/`).
- **Global UI state:** React Context providers initialized in `pages/_app.tsx` — `AppContextProvider`, `SettingsContextProvider`, `MarketplaceContextProvider`, etc.
- **Real-time data:** WebSocket via `SocketProvider` (moving to `client/api/socket/`).
- **App config:** always read via `configs/app/` (which reads `window.__envs` at runtime) — never `process.env.*` directly in component code.
---
## Ongoing migration: `client/` architecture
The codebase is being progressively restructured. The long-term target moves all product code from `ui/` and `lib/` into a new `client/` directory with a clear domain-based layout.
**Blueprint:** `client/ARCH_REDESIGN.md`
**Task backlog and current status:** `client/MIGRATION_TASKS.md`
### Target layout inside `client/`
| Directory | Contents |
|---|---|
| `client/shell/` | App chrome: layout, header, footer, navigation, top-bar, root contexts |
| `client/api/` | API transport, fetch utilities, query client, WebSocket; migrated from `lib/api/` and `lib/socket/` |
| `client/slices/` | Core explorer entities always present on any EVM chain (tx, block, address, token, contract, search, …) |
| `client/features/` | Optional / config-gated areas: rollups, chain variants, account, rewards, marketplace, stats, … |
| `client/shared/` | Cross-cutting utilities with no single domain owner (analytics, errors, hooks, router helpers, text utils, …) |
### Slice vs feature
- **Slice** — present on every vanilla EVM chain, no feature flag required. Examples: `tx`, `block`, `address`, `token`, `contract`.
- **Feature** — config-gated or chain-specific. Examples: `rollup/optimism`, `chain-variants/celo`, `account`, `stats`, `gas-tracker`.
### Key rules for migrated code
- **No barrel `index.ts` files** inside `client/` unless the file defines a genuine public facade (not just `export * from ...`).
- **`client/api` must not have runtime imports** from `client/slices/*` or `client/features/*`; `import type` is allowed.
- **`configs/` never imports `client/`.**
- **`pages/` files are thin wrappers** — a dynamic import and optionally `getServerSideProps`; no UI components or business logic inline.
- **Naming:** kebab-case for directories and non-component modules; `PascalCase.tsx` for React components; `useCamelCase.ts` for hooks.
- **Types:** each slice/feature owns its API response types in `<slice|feature>/types/api.ts`. Do not import from deeper internal paths across domain boundaries.
- **When editing legacy `lib/` or `ui/` code:** check `client/MIGRATION_TASKS.md` to see if that area has a migration task in progress or planned. If so, note the target destination in your PR description.
+136
View File
@@ -0,0 +1,136 @@
---
description: Code quality rules for the Blockscout frontend
globs: *.tsx,*.ts
alwaysApply: false
---
# Code Quality
We use ESLint, cSpell and Typescript to maintain code quality and consistency across the project.
In order to check code quality run the following commands:
```bash
pnpm lint:eslint:fix
pnpm lint:tsc
pnpm lint:cspell
```
Moreover, please find below the general sense rules that linters do not cover.
## Code Style and Structure
### General Principles
- Write concise, readable TypeScript code
- Use functional and declarative programming patterns; avoid classes
- Prefer iteration and modularization over code duplication
- Implement early returns for better readability
- Structure components logically: exports, subcomponents, helpers, types
### Naming Conventions
- Prefer descriptive names with auxiliary verbs (isLoading, hasError)
- Prefix event handlers with "handle" (handleClick, handleSubmit)
- Favor default exports for React components
Names should be specific and self-documenting. Vague names hide intent and make the codebase harder to navigate.
```ts
// BAD
const useWidgets = () => { ... };
// GOOD
const useAddress3rdPartyWidgets = () => { ... };
```
This applies to hooks, components, functions, and variables alike..
## Specific rules
### Magic numbers
Extract magic numbers into named `UPPER_SNAKE_CASE` constants, placed above the component or function that uses them. This makes intent clear and avoids silent duplication.
```ts
// BAD
const visibleItems = items.slice(0, 4);
// GOOD
const MAX_VISIBLE_ITEMS = 4;
const visibleItems = items.slice(0, MAX_VISIBLE_ITEMS);
```
The same applies to magic strings used as discriminators, keys, or thresholds. In tests, unexplained magic values should also be extracted into named constants so their meaning is clear.
### Static empty defaults
Never define empty arrays or objects inline as default values — a new reference is created on every render, causing unnecessary re-renders or stale hook dependencies.
```ts
// BAD
const items = data ?? [];
// GOOD
const EMPTY_ITEMS: Array<Item> = [];
const items = data ?? EMPTY_ITEMS;
```
Define the constant outside the component or hook.
### useMemo for derived arrays
Wrap `.filter()`, `.map()`, or `.reduce()` results in `useMemo` when the result is passed as a prop or used as a hook dependency. Without memoisation, a new array reference is produced on every render.
```ts
// BAD
const filtered = items.filter(isActive);
return <List items={filtered} />;
// GOOD
const filtered = useMemo(() => items.filter(isActive), [items]);
return <List items={filtered} />;
```
### eslint-disable comments
Every `eslint-disable` comment must include an explanation. Without one, the reason for the suppression is lost and the comment becomes a maintenance hazard.
```ts
// BAD
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// GOOD
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- API response shape is dynamic and validated at runtime
```
### File path string interpolation
Prefer explicit file names over template literals in asset paths. Explicit names are easier to locate with search tools and eliminate accidental runtime errors.
```ts
// BAD
const src = `streak_${days}.png`;
// GOOD — map variants explicitly
const STREAK_IMAGES: Record<number, string> = {
30: 'streak_30.png',
60: 'streak_60.png',
};
```
### Prefer es-toolkit utilities
Before writing custom utility logic (clamping, deep cloning, grouping, etc.), check whether `es-toolkit` already provides it. Prefer the library function over a manual implementation. Documentation: https://es-toolkit.dev/llms-full.txt
### Commented-out code
Remove commented-out code blocks. The git history preserves anything that might be needed later. TODOs are acceptable only when paired with a clear follow-up plan or issue reference.
### Links
- Use `toolkit/chakra/link` instead of `next/link`. Never import `Link` from `next/link` directly.
- When links to **application pages** are constructed, verify that `nextjs-routes` or `nextjs/routes` utilities are used instead of string concatenation or template literals. The full list of application routes is available in `nextjs/nextjs-routes.d.ts`.
### Date and time
- Import `dayjs` only via `lib/date/dayjs.ts` — never directly from the `dayjs` package.
- Render all dates and times through the shared `Time` or `TimeWithTooltip` components. Do not format timestamps inline.
+124
View File
@@ -0,0 +1,124 @@
---
description: Design system and styling rules for the Blockscout frontend
globs: *.tsx,*.ts
alwaysApply: false
---
# Design System
## Stack
The app uses **Chakra UI v3** as its component and styling foundation.
**Documentation:** https://chakra-ui.com/llms.txt — consult this to understand what components Chakra provides and how its styling system works.
## Project configuration
The design system is layered on top of Chakra UI inside `toolkit/`:
| Path | Purpose |
|---|---|
| `toolkit/chakra/` | Custom wrappers for Chakra components — always prefer these over bare Chakra imports |
| `toolkit/theme/theme.ts` | Theme entry point; uses Chakra v3's `createSystem` API to merge defaults with project config |
| `toolkit/theme/foundations/semanticTokens.ts` | Full list of semantic color tokens (text, bg, border, icon, component-level tokens) |
| `toolkit/theme/foundations/colors.ts` | Raw color palette referenced by semantic tokens |
| `toolkit/theme/recipes/` | Component style recipes (slot recipes and simple recipes) |
| `toolkit/components/` | Custom business components (forms, charts, tabs, etc.) built on top of Chakra |
| `toolkit/hooks/` | Shared React hooks (useDisclosure, useClipboard, etc.) |
The `Provider` component at `toolkit/chakra/provider.tsx` wraps `ChakraProvider` with the custom theme and color mode support. It must be mounted at the app root.
## Component import priority
Always check `toolkit/chakra/` before importing from Chakra UI directly. If a custom wrapper exists there, use it — never the bare Chakra component.
```ts
// BAD
import { Button } from '@chakra-ui/react';
// GOOD
import { Button } from 'toolkit/chakra/button';
```
## Colors
Never use raw color values (hex, RGB, HSL). Always reference a token. Three sources are valid:
1. **Semantic tokens** — context-aware, light/dark aware. Full list in `toolkit/theme/foundations/semanticTokens.ts`. Prefer these whenever a semantic meaning exists.
```tsx
<Text color="text.secondary" />
<Box bg="bg.secondary" borderColor="border.divider" />
```
Common groups: `text.*`, `bg.*`, `border.*`, `icon.*`, `link.*`, `button.*`, `badge.*`.
2. **Project color palette** — scale and alpha colors defined in `toolkit/theme/foundations/colors.ts`: `gray`, `blue`, `red`, `orange`, `yellow`, `green`, `teal`, `cyan`, `purple`, `pink` (steps 50–900), `black`, `white`, `whiteAlpha.*`, `blackAlpha.*`.
```tsx
<Box bg="blue.50" color="gray.700" />
```
3. **Brand colors** — also defined in `toolkit/theme/foundations/colors.ts`: `github`, `telegram`, `linkedin`, `discord`, `slack`, `twitter`, `opensea`, `facebook`, `medium`, `reddit`, `celo`, `clusters`.
```tsx
<Icon color="github" />
```
If a raw color value is truly unavoidable (e.g. a third-party embed), leave a comment explaining why.
## Design tokens
The project customizes the following Chakra token categories in `toolkit/theme/`. Always use these tokens instead of raw CSS values:
| Token type | File | Example |
|---|---|---|
| Border radius | `foundations/borders.ts` | `borderRadius="md"` instead of `borderRadius="12px"` |
| Shadows | `foundations/shadows.ts` | `boxShadow="size.md"` instead of a custom `box-shadow` |
| Z-index | `foundations/zIndex.ts` | `zIndex="modal"` instead of a raw number |
| Font weights | `theme.ts` (inline) | `fontWeight="semibold"` instead of `fontWeight={600}` |
| Durations | `foundations/durations.ts` | Use duration tokens for CSS transitions |
| Keyframes | `foundations/animations.ts` | Reference named keyframes for custom animations |
Available `radii` tokens: `none`, `sm` (4px), `base` (8px), `md` (12px), `lg` (16px), `xl` (24px), `full`.
Available `shadows` tokens: `size.xs`, `size.sm`, `size.base`, `size.md`, `size.lg`, `size.xl`, `size.2xl`, `action_bar`, `dark-lg`.
## Text styles
Do not set `fontSize` or `lineHeight` directly. Apply the appropriate `textStyle` token instead — it encodes both properties together along with `fontWeight` and `fontFamily`.
```tsx
// BAD
<Text fontSize="14px" lineHeight="20px">Label</Text>
// GOOD
<Text textStyle="text.sm">Label</Text>
```
Available text styles (defined in `toolkit/theme/foundations/typography.ts`):
| Token | fontSize / lineHeight |
|---|---|
| `heading.xl` | 32px / 40px |
| `heading.lg` | 24px / 32px |
| `heading.md` | 18px / 24px |
| `heading.sm` | 16px / 24px |
| `heading.xs` | 14px / 20px |
| `text.xl` | 20px / 28px |
| `text.md` | 16px / 24px |
| `text.sm` | 14px / 20px |
| `text.xs` | 12px / 16px |
For a regular text block, the `text.` prefix can be omitted.
## Compound component spacing
Do not override the default spacing of **internal parts** of compound components (e.g. adding custom padding to `DialogHeader` inside a `Dialog`, or to a `MenuList` item). The root component itself may be spaced freely; its sub-parts may not.
This rule applies to all components from `toolkit/chakra/`.
## Duplicated style props
Before adding a style prop, check whether the same property is already set by an inherited style or a parent component. Flag and remove redundant re-declarations.
+83
View File
@@ -0,0 +1,83 @@
---
description: Environment variables — where they live, how they're delivered at runtime, validated, and how to add or deprecate them
globs:
alwaysApply: false
---
# Environment Variables
## Reference
All environment variables are documented in `docs/ENVS.md`. This is the authoritative list — every variable must be present there.
## Runtime delivery (not build-time)
Almost all variables are injected at **runtime**, not baked into the Next.js build. Only three variables are true build-time constants, compiled into the image via Docker `ARG`: `NEXT_PUBLIC_GIT_COMMIT_SHA`, `NEXT_PUBLIC_GIT_TAG`, `NEXT_OPEN_TELEMETRY_ENABLED`.
Everything else follows this pipeline:
**1. Build time — `deploy/scripts/collect_envs.sh`**
Scans `docs/ENVS.md` for every `NEXT_PUBLIC_*` name and writes `.env.registry` (a list of expected variable names with placeholder values). This registry is copied into the Docker image and tells the runtime which variables the app expects. **If a variable is not in `docs/ENVS.md`, it will not appear in the registry and will not be delivered to the browser.**
**2. Container startup — `deploy/scripts/entrypoint.sh`**
Runs in sequence:
1. Optionally loads a named preset from `configs/envs/`
2. Downloads external asset files (images, JSON configs) via `download_assets.sh`
3. Validates environment values against the schema (`validate_envs.sh` → `deploy/tools/envs-validator/`)
4. Writes `public/assets/envs.js` via `make_envs_script.sh`:
```js
window.__envs = {
NEXT_PUBLIC_API_HOST: "...",
...
}
```
**3. In the app — `configs/app/utils.ts` `getEnvValue()`**
Reads `window.__envs` when running in the browser, and `process.env` during SSR / build-time evaluation.
## Accessing variables in code
Never read `process.env.NEXT_PUBLIC_*` directly in component or library code. Always go through `configs/app/`, which exposes a frozen, typed config object. The config is split into sections:
| Section | Purpose |
|---|---|
| `app` | App-level settings (host, protocol, env flags) |
| `apis` | Main API and related service endpoints |
| `chain` | Blockchain parameters |
| `UI` | Visual customizations (colors, fonts, layout) |
| `features` | Feature flags |
| `services` | Third-party service integrations |
| `meta` | SEO and meta-tag settings |
## Validation
At container startup, `deploy/tools/envs-validator/` validates the current environment against a [Valibot](https://valibot.dev/) schema defined in `deploy/tools/envs-validator/schema.ts`. The app will not start if validation fails (unless `SKIP_ENVS_VALIDATION=true`).
For complex config shapes, define a **separate sub-schema** (see `tacSchema`, `beaconChainSchema` for examples) rather than inlining everything.
## How to add a new variable
Follow all steps — skipping any one of them will cause the variable to be missing at runtime or fail CI validation.
1. **`docs/ENVS.md`** — document the variable: name, expected type, required/optional, default value, example value. This step is mandatory; without it the runtime script will not deliver the value to the browser.
2. **`configs/app/`** — add a property in the appropriate section file (`app.ts`, `apis.ts`, `chain.ts`, `ui.ts`, `features/`, `services.ts`, `meta.ts`). Read the value via `getEnvValue('NEXT_PUBLIC_...')`. Never use the env var name directly outside `configs/app/`.
3. **`deploy/tools/envs-validator/schema.ts`** — add or update the validation rule. For complex structures, create a dedicated sub-schema.
4. **`deploy/tools/envs-validator/test/.env.base`** — add the variable to the base test preset. If the variable supports alternative configurations, add examples to `.env.alt` as well. Verify locally:
```bash
cd deploy/tools/envs-validator && pnpm test
```
5. **CSP** (if the variable holds an external non-asset URL) — add the domain to the appropriate policy in `nextjs/csp/policies/`. Only add the policy when the relevant config option is enabled.
6. **`deploy/scripts/download_assets.sh`** (if the variable holds an asset URL — image or JSON config) — extend the `ASSETS_ENVS` array with the variable name.
7. **JSON config URL only** — add an example file to `deploy/tools/envs-validator/test/assets/configs/` (filename derived by stripping `NEXT_PUBLIC_` prefix and `_URL` suffix, lowercased — e.g. `NEXT_PUBLIC_MARKETPLACE_CONFIG_URL` → `marketplace_config.json`) and add the variable name to the `envsWithJsonConfig` array in `deploy/tools/envs-validator/index.ts`.
## TBD: Deprecating a variable
> This section is not yet defined. Topics to cover: how to announce deprecation, the grace period before removal, how to update `docs/ENVS.md` and `schema.ts`, and how to handle backwards compatibility for existing deployments.
+8
View File
@@ -0,0 +1,8 @@
---
description: Project domain terms and ubiquitous language — consult docs/GLOSSARY.md
alwaysApply: true
---
# Ubiquitous language (Glossary)
Product and feature codenames used in this codebase are defined in `docs/GLOSSARY.md`. Consult it when you encounter an unfamiliar term (e.g. `tac`, `bens`, `cctx`, `kettle`, `epoch`).
+75
View File
@@ -0,0 +1,75 @@
---
description: Vitest unit tests — purpose, setup, utilities, and conventions
globs: "*.spec.ts,*.spec.tsx"
alwaysApply: false
---
# Unit Tests (Vitest)
## Purpose
Unit tests cover logic that is independent of visual presentation: utility functions, custom hooks, and component behavior (state transitions, conditional rendering, event handling). If a change has no visual output to verify, prefer a Vitest test over a Playwright one — it is faster and cheaper.
## File naming and location
Test files must be named `*.spec.ts` or `*.spec.tsx` and placed alongside the code they test. Run all tests:
```bash
pnpm test:vitest
```
Run a single file:
```bash
pnpm test:vitest path/to/file.spec.ts
```
## Setup
`vitest/setup.ts` runs before each test file and provides:
- **Environment variables** — loaded from `.env.vitest` via dotenv; accessible as `window.__envs` in test code.
- **Fetch mocking** — `vitest-fetch-mock` is initialized globally; all `fetch` calls are interceptable.
`vitest/global-setup.ts` runs once before the entire test suite and loads `.env.vitest`.
## Rendering components
**Never import from `@testing-library/react` directly.** Use the project's custom wrapper instead:
```tsx
import { render, screen } from 'vitest/lib';
```
`vitest/lib.tsx` re-exports everything from `@testing-library/react` and replaces `render` with a custom version that wraps the component in the full app context:
- `QueryClientProvider` (no retry, no window-focus refetch)
- `AppContextProvider`
- `GrowthBookProvider`
- `SocketProvider`
The `wrapper` export is also available if you need to pass it separately to RTL hooks:
```tsx
import { wrapper } from 'vitest/lib';
const { result } = renderHook(() => useMyHook(), { wrapper });
```
## Utilities
**`vitest/utils/flushPromises.ts`** — call after an action that triggers async effects (e.g. a state update followed by a data fetch) to flush all pending microtasks before asserting:
```tsx
import flushPromises from 'vitest/utils/flushPromises';
await userEvent.click(button);
await flushPromises();
expect(screen.getByText('Loaded')).toBeInTheDocument();
```
## Mocking fetch responses
`vitest-fetch-mock` is active globally. Mock responses before the code under test runs:
```tsx
fetchMock.mockResponseOnce(JSON.stringify({ data: 'value' }));
```
Reset mocks between tests using `beforeEach` / `afterEach` if needed.
+189
View File
@@ -0,0 +1,189 @@
---
description: Playwright component visual tests — purpose, setup, fixtures, and conventions
globs: "*.pw.tsx"
alwaysApply: false
---
# Visual Component Tests (Playwright)
## Purpose
Playwright tests verify the visual presentation and interactive behavior of UI components across multiple viewports and color modes. Use them when a change affects what the user sees — layout, styling, conditional UI states.
## File naming and location
Test files must be named `*.pw.tsx` and placed alongside the component they test. Run all tests:
```bash
pnpm test:pw # locally (fast, no Docker)
pnpm test:pw:docker # in Docker (required for screenshot updates)
pnpm test:pw:docker path/to/Component.pw.ts --update-snapshots # Run single file in Docker and update all corresponding screenshots
```
**Never commit screenshots generated by `pnpm test:pw` (local).** Screenshots used in CI must be generated via `pnpm test:pw:docker`, otherwise tests will fail in CI due to rendering differences.
## Test projects
Three projects run against every test file:
| Project | Tag | Browser | Viewport |
|---|---|---|---|
| `default` | _(runs unless excluded with `-@default`)_ | Desktop Chrome | 1200×750 |
| `mobile` | `+@mobile` | iPhone 13 Pro (Safari) | 375×812 |
| `dark-color-mode` | `+@dark-mode` | Desktop Chrome | 1200×750, forced dark |
Tag a test to include or exclude it from a project:
```tsx
test('renders correctly +@mobile', async ({ render }) => { ... });
test('mobile only -@default +@mobile', async ({ render }) => { ... });
test('dark mode variant +@dark-mode', async ({ render }) => { ... });
```
## Imports
**Never import directly from `@playwright/experimental-ct-react`.** Use the project's extended test:
```tsx
import { test, expect } from 'playwright/index';
```
`playwright/index.ts` extends Playwright's `test` with all project fixtures and re-exports everything from `@playwright/experimental-ct-react`.
## Component mounting
Use the `render` fixture — it mounts the component inside `TestApp`, which provides the full provider stack: ChakraProvider, QueryClientProvider, SocketProvider, AppContextProvider, SettingsContextProvider, GrowthBookProvider, WagmiProvider, RewardsContextProvider, and CsvExportContextProvider.
```tsx
test('example', async ({ render }) => {
const component = await render(<MyComponent prop="value" />);
await expect(component).toHaveScreenshot();
});
```
`render` accepts optional second and third arguments to customize `hooksConfig` (mainly used for mocking Next.js router object), `appContext` and `marketplaceContext` props passed to `TestApp`.
## Fixtures
All fixtures are injected via the `test` function. Available fixtures:
### `mockApiResponse`
Intercepts Blockscout API calls. Returns the mocked URL.
```tsx
await mockApiResponse('token', tokenMock, {
pathParams: { hash: '0x...' },
queryParams: { tab: 'holders' },
status: 200, // optional, default 200
times: 1, // optional, intercept N times
});
```
### `mockEnvs`
Sets environment variables for the test (stored in localStorage, read by `getEnvValue`). Use the built-in `ENVS_MAP` for common configs:
```tsx
await mockEnvs([
['NEXT_PUBLIC_ROLLUP_TYPE', 'optimistic'],
['NEXT_PUBLIC_API_HOST', 'localhost'],
]);
```
### `mockFeatures`
Sets feature flags in localStorage:
```tsx
await mockFeatures([
['account', true],
['rewards', false],
]);
```
### `mockAssetResponse`
Routes any URL to serve a file from disk:
```tsx
await mockAssetResponse('https://example.com/logo.png', './playwright/mocks/duck.png');
```
### `mockConfigResponse`
Routes an external config asset URL (JSON or image) derived from an env var name:
```tsx
await mockConfigResponse('NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', 'https://example.com/config.json', content);
```
### `mockRpcResponse`
Mocks RPC calls (typed against viem's `PublicRpcSchema`):
```tsx
await mockRpcResponse([{ method: 'eth_blockNumber', result: '0x1' }]);
```
### `mockContractReadResponse`
Mocks `eth_call` for a specific contract read:
```tsx
await mockContractReadResponse({
abiItem: erc20ABI[0],
args: ['0x123'],
address: '0x00ab',
result: '1000000',
});
```
### `createSocket`
Creates a local WebSocket server on port 3200. Resolves when a client connects:
```tsx
const socket = await createSocket();
// push events via socket
```
### `mockTextAd`
Mocks the text ad provider (Sevio + duck ad data). Call this in tests that render ad-containing layouts to avoid network requests and visual noise.
### `injectMetaMaskProvider`
Injects `window.ethereum` with `isMetaMask: true`:
```tsx
await injectMetaMaskProvider();
```
### `auth` helpers
For tests requiring an authenticated user, use the `contextWithAuth` fixture or call `authenticateUser(context)` to set the API token cookie.
### `mockMultichainConfig`
Injects `window.__multichainConfig` with 5 mock chains (chainA–E). Use in tests that render multichain UI.
## Selectors
Always use semantic selectors — never CSS class names. CSS classes are implementation details and break when styling changes.
```tsx
// BAD
page.locator('.tx-hash-link')
// GOOD
page.getByRole('link', { name: /transaction/i })
page.getByLabel('Address menu')
page.getByText('0x1234…')
page.getByTestId('tx-hash')
```
## Environment and date
`playwright/lib.tsx` runs before every component mount and:
- Mocks `next/router.useRouter()` with a configurable router object.
- Fixes the current date to `2022-11-11T12:00:00Z` via MockDate — all `new Date()` / `Date.now()` calls return this value.
## Mock data
Use existing files from `mocks/` and `playwright/mocks/` rather than hardcoding values inline. The `mocks/` directory contains shared mock data (API responses, entities) used across both Vitest and Playwright tests. `playwright/mocks/` contains Playwright-specific assets (images, JSON files, module mocks).
Extract any non-obvious test values (IDs, amounts, hashes) into named constants above the test suite.
## Conventions
- Do not use the `testFn: TestFixture<...>` pattern. It makes refactoring harder and can bypass Playwright lint rules. Use it only when genuinely sharing fixture logic across multiple suites.
- Keep each test focused on one visual state or interaction.
- Screenshots are the contract — keep them up to date and review diffs carefully in PRs.
@@ -8,7 +8,6 @@ alwaysApply: false
- Use TypeScript for all code
- Prefer interfaces over types
- Implement proper type safety and inference
- Use `satisfies` operator for type validation
## Import types
@@ -80,7 +79,21 @@ const youSayGoodbyeISayHello = <
};
```
Outside of generic functions, use `any` extremely sparingly.
Outside of generic functions, use `any` extremely sparingly. Prefer `unknown` with proper narrowing instead:
```ts
// BAD
function process(value: any) {
return value.name;
}
// GOOD
function process(value: unknown) {
if (typeof value === 'object' && value !== null && 'name' in value) {
return (value as { name: string }).name;
}
}
```
## Default exports
@@ -444,3 +457,54 @@ if (result.ok) {
console.error(result.error);
}
```
## Type assertions and satisfies
Prefer `satisfies` over `as` assertions for type validation. `as` silently widens or narrows the type; `satisfies` validates without losing inference.
```ts
// BAD
const items = response.data as MyType[];
// GOOD
const items = response.data satisfies Array<MyType>;
```
Avoid double-cast coercions (`as unknown as MyType`). If a cast is unavoidable, add a comment explaining why it is safe:
```ts
// BAD
const el = ref.current as unknown as HTMLInputElement;
// GOOD — explain why the double cast is safe
// The portal always renders an <input>, so this cast is guaranteed at runtime.
const el = ref.current as unknown as HTMLInputElement;
```
## Window globals
Never access third-party globals via `(window as any)`. Declare the property inside the existing `declare global { interface Window { ... } }` block in `global.d.ts`:
```ts
// BAD
const analytics = (window as any).analytics;
// GOOD — in global.d.ts
declare global {
interface Window {
analytics: SegmentAnalytics.AnalyticsJS;
}
}
```
## Module type declarations
Use `decs.d.ts` exclusively for untyped third-party module declarations. Do not put other type definitions there.
```ts
// decs.d.ts
declare module 'some-untyped-package';
```
+107
View File
@@ -0,0 +1,107 @@
---
name: arch-migrate
description: Execute a migration task from a GitHub issue through to a PR, or fix unresolved review comments on an existing migration PR. Reads scope and acceptance criteria from the issue body. Use: /arch-migrate <issue-number> (execute) or /arch-migrate fix <pr-number> (fix review comments).
---
You are executing a step of the Blockscout frontend client architecture migration.
## Prerequisites
Follow the **check-github-cli** skill first to ensure `gh` is available and authenticated.
## Mode
**Execute** (`/arch-migrate <issue>`): fetch the GitHub issue and execute the migration through to a PR.
**Fix** (`/arch-migrate fix <pr>`): address unresolved review comments on an existing PR.
---
## Execute mode
### 1. Read context
```bash
gh issue view <issue> --repo blockscout/frontend
```
Also read:
- `client/ARCH_REDESIGN.md` — naming conventions (§2), dependency rules (§8), execution rules (§10)
- `docs/GLOSSARY.md` — domain terminology
The issue body contains the **Scope**, **Findings**, and **Acceptance criteria** for this task. That is your working spec.
### 2. Explore before touching
Read all source files listed in the issue **Scope**. Understand what they export and what tests exist.
### 3. Move files
- Destination comes from the issue Scope and `ARCH_REDESIGN.md §6` migration map.
- Rename files to kebab-case **at move time** — no separate rename PR.
- Component files stay PascalCase. Hook files stay `useCamelCase.ts`. Everything else: kebab-case.
- Extract types/interfaces flagged in the issue **Findings** section into their new `types/api.ts` homes.
### 4. Update all imports in the same PR
- Update every import path across the entire repo.
- For high-fanout files, use a codemod script as a dedicated commit within the branch.
- No re-export shims. No long-lived compatibility aliases.
### 5. Verify dependency rules
- `client/api` must not have runtime imports from `client/slices/*` or `client/features/*`. `import type` is allowed.
- No new import cycles within `client/`. Fix all ESLint errors inside `client/`.
- Warnings in legacy `lib/` and `ui/` paths are expected and do not block the PR.
### 6. Run checks
```
pnpm lint:tsc # must pass
pnpm lint:eslint # must pass within client/; legacy warnings acceptable
```
### 7. Cross-slice dependencies left at old paths
If a dependency is not yet migrated, leave its import at the old path and list it explicitly in the PR description as a follow-up for the relevant future task.
## Branch and PR
- Extract the task ID from the issue title (format: `[Migration] <task-id>: ...`).
- Branch name: `migration/<task-id>-<short-slug>` (e.g. `migration/1-1-client-api`)
- Branch from `main`.
- PR title: `[Migration <task-id>] <task title>`
- PR targets `main`.
- PR body must include:
- `Closes #<issue-number>` — on the first line, so GitHub auto-closes the issue on merge
- What moved and where
- Any codemods run (include the script or command used)
- Cross-slice deps left at legacy paths (list file + old import path)
- Checklist: `pnpm lint:tsc` passing, `pnpm lint:eslint` clean within `client/`
---
## Fix mode
`/arch-migrate fix <pr>` — address unresolved review comments only.
1. Fetch review threads, filtering to unresolved only:
```bash
gh api graphql -f query='
{
repository(owner: "blockscout", name: "frontend") {
pullRequest(number: <pr>) {
reviewThreads(first: 50) {
nodes {
isResolved
path
line
comments(first: 5) {
nodes { body author { login } }
}
}
}
}
}
}'
```
Keep only threads where `isResolved: false`. Skip everything else — the reviewer marks threads resolved when they are no longer relevant.
2. Address each unresolved thread. Push fixes to the existing branch.
3. Summarise which threads were addressed and what changed.
+122
View File
@@ -0,0 +1,122 @@
---
name: arch-research
description: Research a migration task, create a GitHub sub-issue with scope and acceptance criteria, and update the task backlog. Takes a task ID from docs/MIGRATION_TASKS.md, explores the codebase, creates an issue in blockscout/frontend, links it to the parent migration issue, and commits the status update. Use: /arch-research <task-id> (e.g. /arch-research 1-1)
---
You are preparing a migration task for execution by exploring the codebase and producing a well-scoped GitHub issue.
## Prerequisites
Follow the **check-github-cli** skill first to ensure `gh` is available and authenticated.
## Steps
### 1. Find the task
Read `client/MIGRATION_TASKS.md`. Locate the task by the given ID (e.g. `1-1`). Read its **Scope** section.
If the task already has `[~]` or `[x]` status, stop and report that it has already been researched or completed.
Also read:
- `client/ARCH_REDESIGN.md` — conventions and migration map (§2, §4, §6)
- `docs/GLOSSARY.md` — domain terminology
### 2. Explore the codebase
For each source path in the task Scope:
- **Verify the path exists.** If a path is missing, note it — the task description may need correction.
- **Check the destination for conflicts.** Does `client/<target-path>` already contain files that would clash?
- **Classify each file: move or create.** Files that don't yet exist at the destination and aren't in the source need to be created (e.g. a new `types/api.ts` aggregating types from several source files).
- **Flag type extractions.** Look for type definitions or interfaces inside the source files that logically belong to a *different* slice or feature — these must be extracted into the new owner's `types/api.ts` rather than simply moved. Example: a chain-specific sub-type (`arbitrum?`, `scroll?`) embedded in a shared `Transaction` type needs to move to `features/rollup/<type>/types/api.ts`.
### 3. Draft the issue body
```markdown
## Scope
[Exact source → destination file mappings. One line per file or directory.]
## Findings
[Destination conflicts, if any.]
[Files to create rather than move, and what they should contain.]
[Types or interfaces that need extraction — name the type, its current location, and where it belongs after migration.]
[Any other non-obvious work discovered during exploration.]
If no issues were found, write: "No conflicts or extractions identified — straightforward move."
## Acceptance criteria
- [ ] All files moved to target paths per `ARCH_REDESIGN.md §6`
- [ ] All import paths updated repo-wide (no references to old paths remain)
- [ ] Extracted types live in their new slice/feature `types/api.ts`
- [ ] `pnpm lint:tsc` passes
- [ ] `pnpm lint:eslint` clean within `client/` (warnings in legacy paths acceptable)
- [ ] Cross-slice deps left at old paths are explicitly listed in the PR description
```
### 4. Confirm with the user
Before creating anything, present the full draft to the user:
- **Issue title:** `[Migration] <task-id>: <task title>`
- **Issue body:** the complete markdown from step 3
Ask: *"Shall I create this issue? You can request changes before I proceed."*
Wait for explicit confirmation. Apply any requested edits and re-confirm if the changes are significant. Do not proceed to step 5 until the user approves.
### 5. Create the GitHub issue
```bash
gh issue create \
--repo blockscout/frontend \
--title "[Migration] <task-id>: <task title>" \
--body "<issue body from step 4>" \
--label "Task for agent"
```
Capture the issue number from the output URL (e.g. `https://github.com/blockscout/frontend/issues/42` → number is `42`).
### 6. Link to the parent issue
Get the internal numeric `id` of the new issue (this is different from the issue number):
```bash
gh api repos/blockscout/frontend/issues/<new-issue-number> --jq '.id'
```
Link it as a sub-issue of the parent (parent issue number is in the `client/MIGRATION_TASKS.md` header):
```bash
gh api \
--method POST \
repos/blockscout/frontend/issues/<parent-issue-number>/sub_issues \
--field sub_issue_id=<new-issue-id>
```
### 7. Update client/MIGRATION_TASKS.md
In the task entry, make two changes:
- Change status from `[ ]` to `[~]`
- Append the issue link to the task heading line
Example — before:
```
### 1-1 · [ ] Migrate `client/api/`
```
After:
```
### 1-1 · [~] Migrate `client/api/` · [#42](https://github.com/blockscout/frontend/issues/42)
```
Commit directly to `main`:
```bash
git add client/MIGRATION_TASKS.md
git commit -m "track: 1-1 in progress — blockscout/frontend#42"
git push origin main
```
Replace `1-1` and `42` with the actual task ID and issue number.
@@ -1,7 +1,6 @@
---
name: create-pr
description: Create a well-structured pull request with proper description, labels, and reviewers
disable-model-invocation: true
---
# Create PR
+1
View File
@@ -0,0 +1 @@
../.agents/AGENTS.md
+1
View File
@@ -0,0 +1 @@
../.agents/rules
+1
View File
@@ -0,0 +1 @@
../.agents/skills
+10 -39
View File
@@ -4,50 +4,21 @@ In addition to the regular bug-finding process, check the following aspects in t
## 1. Design System & Theming
- Verify that all color values use **semantic color tokens** from the project's design system (`text.secondary`, `border.divider`, `icon.secondary`, `button.solid.bg`, `global.body.bg`, etc.). Flag any hardcoded color values such as `rgb(...)`, `#hex`. The full list of available tokens can be found in `toolkit/theme/foundations/semanticTokens.ts` and `toolkit/theme/foundations/colors.ts`. In rare cases the author may use hardcoded color values, but they must leave a comment explaining why.
- Flag any custom `box-shadow` values. Advise the author to use the design system's shadow tokens instead.
- Check for custom margins or paddings that override defaults of **internal parts** of compound components (e.g., `DialogHeader` inside a `Dialog`). This rule does not apply to the root component itself. If a part component already has default spacing from the design system, it should not be overridden with custom values. This applies to all components from the toolkit (see `toolkit/chakra`).
- Look for duplicated style props — check whether a property is already set by an inherited style prop and flag any redundant re-declarations.
- Refer to the Chakra documentation at [llms.txt](https://chakra-ui.com/llms.txt) to understand what components Chakra UI provides and how the styling system works.
See `.agents/rules/design-system.mdc`.
## 2. TypeScript & Code Quality
## 2. TypeScript
- Check for usage of the `any` type. If found, advise the author to use `unknown` instead and narrow it properly in the code.
- Look for `eslint-disable` comments. If any exist without an accompanying explanation, ask the author to add a comment describing why the rule was disabled. Example: `// eslint-disable-next-line @typescript-eslint/no-explicit-any -- API response shape is dynamic and validated at runtime`.
- Identify magic numbers in the code. Advise the author to extract them into named constants using `UPPER_UNDERSCORE_CASE` and place the constants above the component definition. For example, `const MAX_VISIBLE_ITEMS = 4;` is preferable to an inline `4`.
- Evaluate naming of hooks, features, components, functions, and variables. Names should be specific and self-documenting. Flag vague names like `useWidgets` and suggest more descriptive alternatives such as `useAddress3rdPartyWidgets`.
- Where an `as MyType[]` assertion is used, suggest using `satisfies Array<MyType>` instead, if applicable.
- Check for inline empty arrays or objects used as default values on every render (e.g., `?? []`). Advise the author to define a static constant outside the component (e.g., `const EMPTY_ARRAY: Array<T> = [];`) and reference it instead.
- Check whether `.filter()`, `.map()`, or `.reduce()` calls produce new array references that are passed as props or used as hook dependencies. If so, advise the author to wrap them in `useMemo`.
- Flag unnecessary string interpolation in file paths. Explicit file names (e.g., `streak_30.png`) are preferable to template literals (e.g., `` `streak_${days}.png` ``), as they are easier to locate with search tools.
See `.agents/rules/typescript.mdc`.
## 3. Environment Variables
## 3. Code Quality
When a PR adds, changes, renames, or removes an environment variable, verify that **all** of the following steps have been completed:
See `.agents/rules/code-quality.mdc`.
- The variable is documented in `docs/ENVS.md` with its name, expected type, compulsoriness, default value, and an example value.
- The variable is added to the app config in the appropriate section of `configs/app/` (`features/`, `ui.ts`, `api.ts`, etc.).
- A validation schema is added or updated in `deploy/tools/envs-validator/schema.ts`. For complex configs, a **separate sub-schema** should be created (similar to `tacScheme` or `beaconChainSchema`).
- The variable is added to the test presets in `deploy/tools/envs-validator/test/.env.base`. If the variable supports alternative configurations, examples should also be added to `.env.alt`.
- If the variable contains an external URL that is **not** an asset URL (i.e., not an image or JSON file), the appropriate CSP policy in `nextjs/csp/policies/` is updated. Policies should only be added when the relevant config option is actually enabled.
- If the variable links to an external resource (image or JSON config), the `ASSETS_ENVS` array in `deploy/scripts/download_assets.sh` is extended with the variable name.
- If the variable stores a JSON config URL, an example file is added to `deploy/tools/envs-validator/test/assets/configs/` and the `envsWithJsonConfig` array in `deploy/tools/envs-validator/index.ts` is extended.
- The PR description clearly states that a new ENV variable was added and includes example values.
## 4. Environment Variables
## 4. Component Architecture & Reuse
See `.agents/rules/env-vars.mdc`.
- When links to **application pages** are constructed, verify that `nextjs-routes` or `nextjs/routes` utilities are used instead of string concatenation or template literals. The full list of application routes is available in `nextjs/nextjs-routes.d.ts`.
## 5. Testing
## 5. Testing with Playwright
- Flag any tests that rely on CSS class selectors (`.className`). These will break when the implementation changes. Advise the author to use roles, test IDs, text content, or other semantic selectors instead.
- Flag unnecessary use of the `testFn` pattern (i.e., `const testFn: TestFixture<...> = async (...)`). This pattern makes refactoring harder and can bypass Playwright lint rules. It should only be used when genuinely sharing test logic across multiple test suites.
- Check for unexplained magic values in test data. If a test uses a specific number or string, its meaning should be clear. Advise the author to use named constants (e.g., `const BLOCK_HEIGHT = 3947;` instead of a bare `3947`).
- Check for hardcoded URLs or values that already exist in mock files. Advise the author to import and reference them from the existing mocks to keep tests DRY and consistent.
## 6. Utilities & Best Practices
- Check whether any utility logic could be replaced by an `es-toolkit` function (e.g., `clamp`, `get`). Advise the author to prefer existing utilities over manual implementations.
- Flag unsafe type coercions such as `as unknown as MyType`. If coercion is necessary, the author should add runtime validation or a comment explaining why the cast is safe.
- Check that date and time formatting uses the shared project utilities (`Time` or `TimeWithTooltip` components). The project maintains a single consistent date format.
- Flag any commented-out code blocks. Advise the author to either implement the feature or remove the dead code entirely. TODOs are acceptable only when there is a clear follow-up plan.
- Unit tests: See `.agents/rules/tests-unit.mdc`.
- Visual component tests: See `.agents/rules/tests-visual.mdc`.
-37
View File
@@ -1,37 +0,0 @@
---
description:
globs: *.tsx,*.ts
alwaysApply: false
---
You are an expert senior software engineer specializing in modern web development, with deep expertise in TypeScript, React 19, Next.js 15 (Pages Router), Chakra UI, and React Query. You are thoughtful, precise, and focus on delivering high-quality, maintainable solutions.
## Code Style and Structure
### General Principles
- Write concise, readable TypeScript code
- Use functional and declarative programming patterns; avoid classes
- Prefer iteration and modularization over code duplication
- Implement early returns for better readability
- Structure components logically: exports, subcomponents, helpers, types
### Naming Conventions
- Use descriptive names with auxiliary verbs (isLoading, hasError)
- Prefix event handlers with "handle" (handleClick, handleSubmit)
- Favor default exports for React components
### TypeScript Usage
- Use TypeScript for all code
- Prefer interfaces over types
- Avoid enums; use const maps instead
- Implement proper type safety and inference
- Use `satisfies` operator for type validation
### UI and Styling
- Utilize Chakra UI v3 components for consistent design
- **Component Import Priority**: Always check if a component exists in `toolkit/chakra/**` before importing from Chakra UI. If a custom version exists in `toolkit/chakra/**`, import from there instead of the native Chakra component. Only use default Chakra components when no custom version is available in `toolkit/chakra/**`.
-102
View File
@@ -1,102 +0,0 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Commands
```bash
# Development
pnpm dev # Start dev server
pnpm build # Production build (Next.js only)
pnpm build:next # Full build with asset downloading and sprite building
# Linting
pnpm lint:eslint # ESLint check
pnpm lint:eslint:fix # ESLint auto-fix
pnpm lint:tsc # TypeScript type check
pnpm lint:cspell # Spell check
pnpm lint:envs-validator:test # Validate environment variable schemas
# Testing
pnpm test:vitest # Unit tests (Vitest, matches **/*.spec.ts(x))
pnpm test:pw # Playwright component/E2E tests (matches *.pw.tsx)
pnpm test:pw:docker # Playwright tests in Docker
# Assets
pnpm svg:build-sprite # Rebuild SVG sprite
pnpm chakra:typegen # Regenerate Chakra UI types
```
**Requirements:** Node >=22.14.0, pnpm (see `package.json` `engines` / `packageManager`)
## Architecture
**Stack:** Next.js 15 (Pages Router, not App Router), React 19, Chakra UI v3, React Query 5, Wagmi 2 / Viem 2, Valibot for schema validation, Vitest + Playwright for testing.
**Key directories:**
- `pages/` — Next.js page components (require default exports)
- `ui/` — React UI components organized by feature (~65 subdirectories)
- `lib/` — Business logic, API utilities, custom hooks, context providers
- `toolkit/` — Design system layer: `toolkit/chakra/` (custom Chakra components), `toolkit/theme/` (semantic color tokens), `toolkit/hooks/`
- `configs/app/` — Runtime app configuration (features, API endpoints, UI settings)
- `nextjs/` — Next.js config utilities: headers, rewrites, redirects, type-safe routes via `nextjs-routes`
- `mocks/` — Mock data for tests
- `deploy/tools/envs-validator/` — Environment variable validation schema and tests
**Data flow:** Pages use React Query for server state. Global UI state lives in React Context providers (`AppContextProvider`, `SettingsContextProvider`, etc.) initialized in `pages/_app.tsx`. WebSocket real-time data flows through `SocketProvider`.
**Routing:** Use `nextjs-routes` / `nextjs/routes` utilities for constructing links to application pages — never string concatenation. The full route list is in `nextjs/nextjs-routes.d.ts`.
## Design System Rules
These are enforced by ESLint and must be followed:
- **Always import from `toolkit/chakra/**`** before falling back to native Chakra UI. If a custom version exists in `toolkit/chakra/`, use it.
- **Never use hardcoded colors** (RGB, hex). Use semantic color tokens from `toolkit/theme/foundations/semanticTokens.ts` and `toolkit/theme/foundations/colors.ts` (e.g., `text.secondary`, `border.divider`, `icon.secondary`).
- **No custom `box-shadow`** — use design system shadow tokens.
- Don't override spacing on internal parts of compound components (e.g., don't add custom padding to `DialogHeader` inside a `Dialog`).
- Use `toolkit/chakra/link` instead of `next/link`.
- Use `lib/date/dayjs.ts` instead of importing `dayjs` directly.
- Date/time rendering must use the shared `Time` or `TimeWithTooltip` components.
## Global Type Declarations
- **Never use `(window as any)`** to access third-party globals. Instead, declare the property in `global.d.ts` inside the existing `declare global { interface Window { ... } }` block.
- Use `decs.d.ts` only for untyped third-party module declarations (`declare module 'foo'`).
## TypeScript Conventions
- Prefer `interface` over `type`. Use `interface extends` over `&` intersection (performance).
- No `enum` — use `as const` objects instead.
- Use top-level `import type { Foo }` not inline `import { type Foo }`.
- Default exports only when required by the framework (Next.js pages). All other exports are named.
- Declare return types on top-level module functions.
- `readonly` properties by default; omit only when genuinely mutable.
- Use `satisfies` for type validation instead of `as MyType[]` assertions.
- Outside generic functions, use `any` extremely sparingly; prefer `unknown` with proper narrowing.
- Extract magic numbers as `UPPER_SNAKE_CASE` constants above the component definition.
- Define empty array/object defaults as static constants outside components (not inline `?? []`).
- Wrap `.filter()`, `.map()`, `.reduce()` results in `useMemo` when passed as props or used as hook deps.
- Type parameters in generics are prefixed with `T` (e.g., `TKey`, `TValue`).
## Adding Environment Variables
When adding, renaming, or removing an environment variable, all of the following must be updated:
1. `docs/ENVS.md` — document name, type, whether required, default, and example
2. `configs/app/` — add to the appropriate section (`features/`, `ui.ts`, `api.ts`, etc.)
3. `deploy/tools/envs-validator/schema.ts` — add/update validation schema
4. `deploy/tools/envs-validator/test/.env.base` — add to test presets
5. `nextjs/csp/policies/` — update CSP if the variable references an external (non-asset) URL
6. `deploy/scripts/download_assets.sh` — add to `ASSETS_ENVS` if it's an asset URL
7. If it's a JSON config URL: add example to `deploy/tools/envs-validator/test/assets/configs/` and extend `envsWithJsonConfig` in `deploy/tools/envs-validator/index.ts`
## Testing
**Vitest** (unit): Files named `*.spec.ts` / `*.spec.tsx`. Run a single file with `pnpm test:vitest path/to/file.spec.ts`.
**Playwright** (component/E2E): Files named `*.pw.tsx`. Three test projects run against each test: `default` (desktop Chrome 1200×750), `mobile` (iPhone 13 Pro), `dark-color-mode`. Tag tests with `@mobile` or `@dark-mode` to target specific projects; use `-@default` to exclude desktop.
- Use roles, test IDs, and text content as selectors — never CSS class selectors.
- Import mock values from existing files in `mocks/` rather than hardcoding them.
- Avoid the `testFn: TestFixture<...>` pattern unless sharing logic across multiple suites.
@@ -1,6 +1,4 @@
# Client architecture blueprint (draft — round 2)
Living document: extend after further design rounds. Intended for **human engineers and agents** implementing the migration.
# Client architecture blueprint
---
@@ -24,9 +22,9 @@ Aligned with **Next.js-style** paths: **kebab-case for directories** and for **n
| **Helper / util modules** (non-hook) | `kebab-case.ts` | `format-tx-hash.ts`, `get-tab-list.ts` |
| **Role files** | lowercase | `types.ts`, `mocks.ts`, `stubs.ts` |
Same folder may contain `TxDetails.tsx`, `use-tx-query.ts` — hooks stay **`useCamelCase.ts`**; everything else non-component prefers **kebab-case**.
Same folder may contain `TxDetails.tsx`, `useTxQuery.ts` — hooks stay **`useCamelCase.ts`**; everything else non-component prefers **kebab-case**.
**No `index.ts` barrel files inside `/client`.** Deep imports are preferred; barrels slow TypeScript server performance and break tree-shaking at scale.
**No re-export-only barrel files inside `/client`.** Deep imports are preferred; dumb barrels (files that only `export * from ...`) slow TypeScript server performance and break tree-shaking at scale. Aggregation files that define or curate their own public surface (e.g. `client/api/services/general/index.ts` acting as a facade for the General API) are acceptable where they provide genuine value.
---
@@ -86,13 +84,17 @@ Application **chrome**: layout, error UI, header, footer, page title, top-bar, a
Keep the current organization largely intact. Do not flatten everything into a 1:1 mapping with slices — feature services often have only one or two resources and do not warrant separate files.
`general/` maps to the **Blockscout General API namespace** — it is not restricted to vanilla-EVM resources. Files inside it follow the API grouping, not the slice/feature taxonomy.
```text
client/api/
services/
general/ ← core API resources; only split files inside where genuinely needed
general/ ← Blockscout General API namespace; only split files inside where genuinely needed
tx.ts
block.ts
misc.ts ← resources with no clear single-domain home stay here
rollup.ts ← rollup resources are part of the General API
v1.ts ← previous API version; one active resource
...
rewards.ts ← feature-specific APIs stay flat alongside general/
stats.ts
@@ -145,15 +147,26 @@ client/slices/tx/
- Folders may exist for **readability and maintenance** (e.g. per rollup type) even when not everything is enabled for a given chain; **runtime behavior** still comes from **config/env**, not "folder exists = on."
**Confirmed features (non-exhaustive):** `user-ops`, `blobs`, `multichain`, `name-domains`, `account`, `stats`, `gas-tracker`, `epochs`, `validators`, `marketplace`, `rewards`, `rollup/*`, …
**Confirmed features (non-exhaustive):** `user-ops`, `data-availability`, `multichain`, `name-domains`, `account`, `stats`, `gas-tracker`, `validators`, `marketplace`, `rewards`, `rollup/*`, `chain-variants/*`, `advanced-filter`, `ad-banner`, `safe-address-tags`, `metasuites`, `csv-export`, …
- **`stats` and `gas-tracker`** are **features** (not slices) — they are config-gated per chain.
- **`epochs` and `validators`** are **features** — chain-specific, not present on a vanilla EVM chain.
- **`validators`** is a **feature** — chain-specific, not present on a vanilla EVM chain.
- **`epochs`** is a sub-concern of the Celo chain variant (`features/chain-variants/celo/`), not a standalone feature.
- **`data-availability`** was previously referred to as `blobs` — use `data-availability` throughout.
**Classification rules:**
1. *"Can this area exist on a vanilla EVM chain with no feature flag?"* Yes → slice. No → feature.
2. **Config-gated infrastructure with no user-facing UI** (analytics providers, error monitoring, A/B flag providers) lives in **`client/shared/`**, not `features/` — the presence of a config flag alone is not sufficient to qualify for a feature folder.
3. Every config-gated feature with user-facing UI gets its own folder under `features/`, regardless of size — consistency and discoverability outweigh folder count.
**Chain-specific non-rollup features** — chain variants that are not rollups (ZetaChain, TAC, SUAVE, Celo, MegaETH, Beacon chain, etc.) are grouped under **`features/chain-variants/<name>/`**, parallel to `features/rollup/<type>/`. Example: `features/chain-variants/celo/`, `features/chain-variants/tac/`, `features/chain-variants/zeta-chain/`.
**Variant / chain-specific transaction lists** (e.g. kettle, zeta-specific list UIs) live under **`features/<name>/pages/...`**, **not** as variants inside `slices/tx`.
**Rollups:** `features/rollup/<rollup-type>/` (e.g. `optimism`, `arbitrum`, …) — **organized by rollup type**, because **views and helpers differ by rollup**. Sub-areas (deposits, batches, withdrawals) are **subfolders** under that rollup, not separate top-level rollup taxonomies.
**Chain variants (non-rollup):** `features/chain-variants/<name>/` (e.g. `celo`, `tac`, `zeta-chain`, `mega-eth`, `suave`, `beacon-chain`) — same sub-folder structure as rollups.
**Typical feature shape:** same idea as slices — `pages/`, `components/`, `hooks/`, `utils/`, `types/`, `mocks.ts`, `stubs.ts`.
### 4.5 `client/shared`
@@ -176,7 +189,7 @@ client/slices/tx/
| `hooks/` | Generic hooks from `lib/hooks/` with no single domain owner |
| `i18n/` | `lib/setLocale.ts` |
| `links/utils/` | `lib/utils/stripUtmParams.ts` and URL-related helpers |
| `lists/` | `lib/hooks/useLazyRenderedList`, `lib/getItemIndex.ts` |
| `lists/` | `lib/hooks/useLazyRenderedList`, `lib/hooks/useInitialList`, `lib/getItemIndex.ts` |
| `metadata/` | `lib/metadata/` (centralized — see note below) |
| `monitoring/rollbar/` | `lib/rollbar/` (client-side observability infrastructure) |
| `router/` | `lib/router/` + query-param filter helpers (`getFilterValueFromQuery`, etc.) |
@@ -231,8 +244,15 @@ client/slices/tx/
| `lib/hooks/useNavItems` | `client/shell/` |
| `lib/hooks/useTimeAgoIncrement` | `client/shared/date-and-time/` |
| `lib/hooks/useLazyRenderedList` | `client/shared/lists/` |
| `lib/hooks/useInitialList` | `client/shared/lists/` |
| `lib/hooks/useRewardsActivity` | `client/features/rewards/` |
| `lib/hooks/useAddressProfileApiQuery` | `client/slices/address/` |
| `lib/hooks/useFetch` | `client/api/` |
| `lib/hooks/useGetCsrfToken` | `client/features/account/` |
| `lib/hooks/useGraphLinks` | `client/features/marketplace/` |
| `lib/hooks/useAdblockDetect` | `client/features/ad-banner/` |
| `lib/hooks/useIsSafeAddress` | `client/features/safe-address-tags/` |
| `lib/hooks/useNotifyOnNavigation` | `client/features/metasuites/` |
| `lib/hooks/` (remaining) | `client/shared/hooks/` |
| `lib/contexts/app.tsx`, `fallback.tsx` | `client/shell/` |
| `lib/contexts/settings.tsx` | `client/shell/top-bar/` |
@@ -281,6 +301,7 @@ client/slices/tx/
## 8. Dependency rules (high level)
- **ESLint rules (`import/no-cycle` + explicit `boundaries` rules for `client/api`, `configs`, `toolkit`) must be enabled _before_ the migration starts.** Running them against the existing code surfaces real problem areas cheaply, before they become migration blockers.
- **Enforcement strategy: warn on legacy, error on new.** Configure boundary rules to emit **errors** only within `client/`; legacy `lib/` and `ui/` paths receive **warnings** only. This avoids a costly upfront clean-up of the entire codebase while ensuring every migrated file is immediately held to the new standard.
- **Guidelines:**
- **`client/api`** allows **`import type`** from slices/features for response-shape typing; **no runtime imports** (no logic, hooks, or components — types only).
- **Slice → feature type imports** are intentional: the backend includes feature-specific fields (e.g. `arbitrum?`, `scroll?`) in API responses unconditionally when the rollup is enabled — the frontend type must mirror the full API contract. UI component imports follow the same direction (e.g. `TxDetails` imports `TxDetailsArbitrum`).
@@ -312,19 +333,23 @@ client/slices/tx/
### Rules during migration
- Update **all imports in the same PR** as the file move — no shims.
- Each PR leaves ESLint green; do not merge with new cycle violations.
- `pages/` thin wrappers: **dynamic import only** — `getServerSideProps` helpers stay in `nextjs/`; SEO metadata lives in the slice/feature page folder.
- Update **all imports in the same PR** as the file move — no shims. For high-fanout files use an automated codemod as a dedicated commit within the branch.
- **Rename to kebab-case at move time** — file moves and naming convention changes happen in one pass, never two separate PRs.
- Each PR leaves ESLint green within `client/`; do not merge with new cycle/boundary violations inside `client/`.
- **`types/api.ts` is the stable external type surface** for each slice/feature. External consumers (other slices, features, `client/api/services/`) must import types only from `<slice|feature>/types/api.ts`, never from deeper internal paths. Enforced via ESLint `boundaries`.
- `pages/` thin wrappers: **no UI component or business logic** — a dynamic import and, if needed, a `getServerSideProps` inline (per-page logic may stay inline; only shared/reusable SSR helpers move to `nextjs/`). SEO metadata lives in the slice/feature page folder.
- **Tx pilot PR strategy:** migrate the tx slice end-to-end (UI, hooks, utils, types, mocks) while leaving cross-slice dependencies (address, rollup types, etc.) at their old `lib/` paths. Every remaining old-path import in `client/slices/tx/` after the pilot becomes an explicit migration task for the relevant slice/feature PR.
---
## 11. Open topics (round 3)
## 11. Open topics (round 4)
- [ ] **Path aliases** — `client/*` imports; document in `tsconfig.json` / CONTRIBUTING.
- [ ] **Public type surfaces** — document **thin** export files (e.g. `slices/tx/types/api.ts`) to avoid deep cross-slice imports.
- [ ] **Storybook / toolkit** — if any stories live under `client`, how they resolve paths to `toolkit`.
- [ ] **`lib/hooks/` full audit** — confirm every remaining hook in `lib/hooks/` that is not explicitly listed above goes to `client/shared/hooks/`.
- [ ] **`lib/api/services/general/` audit** — identify any files inside `general/` that should be split out (beyond `tx.ts`, `block.ts`, etc.) now that slices own their types.
- [x] ~~**Path aliases**~~ — `baseUrl: "."` in `tsconfig.json` makes `client/**` absolute imports work without explicit `paths` entries. No action needed.
- [x] ~~**Public type surfaces**~~ — `types/api.ts` is the stable external surface per slice/feature; documented in §10. Cross-slice `types/client.ts` imports left as an open question to resolve with real examples during implementation.
- [x] ~~**Storybook / toolkit**~~ — project does not use Storybook. Topic closed.
- [x] ~~**`lib/hooks/` full audit**~~ — all hooks explicitly placed; see §6 migration map.
- [x] ~~**`lib/api/services/general/` audit**~~ — all files stay under `general/` (General API namespace). No splits needed beyond existing file boundaries.
- [x] ~~**Project glossary**~~ — a `GLOSSARY.md` (or section in CONTRIBUTING) mapping internal codenames to their product/feature meaning (e.g. `tac` → Ton Application Chain, `blobs`/`data-availability`, `cctx` → cross-chain transactions, `epochs` → Celo epoch pages). Aids both engineers and agents navigating the codebase.
---
@@ -370,5 +395,3 @@ client/
```
---
*End of round 2 blueprint.*
+324
View File
@@ -0,0 +1,324 @@
# Architecture Migration — Task Backlog
Progress tracker for the `client/` architecture redesign.
Blueprint: `client/ARCH_REDESIGN.md`. Terminology: `docs/GLOSSARY.md`.
**Task ID format:** `<stage>-<index>` — stage number + sequential index within that stage.
Adding tasks to a stage: append the next index; no renumbering needed.
**Status:** `[ ]` to do · `[~]` in progress · `[x]` done
**Base branch:** `main` — each task branches from `main`; PRs target `main`.
**Parent issue:** [`blockscout/frontend#3341`](https://github.com/blockscout/frontend/issues/3341)
> `/arch-research <task-id>` creates the sub-issue and marks the task `[~]`.
> `/arch-migrate <issue>` executes the task and opens a PR.
> Change `[~]` → `[x]` after the PR is merged.
---
## Stage 0 — Pre-migration
### 0-1 · [ ] Rename `configs/app/features/` files to kebab-case
**Scope:** Rename every `.ts` file under `configs/app/features/` from camelCase to kebab-case
(e.g. `userOps.ts` → `user-ops.ts`, `adsBanner.ts` → `ads-banner.ts`).
Update all import paths repo-wide. Named exports inside files are unchanged — filenames only.
**PR:** —
---
### 0-2 · [ ] Enable ESLint `import/no-cycle` + `boundaries` rules
**Scope:** Add `eslint-plugin-boundaries` (if not already installed). Configure rules so that:
- Imports within `client/` emit **errors** on violations.
- Imports in legacy `lib/` and `ui/` emit **warnings** only.
Fix all pre-existing errors (there should be none in `client/` yet). Confirm warnings in legacy code are expected and do not block CI.
**PR:** —
---
## Stage 1 — Foundation
### 1-1 · [ ] Migrate `client/api/`
**Scope:** Move `lib/api/**` → `client/api/**` (preserve `services/` structure).
Additional moves in the same PR:
- `lib/socket/` → `client/api/socket/`
- `lib/hooks/useFetch.ts` → `client/api/`
- `lib/api/services/utils.ts` → `client/api/types.ts`
Update all repo-wide imports. Verify `client/api` has no runtime imports from `client/slices/*` or
`client/features/*` (`import type` is permitted).
**PR:** —
---
### 1-2 · [ ] Migrate `client/shared/`
**Scope:** Move all cross-cutting utilities from `lib/` to `client/shared/<subfolder>/`.
Key moves (see `ARCH_REDESIGN.md §6` for full list):
| Source | Destination |
|--------|-------------|
| `lib/mixpanel/` | `client/shared/analytics/` |
| `lib/rollbar/` | `client/shared/monitoring/rollbar/` |
| `lib/growthbook/` | `client/shared/feature-flags/` |
| `lib/networks/` + `lib/units.ts` | `client/shared/chain/` (rename `network` → `chain` throughout) |
| `lib/router/` + filter-value helpers | `client/shared/router/` |
| `lib/errors/` + `lib/getErrorMessage.ts` | `client/shared/errors/` |
| `lib/web3/` | `client/shared/web3/` |
| `lib/metadata/` | `client/shared/metadata/` |
| `lib/hooks/useTimeAgoIncrement` | `client/shared/date-and-time/` |
| `lib/hooks/useLazyRenderedList`, `useInitialList`, `lib/getItemIndex.ts` | `client/shared/lists/` |
| `lib/cookies.ts` | `client/shared/storage/` |
| `lib/decodeJWT.ts` | `client/shared/auth/` |
| `lib/setLocale.ts` | `client/shared/i18n/` |
| `lib/capitalizeFirstLetter.ts`, `shortenString.ts`, `escapeRegExp.ts`, `highlightText.ts` | `client/shared/text/` |
| `lib/base64ToHex.ts` and other hex/bytes helpers | `client/shared/transformers/` |
| `lib/utils/stripUtmParams.ts` | `client/shared/links/utils/` |
| `lib/delay.ts`, `lib/isMetaKey.tsx` | `client/shared/utils/` |
| Remaining generic hooks from `lib/hooks/` | `client/shared/hooks/` |
Note: `lib/monitoring/` (server-side) → `nextjs/`, not `client/shared/`.
**PR:** —
---
## Stage 2 — Pilot slice
### 2-1 · [ ] Pilot: migrate `slices/tx` end-to-end
**Scope:** Full migration of the `tx` slice. Canonical template for all subsequent slices.
- `lib/tx/` → `client/slices/tx/utils/`
- `lib/api/services/general/tx.ts` → `client/api/services/general/tx.ts` (if not already done in 1-1)
- `ui/tx/**` → `client/slices/tx/` (restructure into `pages/`, `components/`, `hooks/`, `utils/`, `types/`, `mocks.ts`, `stubs.ts`)
- Move associated `types/api/tx.ts` → `client/slices/tx/types/api.ts`
- Move associated `mocks/` and `stubs/` entries
Cross-slice dependencies (address types, rollup sub-types, etc.) may remain at their old `lib/` paths
after this PR — note each as an explicit follow-up in the PR description.
**PR:** —
---
## Stage 3 — Core slices
Each slice follows the template established in 2-1.
### 3-1 · [ ] Slice: `block`
**Scope:** `lib/block/` (if any), `ui/block/**`, related `types/api/`, `mocks/`, `stubs/` → `client/slices/block/`
**PR:** —
### 3-2 · [ ] Slice: `address`
**Scope:** `lib/address/`, `ui/address/**`, related types/mocks/stubs, `lib/hooks/useAddressProfileApiQuery`, `lib/contexts/addressHighlight.tsx` → `client/slices/address/`
**PR:** —
### 3-3 · [ ] Slice: `search`
**Scope:** `lib/search/`, `ui/snippets/searchBar/`, `lib/recentSearchKeywords.ts`, related types/mocks/stubs → `client/slices/search/`
**PR:** —
### 3-4 · [ ] Slice: `token`
**Scope:** `lib/token/`, `ui/token/**`, related types/mocks/stubs → `client/slices/token/`
**PR:** —
### 3-5 · [ ] Slice: `contract`
**Scope:** `lib/contracts/`, `lib/solidityScan/`, `ui/contract/**`, related types/mocks/stubs → `client/slices/contract/`
**PR:** —
### 3-6 · [ ] Slice: `internal-tx`
**Scope:** `ui/internalTxs/**` (and any `lib/` counterparts), related types/mocks/stubs → `client/slices/internal-tx/`
**PR:** —
### 3-7 · [ ] Slice: `home`
**Scope:** `ui/home/**`, related types/mocks/stubs → `client/slices/home/`
**PR:** —
### 3-8 · [ ] Slice: `tokens` (token list)
**Scope:** `ui/tokens/**`, related types/mocks/stubs → `client/slices/tokens/`
**PR:** —
### 3-9 · [ ] Slice: `accounts`
**Scope:** `ui/accounts/**`, related types/mocks/stubs → `client/slices/accounts/`
**PR:** —
### 3-10 · [ ] Slice: `token-instance`
**Scope:** `ui/tokenInstance/**`, related types/mocks/stubs → `client/slices/token-instance/`
**PR:** —
---
## Stage 4 — Features: rollups
One PR per rollup type. Each goes under `client/features/rollup/<type>/`.
> Enumerate additional rollup tasks when this stage begins, based on what exists under `lib/rollups/` and `ui/`.
### 4-1 · [ ] Feature: `rollup/optimism`
**Scope:** All Optimism-specific UI, hooks, utils, types → `client/features/rollup/optimism/`. Includes fault proof system and dispute games.
**PR:** —
### 4-2 · [ ] Feature: `rollup/arbitrum`
**Scope:** All Arbitrum-specific UI, hooks, utils, types → `client/features/rollup/arbitrum/`
**PR:** —
### 4-3 · [ ] Feature: `rollup/zk-sync`
**Scope:** zkSync-specific UI, hooks, utils, types → `client/features/rollup/zk-sync/`. Check for other zk-based rollup types (scroll, polygon-zk-evm, etc.) and add tasks if needed.
**PR:** —
---
## Stage 5 — Features: chain variants
One PR per chain variant. Each goes under `client/features/chain-variants/<name>/`.
### 5-1 · [ ] Feature: `chain-variants/celo`
**Scope:** All Celo-specific UI and logic including epochs → `client/features/chain-variants/celo/`. See `configs/app/features/celo.ts`.
**PR:** —
### 5-2 · [ ] Feature: `chain-variants/tac`
**Scope:** TAC operations and bridge UI → `client/features/chain-variants/tac/`. See `configs/app/features/tac.ts`.
**PR:** —
### 5-3 · [ ] Feature: `chain-variants/zeta-chain`
**Scope:** ZetaChain CCTX UI → `client/features/chain-variants/zeta-chain/`. See `configs/app/features/zetachain.ts`.
**PR:** —
### 5-4 · [ ] Feature: `chain-variants/suave`
**Scope:** SUAVE Kettle UI → `client/features/chain-variants/suave/`. See `configs/app/features/suave.ts`.
**PR:** —
### 5-5 · [ ] Feature: `chain-variants/mega-eth`
**Scope:** MegaETH Flashblocks UI → `client/features/chain-variants/mega-eth/`. See `configs/app/features/megaEth.ts` and `flashblocks.ts`.
**PR:** —
### 5-6 · [ ] Feature: `chain-variants/beacon-chain`
**Scope:** Beacon chain deposits/withdrawals UI → `client/features/chain-variants/beacon-chain/`. See `configs/app/features/beaconChain.ts`.
**PR:** —
---
## Stage 6 — Features: standalone
One PR per feature. Features that are pure infrastructure (analytics, monitoring, A/B flags) were
migrated to `client/shared/` in 1-2 and do not appear here.
### 6-1 · [ ] Feature: `user-ops`
**PR:** —
### 6-2 · [ ] Feature: `data-availability`
**PR:** —
### 6-3 · [ ] Feature: `multichain`
**Scope:** Includes `lib/multichain/`, `lib/contexts/multichain.tsx`, `ui/snippets/networkMenu/` → `client/features/multichain/`
**PR:** —
### 6-4 · [ ] Feature: `name-domains` (BENS)
**Scope:** `configs/app/features/nameServices.ts` — use `name-domains` as folder name.
**PR:** —
### 6-5 · [ ] Feature: `account`
**Scope:** Includes `lib/hooks/useGetCsrfToken`, `ui/snippets/auth/`, `ui/snippets/user/` → `client/features/account/`
**PR:** —
### 6-6 · [ ] Feature: `stats`
**Scope:** `lib/stats/` and stats UI → `client/features/stats/`
**PR:** —
### 6-7 · [ ] Feature: `gas-tracker`
**PR:** —
### 6-8 · [ ] Feature: `validators`
**PR:** —
### 6-9 · [ ] Feature: `marketplace`
**Scope:** Includes `lib/contexts/marketplace.tsx`, `lib/hooks/useGraphLinks` → `client/features/marketplace/`
**PR:** —
### 6-10 · [ ] Feature: `rewards`
**Scope:** Includes `lib/contexts/rewards.tsx`, `lib/hooks/useRewardsActivity` → `client/features/rewards/`
**PR:** —
### 6-11 · [ ] Feature: `advanced-filter`
**PR:** —
### 6-12 · [ ] Feature: `ad-banner`
**Scope:** Includes `lib/hooks/useAdblockDetect` → `client/features/ad-banner/`. Covers both `adsBanner.ts` and `adsText.ts` configs.
**PR:** —
### 6-13 · [ ] Feature: `safe-address-tags`
**Scope:** Includes `lib/hooks/useIsSafeAddress` → `client/features/safe-address-tags/`. See `configs/app/features/safe.ts`.
**PR:** —
### 6-14 · [ ] Feature: `metasuites`
**Scope:** Includes `lib/hooks/useNotifyOnNavigation` → `client/features/metasuites/`
**PR:** —
### 6-15 · [ ] Feature: `csv-export`
**PR:** —
### 6-16 · [ ] Feature: `pools`
**PR:** —
### 6-17 · [ ] Feature: `hot-contracts`
**PR:** —
### 6-18 · [ ] Feature: `interchain-indexer`
**Scope:** Cross-chain message indexer (not ZetaChain). See `configs/app/features/crossChainTxs.ts`.
**PR:** —
### 6-19 · [ ] Feature: `mud-framework`
**PR:** —
### 6-20 · [ ] Feature: `visualize`
**Scope:** Solidity-to-UML diagrams. See `configs/app/features/sol2uml.ts`.
**PR:** —
### 6-21 · [ ] Feature: `tx-interpretation`
**PR:** —
### 6-22 · [ ] Feature: `public-tags`
**Scope:** Community address labels. See `configs/app/features/publicTagsSubmission.ts`.
**PR:** —
### 6-23 · [ ] Feature: `address-widgets`
**Scope:** Third-party widgets on address pages. See `configs/app/features/address3rdPartyWidgets.ts`.
**PR:** —
### 6-24 · [ ] Feature: `address-metadata`
**PR:** —
### 6-25 · [ ] Feature: `address-verification`
**Scope:** Covers `addressProfileAPI.ts` and `addressVerification.ts` configs.
**PR:** —
### 6-26 · [ ] Feature: `bridged-tokens`
**PR:** —
### 6-27 · [ ] Feature: `web3-wallet`
**Scope:** Includes `blockchainInteraction.ts` config.
**PR:** —
> Other small features (`externalTxs`, `xStarScore`, `deFiDropdown`, `getGasButton`, `easterEgg*`, `apiDocs`, `verifiedTokens`, `multichainButton`) — enumerate as separate tasks or group with related features when this stage begins.
---
## Stage 7 — Shell
### 7-1 · [ ] Migrate `client/shell`
**Scope:** Migrate after all slices and features it depends on are in place.
- `ui/snippets/header/`, `footer/`, `navigation/`, `topBar/` → `client/shell/`
- `lib/contexts/app.tsx`, `fallback.tsx` → `client/shell/`
- `lib/contexts/settings.tsx` → `client/shell/top-bar/`
- `lib/hooks/useNavItems` → `client/shell/`
**PR:** —
---
## Stage 8 — Cleanup
### 8-1 · [ ] Remove legacy root directories
**Scope:** Confirm `lib/`, `ui/`, `mocks/`, `stubs/`, `types/` are empty (no remaining unconverted files).
Delete them. Fix any remaining lint warnings that referenced legacy paths.
**PR:** —
+1
View File
@@ -258,6 +258,7 @@
"utka",
"utko",
"UUPS",
"Valibot",
"VCALENDAR",
"vercel",
"verifreg",
+65
View File
@@ -0,0 +1,65 @@
# Ubiquitous Language Glossary
Domain terms used in the Blockscout frontend codebase. Intended for both engineers and agents.
Terms are listed alphabetically within each section. Where a codename differs from the product name, both are shown.
---
## Architecture
Terms specific to how this codebase is structured. Read this section first when navigating the repo or `ARCH_REDESIGN.md`.
| Term | Definition |
|------|------------|
| **Chain Variant** | A non-rollup chain with custom UI or domain entities (e.g. Celo, TAC, SUAVE, MegaETH, ZetaChain). Grouped under `features/chain-variants/<name>/`. Distinct from a **Rollup**, which implies an L1/L2 settlement relationship. |
| **Feature** | An optional, config-gated product area (e.g. `stats`, `marketplace`, `user-ops`). Corresponds roughly to a flag in `configs/app/features/`. Lives under `client/features/`. Contrast with **Slice**. |
| **Rollup** | A chain that settles transactions on a parent (L1) chain. Introduces specific entities: deposits, withdrawals, transaction batches, output roots. Organized by rollup type under `features/rollup/<type>/` (e.g. `optimism`, `arbitrum`, `zk-sync`). Contrast with **Chain Variant**. |
| **Slice** | A core explorer domain entity that is always present on any EVM chain, regardless of feature flags (e.g. `tx`, `block`, `address`, `token`). Lives under `client/slices/`. Contrast with **Feature**. |
---
## Domain entities
Blockchain objects that appear as first-class UI elements — detail pages, list rows, API resources, stubs.
| Term | Definition |
|------|------------|
| **Blob** | An individual EIP-4844 data blob attached to a transaction. A single transaction can carry multiple blobs. Each blob has its own detail page (`/blob/[hash]`) and is a first-class entity in the UI (with its own components, API resource, and stub). The term "blob" remains correct for the entity — only the *feature folder* uses `data-availability` (see **Data Availability**). |
| **CCTX (Cross-Chain Transaction)** | A ZetaChain-specific transaction type that spans multiple chains. Displayed as a separate tab on the transactions list page. Unrelated to the general cross-chain transactions feature (`interchainIndexer`). |
| **Dispute Games** | Part of the Optimism **Fault Proof System**. On-chain games used to challenge and resolve disputed L2 output roots. Displayed in the rollup navigation when `faultProofSystem` is enabled. |
| **Epoch** | A consensus time period specific to **Celo**. Has its own index and detail pages in the UI. Not a generic blockchain concept in this codebase — always refers to a Celo epoch. Lives under `features/chain-variants/celo/`. |
| **Interop Messages** | Cross-rollup messages passed between OP Stack chains using the native interoperability protocol. Visible in rollup navigation when `NEXT_PUBLIC_INTEROP_ENABLED` is set. Distinct from **Interchain Indexer** messages. Currently in deprecated state. |
| **Kettle** | In the **SUAVE** architecture, a Kettle is a trusted execution environment (TEE) node that processes MEV bundles. Transactions on SUAVE are associated with a Kettle, and the UI exposes a per-Kettle transaction list at `/txs/kettle/[hash]`. |
| **Operation** | A **TAC**-specific entity representing a bridge operation between the TON and EVM ecosystems. Has no equivalent on standard EVM chains. Exposed at `/operations` and `/operation/[id]`. |
| **User Op (User Operation)** | An ERC-4337 account abstraction operation — a transaction-like object submitted to a bundler rather than directly to the network. Displayed in a dedicated list at `/ops` and detail page at `/op/[hash]`. Config-gated via `NEXT_PUBLIC_HAS_USER_OPS`. |
---
## Features & integrations
Optional product areas and their backing services. Each corresponds to a folder under `client/features/`.
| Term | Definition |
|------|------------|
| **Advanced Filter** | A UI feature for filtering transactions using complex multi-criteria queries (address, method, token, amount ranges, etc.). Distinct from the basic filter controls present on most list pages. Config-gated via `NEXT_PUBLIC_ADVANCED_FILTER_ENABLED`. |
| **Beacon Chain** | The Ethereum Proof-of-Stake consensus layer. When enabled, exposes beacon chain deposits and withdrawals alongside regular transactions. Not a rollup — it is the Ethereum L1 consensus mechanism. |
| **BENS (Blockscout Name Service)** | Blockscout's own address naming service, chain-agnostic and operated by Blockscout. Distinct from ENS (which is Ethereum-specific). In the codebase also referred to as `name-services` or `name-domains`. |
| **Clusters** | An address identity and grouping service. Aggregates multiple addresses under a named cluster (individual, protocol, organization). Exposes a directory, leaderboard, and per-address lookup. Backed by the Clusters API (`NEXT_PUBLIC_CLUSTERS_API_HOST`). |
| **Data Availability** | The config-gated *feature* that surfaces **Blob** data posted to Ethereum (EIP-4844). `data-availability` is the name used for the feature folder and config flag — the underlying entity is still called a **Blob**. Config-gated via `NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED`. |
| **Fault Proof System** | Optimism's mechanism for proving the correctness of L2 state transitions on L1 via **Dispute Games**. Enabled by the `NEXT_PUBLIC_FAULT_PROOF_ENABLED` feature flag. |
| **Flashblocks** | MegaETH's sub-second block streaming mechanism. Delivers real-time pre-confirmation block data via WebSocket. Specific to the MegaETH chain variant. |
| **Hot Contracts** | A ranked list of the most recently and frequently interacted-with smart contracts on the network. Config-gated via `NEXT_PUBLIC_HOT_CONTRACTS_ENABLED`. |
| **Interchain Indexer** | A microservice that indexes cross-chain messages and token transfers across heterogeneous chains (not ZetaChain-specific — a general interoperability indexer). Distinct from **CCTX**. Backed by `NEXT_PUBLIC_CROSS_CHAIN_TXS_ENABLED`. |
| **Marketplace** | A curated directory of dApps and DeFi applications integrated with Blockscout. Config-gated via `NEXT_PUBLIC_MARKETPLACE_ENABLED`. Has its own full-page UI with categories and app detail views. |
| **MetaSuites** | A third-party browser extension that enhances the Blockscout UI with additional data and links. The frontend notifies it about route changes via a dedicated hook. |
| **MUD Worlds** | Instances of the MUD framework — an on-chain autonomous world / game engine. Chains that use MUD expose a list of active worlds. Config-gated via `mudFramework` feature flag. |
| **Multichain** | The feature that aggregates data across multiple Blockscout-indexed chains into a single explorer view. Introduces an "Ecosystems" page and a multichain balance button. Config-gated via `NEXT_PUBLIC_MULTICHAIN_ENABLED`. |
| **Pools** | DEX liquidity pool positions tracked on-chain. Exposed as a "DEX tracker" section under Tokens. Config-gated via `NEXT_PUBLIC_DEX_POOLS_ENABLED`. |
| **Public Tags** | Community-submitted labels for addresses, visible on address pages. Submitted via a dedicated form. Config-gated via `NEXT_PUBLIC_METADATA_SERVICE_API_HOST`. |
| **Rewards** | A token rewards and incentives program operated by Blockscout. Has its own API, context provider, and UI modals. Config-gated via `NEXT_PUBLIC_REWARDS_SERVICE_API_HOST`. |
| **SolidityScan** | A third-party smart contract security vulnerability scanner integrated into contract detail pages. Displays a security score and known vulnerability findings. |
| **SUAVE** | A MEV-focused chain developed by Flashbots, built around a trusted execution environment (TEE) architecture. Introduces the **Kettle** entity. |
| **TAC (Ton Application Chain)** | A chain that bridges the TON blockchain and EVM ecosystems. Introduces the **Operation** entity. Its data comes from a dedicated `tac-operation-lifecycle` microservice. Config-gated via `NEXT_PUBLIC_TAC_OPERATION_LIFECYCLE_API_HOST`. |
| **Validators** | The set of active block validators on chains that expose this concept (e.g. zkSync Era, Celo). Displayed in a dedicated list. Not present on standard PoW/PoS EVM chains without explicit support. Config-gated via `NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE`. |
| **Visualize** | A service that converts Solidity source code into UML diagrams (class and storage layout). Accessed from contract detail pages. Backed by `NEXT_PUBLIC_VISUALIZE_API_HOST`. |
| **Watchlist** | An **Account** feature that lets authenticated users track a set of addresses and receive notifications for their activity. |
+1 -1
View File
@@ -1,3 +1,3 @@
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M16 17a1 1 0 1 1 0 2H4a1 1 0 0 1 0-2h12ZM10 1.11c.525 0 .95.332.95.741v10.046c.001.212.257.317.406.168l4.022-4.022a.95.95 0 0 1 1.343 1.345l-6.048 6.049a.951.951 0 0 1-1.345 0l-6.05-6.049a.951.951 0 0 1 1.345-1.345l4.02 4.022c.15.15.406.044.407-.168V1.851c0-.409.425-.741.95-.741Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M16 17a1 1 0 1 1 0 2H4a1 1 0 0 1 0-2h12ZM10 1.11c.525 0 .95.332.95.741v10.046a.238.238 0 0 0 .406.168l4.022-4.022a.95.95 0 0 1 1.343 1.345l-6.048 6.049a.951.951 0 0 1-1.345 0l-6.05-6.049a.951.951 0 0 1 1.345-1.345l4.02 4.022c.15.15.406.044.407-.168V1.851c0-.409.425-.741.95-.741Z" fill="currentColor"/>
</svg>

Before

Width:  |  Height:  |  Size: 435 B

After

Width:  |  Height:  |  Size: 434 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB