This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Note: This document must be kept in strict sync with AGENTS.md. Any change here should be mirrored there verbatim.
Seamless Protocol - Frontend application for a DeFi protocol that wraps complex leverage strategies into simple ERC-20 tokens. Built for IPFS deployment as a fully static, client-side application.
CRITICAL: Anvil is the default backend for all integration and E2E tests.
- Uses local Anvil fork for fast, reliable testing without API quotas
- Tenderly VNet available as alternative with
--backend=tenderlyflag - Tests automatically create/delete VNet instances when using Tenderly (JIT)
# Development
bun dev # Start dev server on http://localhost:3000
bun build # Build for production (IPFS-ready)
bun preview # Preview production build
# Code Quality (run after making changes)
bun check:fix # Auto-fix linting issues and type-check
bun check # Check only (for CI)
bun format # Format code with Biome
bun typecheck # Type-check only
# Testing
bun run test # Run unit tests with Vitest
bun run test:ui # Run tests with UI
bun run test:coverage # Run tests with coverage
bun run test:integration # Run integration tests (uses Anvil by default)
bun run test:e2e # Run E2E tests with Playwright (uses Anvil by default)
# Advanced Testing
bun run test:integration:raw # Integration tests directly via Vitest (no backend harness)
bun run test:integration:tenderly # Integration tests against Tenderly JIT VNet (advanced)
# Testing Backend Configuration
# DEFAULT: Local Anvil fork
# bun run anvil:mainnet # Start Mainnet fork on port 8545
# - Fast, reliable testing without API quotas
# - Uses rich holder impersonation for token funding
#
# ALTERNATIVE: Tenderly VNet (just-in-time creation/deletion)
# - Use --backend=tenderly flag to switch to Tenderly
# - Set TENDERLY_ACCESS_KEY, TENDERLY_ACCOUNT, TENDERLY_PROJECT environment variables
# - Tests automatically create/delete VNet instances
# Component Development (needs setup)
bun storybook # Start Storybook on port 6006 (needs setup)
bun build-storybook # Build Storybook static site (needs setup)
# ShadCN UI Components
bunx --bun shadcn@latest add [component] # Add new UI componentsImportant: Always run bun check:fix after making code changes to ensure formatting and type safety.
- Hash routing required - TanStack Router must use hash history
- Relative paths only -
base: './'in vite.config.ts is CRITICAL - Static bundle - Pure client-side application
- Wagmi v2 + Viem - For all blockchain interactions
- RainbowKit - Wallet connection UI
- Multi-chain ready - Base (primary) + Ethereum (future)
- Data hierarchy:
- On-chain RPC (current state)
- Subgraph (historical data)
- External APIs (only when necessary)
- Single source:
src/lib/contractscentralizes ABIs and addresses. - Addresses:
src/lib/contracts/addresses.tsexports typed maps bychainIdplus helpers likeSTAKED_SEAMandgetLeverageManagerAddress. - Governance helpers: Use
getGovernanceAddresses(chainId)orgetRequiredGovernanceAddresses(chainId)for governor/timelock bundles. - ABIs: Minimal, pruned ABIs in
src/lib/contracts/abis/*to keep bundle size small. - Re-exports: Import via
@/lib/contractsto access addresses, ABIs, and wagmi codegen. - Features: Keep UI-only constants in
src/features/<feature>, import on-chain config fromsrc/lib/contracts. - Tests: Prefer mocking
@/lib/contracts/addressesin unit tests; avoid duplicating addresses. - Swap routing policy: Production leverage tokens (anything without
isTestOnly) must default to LiFi for Router v2 debt/collateral conversions with bridges disabled. Test-only/Tenderly definitions keep explicit Uniswap V3 pool configs so integration specs stay deterministic. When adding tokens, setswapsinsrc/features/leverage-tokens/leverageTokens.config.tsaccordingly.
- Canonical Base addresses remain the production default; do not edit
addresses.tswith fork-specific values. - Supply fork-specific maps (Tenderly, Anvil, etc.) via
VITE_CONTRACT_ADDRESS_OVERRIDESas a JSON string keyed bychainId(nested objects merge with the defaults). - Example:
export VITE_CONTRACT_ADDRESS_OVERRIDES='{"8453":{"leverageManager":"0x...","tokens":{"weeth":"0x..."}}}' scripts/run-tests.tsalso readsTENDERLY_CONTRACT_ADDRESS_OVERRIDESand forwards it to Vite so deterministic Tenderly VNets stay in sync during integration/E2E runs.- Default deterministic Tenderly map lives at
tests/shared/tenderly-addresses.json; update this file when Tenderly deployments change.
The app is designed for 7 incremental production releases:
- Foundation & Infrastructure (current)
- Leverage Tokens
- User Dashboard
- Morpho Vaults
- Staking
- Governance
- Advanced Features (token creation)
- Strict mode with all safety checks enabled
- Path aliases configured in both TypeScript and Vite
- noUncheckedIndexedAccess enabled for array safety
- Biome for linting and formatting
- Run
bun check:fixafter changes - Remove unused variables: Delete dead code/unused vars instead of prefixing with underscores. Keep files clean to avoid confusion and lint noise.
- Descriptive variable names: Avoid single-letter variable names except for trivial indices in very small scopes. Prefer meaningful names that convey intent (e.g.,
previewWithTotalCollateraloverp,debtQuoteoverdq). This applies across domain code, ports, planners, and tests.
- Function declarations: Prefer for exported utilities and domain-layer helpers when hoisting improves readability (main flow first, helpers below). Useful if overloads are expected or to keep stack names clear.
- Arrow functions: Prefer inside React components/hooks and for callbacks/handlers. Use for small, module-local helpers that capture lexical scope. Always use arrows for array methods, event handlers, and React props.
- Consistency: Within a file, choose the dominant style that best serves readability. If the file tells a top-down “story”, place the main function first and define helper declarations below; otherwise, use arrows consistently for local helpers.
ErrorBoundary (Sentry)
└── TanStack Router (hash mode)
└── TanStack Query
└── Wagmi Provider
└── RainbowKit
└── Theme Provider
└── App
Required for development:
VITE_WALLETCONNECT_PROJECT_ID # Get from WalletConnect Cloud
VITE_BASE_RPC_URL # Base network RPCRequired for testing (integration/E2E):
VITE_ALCHEMY_API_KEY # For Anvil mainnet fork - Get from https://www.alchemy.com/Optional:
VITE_SENTRY_DSN # Error tracking
VITE_INCLUDE_TEST_TOKENS # Include test-only tokens (defaults to true for integration/E2E when using scripts/run-tests)See .env.example for complete list.
src/
├── components/ui/ # ShadCN UI components
├── features/ # Phase-based features (leverage-tokens, vaults, etc.)
├── hooks/ # Custom React hooks
├── lib/ # Core utilities and configs
│ ├── config/ # Chain, wagmi, rainbowkit configs
│ ├── contracts/ # ABIs and addresses (single source of truth)
│ └── utils/ # Helper functions
├── routes/ # TanStack Router pages
└── types/ # TypeScript type definitions
Tests are organized in the tests/ directory:
tests/
├── unit/ # Business logic, calculations
├── integration/ # Blockchain interactions (Anvil Base fork)
├── e2e/ # User flows (Playwright)
├── fixtures/ # Test data and mocks
└── utils/ # Test helpers
IMPORTANT: Anvil is the default backend for all integration and E2E tests.
Integration and E2E tests use local Anvil forks for fast, reliable testing. This approach:
- No API quotas or rate limits - Run tests freely without external dependencies
- Fast execution - Local blockchain with instant mining
- Rich holder impersonation - Fund test accounts via impersonation
- Deterministic - Consistent state with snapshot/revert isolation
- CI/CD ready - Works with GitHub Actions using Alchemy RPC for forking
Tenderly VNet available as alternative with --backend=tenderly flag for specific use cases.
Prerequisites:
# Foundry (includes Anvil)
curl -L https://foundry.paradigm.xyz | bash
foundryup
# Alchemy API key for forking
export VITE_ALCHEMY_API_KEY="your_alchemy_key"Setup:
-
Anvil (Default):
# Terminal 1: Start Anvil Mainnet fork bun run anvil:mainnet # Terminal 2: Run tests bun run test:integration # Uses Anvil automatically bun run test:e2e # Uses Anvil automatically
-
Tenderly VNet (Alternative):
# Set Tenderly credentials export TENDERLY_ACCESS_KEY="your_access_key" export TENDERLY_ACCOUNT="your_account_slug" export TENDERLY_PROJECT="your_project_slug" # Run with Tenderly backend bun scripts/run-tests.ts integration --backend=tenderly bun scripts/run-tests.ts e2e --backend=tenderly
Key Features:
- Rich holder impersonation - Fund test accounts by impersonating accounts with large balances
- Test client actions - Direct balance manipulation via
setBalance,impersonateAccount - Snapshot/revert - Fast state management for test isolation
- Per-test funding - Automatic token funding with
withFork()helper - Pinned fork blocks - Both
anvil:baseandanvil:mainnetscripts supportANVIL_FORK_BLOCK/ANVIL_MAINNET_FORK_BLOCKenv vars to pin specific blocks, enabling Anvil's disk cache for faster subsequent runs
Test Architecture:
- Public Client: Read blockchain state from Base fork
- Wallet Client: Sign and send transactions
- Test Client: Anvil-specific actions (
setBalance,impersonateAccount,snapshot,revert) - Funding: Automatic via WETH deposits or rich holder impersonation
See tests/integration/README.md for complete setup guide and CI configuration.
E2E tests verify complete user workflows using real blockchain transactions against Anvil Base fork.
Test Philosophy:
- Happy Path Tests MUST succeed - Tests named "Happy Path" should complete real transactions successfully, not just validate error handling
- Real transactions required - E2E tests execute actual blockchain transactions via Local Account signing
- Proper funding essential - Test accounts must have sufficient token balances for the operations being tested
Key Features:
- Mock Wallet + Local Account - UI uses MockConnector, transactions signed by Local Account (Anvil account #0)
- Dynamic token funding - Test setup automatically funds accounts with required tokens (weETH, WETH, etc.)
- Comprehensive error decoding - ERC-6093 standard errors are properly decoded and logged
- Chain alignment validation - Runtime assertions ensure frontend connects to correct Anvil instance
Test Structure:
tests/e2e/
├── mint-flow.spec.ts # Leverage token minting workflows
├── global-setup.ts # Anvil startup and account funding
├── fund-test-account.ts # Token funding utilities
└── playwright.config.ts # Test mode configuration
Critical Requirements:
- Anvil must run with Base chain ID (8453) - Configured in
package.jsonanvil:base script - Test accounts must have sufficient balances - Verified before attempting transactions
- Happy Path tests must complete successfully - No passing tests that only validate errors
- Environment variables required - VITE_TEST_MODE=mock, VITE_BASE_RPC_URL=http://127.0.0.1:8545
Use VITE_ENABLE_* environment variables to control feature visibility for phased releases.
When building new UI components, always follow this workflow to ensure design consistency and proper implementation:
- Source: All components should be extracted from the
_figmafolder - Location: Look in
_figma/src/components/for existing component implementations - Context: Components are organized by feature (pages, ui, modals, etc.)
- Structure: Create components in appropriate feature folders (
src/features/*/components/) - Naming: Use descriptive names that match the Figma component purpose
- Props: Design clean, typed interfaces that make components reusable
- Styling: Preserve exact Figma styling including colors, spacing, and animations
- Location: Place stories in
src/stories/features/mirroring the component structure - Variants: Create multiple story variants showing different states
- Controls: Add proper controls for interactive props
- Documentation: Use
tags: ['autodocs']for automatic documentation
- Import: Import the new component into the relevant pages/features
- Props: Map real data to component props appropriately
- Testing: Ensure the component works in different states and screen sizes
# 1. Find component in _figma folder
grep -r "Current Holdings" _figma/
# 2. Extract to reusable component
# Create: src/features/leverage-tokens/Features/LeverageTokenHoldingsCard.tsx
# 3. Create Storybook story
# Create: src/stories/features/leverage-tokens/leveragetokenholdingscard.stories.tsx
# 4. Integrate into application
# Import and use in relevant route/page filesImportant: Never create components from scratch when a Figma design exists. Always extract and adapt from the _figma folder to maintain design consistency.
When making changes to this codebase:
Always run these commands before committing any changes:
bun check:fix- Auto-fix linting issues and check typesbun run build- Ensure the build succeedsbun run test:integration- Run integration tests if modifying contract interactions (requires Anvil)
- Code is a liability - Write less code that does more
- Explicit over implicit - Clear intent matters more than cleverness
- Data over abstractions - Don't abstract until you have 3+ use cases
- Test the critical path - 100% coverage on money-moving code
- Question assumptions - Ask "why" before implementing
- State confidence levels - "I'm 90% sure X because Y, but check Z"
- Clarify ambiguity - Never guess when you can ask
- Consider tradeoffs - Every decision has a cost
- Check latest docs - Wagmi, Viem, and Web3 tools change frequently
- IPFS constraints first - Every feature must work client-side only
- Bundle size matters - Check impact of new dependencies
- Type safety - If TypeScript complains, fix it properly
- Components from Figma Make - All UI components should be derived from designs in the
_figmafolder, not created from scratch
- Don't add server-side features
- Don't assume wallet is connected (check first)
- Don't trust external data (validate everything)
If you encounter The service was stopped or The service is no longer running:
- Cause: Usually happens when multiple processes (dev server + build) compete for esbuild
- Solution: Kill the dev server before running build:
pkill -f "bun dev"or close the terminal - Prevention: Don't run
bun devandbun buildsimultaneously
TypeScript requires bracket notation for env vars due to noPropertyAccessFromIndexSignature:
- Use:
import.meta.env['VITE_VAR'](notimport.meta.env.VITE_VAR) - Why: Type safety - ensures you handle potentially undefined values
- Biome config: Set
useLiteralKeys: "off"to avoid linting conflicts