From b2562f156c6b75ffb94e2103a94e65e355215713 Mon Sep 17 00:00:00 2001 From: Mati OS Date: Tue, 12 May 2026 11:40:47 -0300 Subject: [PATCH 1/2] docs(claude): add testing rules and link from index MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds .claude/rules/testing.md documenting the Vitest + Playwright testing model — unit test config, the two Playwright projects (chromium / mocked), the e2e/tests/ tree organisation, hermetic vs live trade-offs, and CI workflow mapping. Updates CLAUDE.md to reference testing.md in the modular instructions index, patterns.md to add a "Testing Patterns" section pointing at mocked vs live conventions, and commands.md with the focused per-suite e2e runners and the mocked Playwright project command. --- .claude/CLAUDE.md | 1 + .claude/rules/commands.md | 14 +++++ .claude/rules/patterns.md | 16 ++++++ .claude/rules/testing.md | 116 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 147 insertions(+) create mode 100644 .claude/rules/testing.md diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 53d7e058..b4605dd0 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -20,4 +20,5 @@ Detailed instructions are organized in the rules directory: @.claude/rules/code-style.md - Code style, formatting, and quality requirements @.claude/rules/workflow.md - Git workflow, branches, PRs, and issues @.claude/rules/patterns.md - Important coding patterns for this codebase +@.claude/rules/testing.md - Vitest + Playwright testing model (projects, suites, mocking) @.claude/rules/i18n.md - Internationalization guidelines and best practices diff --git a/.claude/rules/commands.md b/.claude/rules/commands.md index 1bdcd4be..097bc72e 100644 --- a/.claude/rules/commands.md +++ b/.claude/rules/commands.md @@ -53,12 +53,21 @@ npm run test # Run e2e tests (Playwright) — both `chromium` (live) and `mocked` projects npm run test:e2e +# Run only the Ethereum mainnet suite +npm run test:e2e:eth-mainnet + +# Run only the EVM L2 networks suite (Arbitrum, Base, Optimism, BSC, Polygon, Avalanche) +npm run test:e2e:evm-networks + # Run a single spec file npx playwright test e2e/tests/shared/errors.spec.ts # Run only the chromium project (skips hermetic `shared/mocked/` specs) npx playwright test --project=chromium +# Run only the mocked project (hermetic specs under e2e/tests/shared/mocked/) +npx playwright test --project=mocked + # Run e2e tests with UI npm run test:e2e:ui @@ -66,6 +75,11 @@ npm run test:e2e:ui npm run test:e2e:debug ``` +E2E tests are organised by chain family under [e2e/tests/](../../e2e/tests/): +`bitcoin/`, `eth-mainnet/`, `evm-networks/`, `shared/`, `solana/`, `testnets/`. +See [testing.md](testing.md) for the full testing model (projects, sharding, +mocking). + ## Test Environment with Local Node ```bash diff --git a/.claude/rules/patterns.md b/.claude/rules/patterns.md index ca8a37ba..24ebd72e 100644 --- a/.claude/rules/patterns.md +++ b/.claude/rules/patterns.md @@ -61,6 +61,22 @@ OpenScan includes special support for localhost development: - Use `--text-primary`, `--text-secondary`, `--text-tertiary` for text colors - Never hardcode `rgba(255, 255, 255, X)` - use CSS variables instead +## Testing Patterns + +See [testing.md](testing.md) for the full model. Two patterns matter most when +adding tests: + +- **Hermetic specs go under `e2e/tests/shared/mocked/`** — the `mocked` + Playwright project picks up only that subtree and uses `page.route` to + intercept RPC and worker traffic. Use this for any test that asserts on + RPC strategy (`fallback` / `parallel` / `race`), provider inconsistency + flags, or error/edge-case paths. These tests are deterministic and don't + flake. +- **Live-RPC specs go under `e2e/tests//`** (e.g. `eth-mainnet/`, + `evm-networks/`, `bitcoin/`) — never under `shared/mocked/`, since the + default `chromium` project explicitly ignores that subtree. Live specs + are sharded per network in CI so flakes in one chain don't block others. + ## Logger Utility Always use the logger utility instead of `console.*` methods for runtime logging. diff --git a/.claude/rules/testing.md b/.claude/rules/testing.md new file mode 100644 index 00000000..77656ca0 --- /dev/null +++ b/.claude/rules/testing.md @@ -0,0 +1,116 @@ +# Testing + +OpenScan has two test layers: Vitest for unit tests and Playwright for end-to-end +browser tests against a real Vite dev server. Most test commands also live in +[commands.md](commands.md); this file documents the *model* — projects, suites, +mocking, and CI mapping — so future test work lands in the right place. + +## Unit tests (Vitest) + +- Config: [vitest.config.ts](../../vitest.config.ts) +- Environment: `happy-dom`, thread pool, globals enabled +- File patterns: `src/**/*.test.ts`, `src/**/*.spec.ts` +- Co-locate unit tests next to the file under test +- Commands: + ```bash + npm run test # watch mode + npm run test:run # single pass (use this in CI / one-shot checks) + ``` + +## E2E tests (Playwright) + +- Config: [playwright.config.ts](../../playwright.config.ts) +- Base URL: `http://localhost:3030`. The `webServer` block auto-starts + `npm run start` if no server is already running (CI starts a fresh one; + local runs reuse an existing dev server). +- Defaults: 60s timeout, trace-on-first-retry, screenshot-on-failure, headless, + 3 retries in CI / 1 retry locally, 1 worker in CI. + +### Two Playwright projects + +``` +chromium → live RPC. Excludes **/shared/mocked/**. +mocked → hermetic. Only matches **/shared/mocked/**/*.spec.ts. +``` + +`npm run test:e2e` runs both projects. Use `--project=chromium` or +`--project=mocked` to scope a run. + +### Test tree + +E2E specs live under [e2e/tests/](../../e2e/tests/) and are organised by +chain family: + +| Directory | What's in it | +|-----------|--------------| +| `bitcoin/` | Bitcoin mainnet + Testnet4 specs | +| `eth-mainnet/` | Ethereum mainnet (block, blocks, transaction, txs, address, token) | +| `evm-networks/` | L2s + alt-EVMs (Arbitrum, Base, Optimism, BSC, Polygon, Avalanche, l2-fields) | +| `shared/` | Cross-network specs (live RPC) | +| `shared/mocked/` | Hermetic specs picked up by the `mocked` project | +| `solana/` | Solana specs | +| `testnets/` | Sepolia / testnet-only specs | + +### Hermetic vs live + +- **Live (`chromium`)**: hits real RPC providers. Subject to rate limits and + upstream flakiness. CI shards these per network to keep wall-clock low. +- **Mocked (`mocked`)**: uses `page.route` to intercept RPC and worker + traffic. Use this for any test that asserts on strategy, fallback, + inconsistency flags, error paths, or large/unusual transactions where + determinism matters more than realism. + +When in doubt, choose mocked — it's faster and won't flake. Only put a spec +in a live folder when the test value depends on real chain state. + +### Per-network runners + +Iterating on a feature that only touches one chain family? Use the focused +script and skip the rest: + +```bash +npm run test:e2e:eth-mainnet # Ethereum-only +npm run test:e2e:evm-networks # L2s + alt-EVMs +``` + +### Single spec / debug / UI + +```bash +# One spec +npx playwright test e2e/tests/shared/errors.spec.ts + +# Step through a spec interactively +npm run test:e2e:debug + +# Time-travel UI mode +npm run test:e2e:ui +``` + +## CI mapping + +Each suite has a dedicated workflow under [.github/workflows/](../../.github/workflows/); +they're sharded so a single flaky live-RPC suite doesn't block the others: + +| Workflow | Triggered by | +|----------|--------------| +| `e2e-eth-mainnet.yml` | Ethereum mainnet suite | +| `e2e-evm-networks.yml` | EVM L2 / alt-EVM suite | +| `e2e-bitcoin.yml` | Bitcoin suite | +| `e2e-solana.yml` | Solana suite | +| `e2e-testnets.yml` | Testnet suite | +| `e2e-shared.yml` | Reusable workflow consumed by the others | +| `e2e-all.yml` | Orchestrates all suites | +| `e2e-nightly.yml` | Scheduled nightly run of `e2e-all` | + +If a PR only touches Ethereum logic, the eth-mainnet workflow is the +canonical signal. The full `e2e-all` is for release-blocking checks. + +## Before pushing tests + +1. `npm run test:run` — unit tests pass +2. `npm run test:e2e:` — at minimum, the suite covering the area you + changed +3. New live-RPC specs go under `eth-mainnet/`, `evm-networks/`, etc. — never + under `shared/mocked/` +4. New deterministic specs go under `e2e/tests/shared/mocked/` so the + `mocked` Playwright project picks them up From cefb7e08af6ee7bd5e7b6ecd9d560b8ae2777c56 Mon Sep 17 00:00:00 2001 From: Mati OS Date: Tue, 12 May 2026 11:41:24 -0300 Subject: [PATCH 2/2] docs(claude): expand commands, architecture, and quick reference CLAUDE.md: pin the Bun version (1.1.0) and add quick-reference rows for `check`, unit/e2e test runners, and `npm run dev`. architecture.md: enumerate every adapter with chain IDs (split out BNB/Polygon/Solana, list Arbitrum/OP-Stack testnets), point at the adapters factory in source, and document the worker/ companion sub-project (Hono RPC proxy, Cloudflare/Vercel/Deno deployment). commands.md: document `build:development`, plain `build`, `preview`, `check`, `audit` (with audit.sh note about the temporary lockfile), and `publish:dist`. --- .claude/CLAUDE.md | 6 ++++- .claude/rules/architecture.md | 46 +++++++++++++++++++++++++++-------- .claude/rules/commands.md | 30 +++++++++++++++++++++++ 3 files changed, 71 insertions(+), 11 deletions(-) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index b4605dd0..c66f5e35 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -4,12 +4,16 @@ OpenScan is a trustless, open-source, standalone web-app and multi-chain blockch ## Quick Reference -- **Package Manager**: Bun +- **Package Manager**: Bun 1.1.0 (lockfile: `bun.lock`, pinned via the `packageManager` field in `package.json`). The repo's documented commands use `npm run` because they invoke `package.json` scripts — `bun run` works equivalently. - **Bundler**: Vite - **Dev Server**: `npm start` (http://localhost:3030) - **Type Check**: `npm run typecheck` - **Format**: `npm run format:fix` - **Lint**: `npm run lint:fix` +- **Combined Biome check**: `npm run check` +- **Test (unit)**: `npm run test:run` +- **Test (e2e)**: `npm run test:e2e` (or `test:e2e:eth-mainnet` / `test:e2e:evm-networks` for focused runs) +- **Local node + explorer**: `npm run dev` ## Modular Instructions diff --git a/.claude/rules/architecture.md b/.claude/rules/architecture.md index 382a61dc..43ee06d9 100644 --- a/.claude/rules/architecture.md +++ b/.claude/rules/architecture.md @@ -13,13 +13,18 @@ Typed RPC clients for blockchain communication: ### 2. Adapter Layer (`services/adapters/`) Abstract `NetworkAdapter` base class with chain-specific implementations: -- `EVMAdapter` - Default EVM adapter (Ethereum, BSC, Polygon, Sepolia) -- `ArbitrumAdapter` - Adds `l1BlockNumber`, `sendCount`, `sendRoot` -- `OptimismAdapter` / `BaseAdapter` - Adds L1 fee breakdown (`l1Fee`, `l1GasPrice`, `l1GasUsed`) +- `EVMAdapter` - Default EVM adapter (Ethereum mainnet 1, Sepolia 11155111, Avalanche 43114 + Fuji 43113) +- `ArbitrumAdapter` - Arbitrum One (42161) + Sepolia (421614). Adds `l1BlockNumber`, `sendCount`, `sendRoot` +- `OptimismAdapter` - Optimism (10) + Sepolia (11155420). Adds L1 fee breakdown (`l1Fee`, `l1GasPrice`, `l1GasUsed`) +- `BaseAdapter` - Base (8453) + Sepolia (84532). Adds L1 fee breakdown +- `BNBAdapter` - BSC mainnet (56) + testnet (97) +- `PolygonAdapter` - Polygon (137) + Amoy testnet (80002) - `HardhatAdapter` - Localhost (31337) with trace support via struct log conversion -- `BitcoinAdapter` - Bitcoin networks with UTXO model, mempool, and block explorer +- `BitcoinAdapter` - Bitcoin networks (bip122:*) with UTXO model, mempool, and block explorer +- `SolanaAdapter` - Solana networks - Each adapter implements: `getBlock`, `getTransaction`, `getAddress`, `getNetworkStats`, trace methods -- `AdapterFactory` routes chain ID to the correct adapter +- `AdapterFactory` routes chain ID to the correct adapter via three entry points: + `createAdapter` (EVM), `createBitcoinAdapter`, `createSolanaAdapter` ### 3. Service Layer (`DataService.ts`) Orchestrates data fetching with caching and metadata: @@ -41,13 +46,18 @@ Global state management: ## Network-Specific Handling -Chain ID detection in `AdapterFactory` determines which adapter to instantiate: +Chain ID detection in `AdapterFactory` determines which adapter to instantiate +(see [src/services/adapters/adaptersFactory.ts](../../src/services/adapters/adaptersFactory.ts)): -- **Arbitrum** (42161): `ArbitrumAdapter` - adds `l1BlockNumber`, `sendCount`, `sendRoot` -- **Bitcoin** (bip122:*): `BitcoinAdapter` - UTXO model, mempool transactions, block rewards -- **OP Stack** (10, 8453): `OptimismAdapter` (10), `BaseAdapter` (8453) - adds L1 fee breakdown (`l1Fee`, `l1GasPrice`, `l1GasUsed`) +- **Arbitrum** (42161, 421614): `ArbitrumAdapter` - adds `l1BlockNumber`, `sendCount`, `sendRoot` +- **OP Stack — Optimism** (10, 11155420): `OptimismAdapter` - adds L1 fee breakdown (`l1Fee`, `l1GasPrice`, `l1GasUsed`) +- **OP Stack — Base** (8453, 84532): `BaseAdapter` - adds L1 fee breakdown +- **BSC** (56, 97): `BNBAdapter` +- **Polygon** (137, 80002): `PolygonAdapter` +- **Bitcoin** (bip122:*): `BitcoinAdapter` - UTXO model, mempool transactions, block rewards (constructed via `createBitcoinAdapter`) +- **Solana**: `SolanaAdapter` (constructed via `createSolanaAdapter`) - **Hardhat** (31337): `HardhatAdapter` - uses `HardhatClient` from `@openscan/network-connectors`; trace support via struct log conversion (`buildCallTreeFromStructLogs`, `buildPrestateFromStructLogs` in `src/utils/structLogConverter.ts`) since Hardhat v3 does not support `callTracer`/`prestateTracer` -- **Default**: `EVMAdapter` for Ethereum (1), BSC (56, 97), Polygon (137), Sepolia (11155111), Avalanche (43114) +- **Default EVM**: `EVMAdapter` for Ethereum (1), Sepolia (11155111), Avalanche (43114, 43113) ## Key Type Definitions @@ -68,3 +78,19 @@ Located in `src/types/index.ts`: - `OPENSCAN_COMMIT_HASH` - Git commit hash - `OPENSCAN_NETWORKS` - Comma-separated chain IDs to display - `OPENSCAN_ENVIRONMENT` - production/development + +## Companion Sub-Project: `worker/` + +Separate Hono-based RPC proxy at [worker/](../../worker/), deployable to +Cloudflare Workers, Vercel Edge Functions, or Deno Deploy. Routes browser +requests to upstream RPC providers (Alchemy, Infura, dRPC, Ankr, OnFinality), +the Etherscan V2 verification API, the Beacon API for blob sidecars, and Groq +for AI analysis. Includes CORS, rate limiting, and method allow-listing. + +- Single Hono app in `worker/src/index.ts` shared across all platforms; each + platform has a thin entry point (`api/index.ts` for Vercel, + `src/entry-deno.ts` for Deno, `wrangler.toml` for Cloudflare). +- Has its own `package.json`, `tsconfig.json`, and `deno.json`. Linted by the + root `biome.json` config (worker sources are inside Biome's scope). +- Frontend automatically falls over between Cloudflare and Vercel deployments + for redundancy. diff --git a/.claude/rules/commands.md b/.claude/rules/commands.md index 097bc72e..2683cf2f 100644 --- a/.claude/rules/commands.md +++ b/.claude/rules/commands.md @@ -16,6 +16,15 @@ npm run build:production # Staging build npm run build:staging +# Development-mode build (sourcemaps, OPENSCAN_ENVIRONMENT=development) +npm run build:development + +# Plain Vite build (no env wrapper) +npm run build + +# Preview the built dist/ on http://localhost:3030 +npm run preview + # Output: dist/ ``` @@ -39,6 +48,9 @@ npm run lint # Fix linting issues automatically npm run lint:fix + +# Combined Biome check (format + lint, max 1024 diagnostics) +npm run check ``` ## Testing @@ -88,6 +100,24 @@ npm run dev # Creates hardhat-test-artifacts.zip for importing ABIs ``` +## Security Audit + +```bash +# Run npm audit at moderate level +# (audit.sh generates a temporary package-lock.json since this repo uses bun) +npm run audit +``` + +Backed by [scripts/audit.sh](../../scripts/audit.sh). + +## Publishing + +```bash +# Publish the built dist/ to npm under the @alpha tag +# Uses dist-package.template.json for the published package metadata +npm run publish:dist +``` + ## Individual Script Execution ```bash