Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -20,4 +24,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
46 changes: 36 additions & 10 deletions .claude/rules/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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

Expand All @@ -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.
44 changes: 44 additions & 0 deletions .claude/rules/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/
```

Expand All @@ -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
Expand All @@ -53,19 +65,33 @@ 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

# Run e2e tests in debug mode
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
Expand All @@ -74,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
Expand Down
16 changes: 16 additions & 0 deletions .claude/rules/patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<network>/`** (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.
Expand Down
116 changes: 116 additions & 0 deletions .claude/rules/testing.md
Original file line number Diff line number Diff line change
@@ -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:<suite>` — 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