From ea2f1b637e37a6786861deafd95d3f40e2945b65 Mon Sep 17 00:00:00 2001 From: Gabito Esmiapodo <4015436+gabitoesmiapodo@users.noreply.github.com> Date: Wed, 1 Apr 2026 10:33:15 -0300 Subject: [PATCH 01/14] docs: add SDLC sections to agent configuration files --- AGENTS.md | 232 +----------------------------------------------------- CLAUDE.md | 95 +++++++++++++++++++++- 2 files changed, 95 insertions(+), 232 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index eceea487..b14931c8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,231 +1,5 @@ -# dAppBooster +# Agent Configuration -> This file is mirrored as `CLAUDE.md` for Claude. Keep both files in sync when making changes. +This repo's agent configuration lives in [`CLAUDE.md`](./CLAUDE.md). -A repository template / starter-kit for building decentralized applications (dApps). Built by BootNode based on 5+ years of dApp development. Docs: https://docs.dappbooster.dev/ Components: https://components.dappbooster.dev/ - -## Requirements - -- Node 24+ (see `.nvmrc`) -- pnpm 10.30.2+ (enforced via `packageManager` in package.json; corepack will block npm/yarn) - -## Setup - -1. `pnpm install` (postinstall automatically runs `pnpm wagmi-generate`) -2. `cp .env.example .env.local` -3. Edit `.env.local`: - - `PUBLIC_APP_NAME` is mandatory - - `PUBLIC_WALLETCONNECT_PROJECT_ID` is needed for wallet connection to work - - RPC vars (`PUBLIC_RPC_*`) are optional -- wagmi falls back to default public RPCs - - Subgraph vars are all-or-nothing: if ANY `PUBLIC_SUBGRAPHS_*` var is missing or empty, codegen will skip and the app will crash at runtime. Either set them all or remove all subgraph-related code. -4. `pnpm subgraph-codegen` (only if subgraph vars are configured) -5. `pnpm dev` - -## Git Hooks (Husky) - -Three hooks run automatically and will block on failure: - -- **pre-commit:** lint-staged runs Biome check + Vitest on related files for staged changes -- **commit-msg:** commitlint enforces conventional commit format. Valid types: `feat`, `fix`, `docs`, `test`, `ci`, `refactor`, `perf`, `chore`, `revert`. PR titles are also validated via CI. -- **pre-push:** full `tsc --noEmit` type check (pushes with type errors will be rejected) - -## Quick Reference - -- **Dev server:** `pnpm dev` -- **Build:** `pnpm build` (runs tsc + vite build) -- **Lint:** `pnpm lint` (Biome) / `pnpm lint:fix` -- **Test:** `pnpm test` / `pnpm test:watch` / `pnpm test:coverage` -- **Generate contract hooks:** `pnpm wagmi-generate` -- **Generate routes:** `pnpm routes:generate` -- **Generate subgraph types:** `pnpm subgraph-codegen` -- **Docs site:** `pnpm docs:dev` - -## Tech Stack - -- React 19 + TypeScript (strict mode, ESM -- `"type": "module"`, no `require()`) -- Vite + SWC (fast JSX transform) -- Wagmi 2 + viem 2 (Ethereum interaction) -- TanStack Router (file-based routing with auto code-splitting) -- TanStack Query (server state / data fetching) -- Chakra UI 3 (component library + theming) -- ConnectKit (default wallet connector; RainbowKit and Web3Modal also available) -- LI.FI SDK (token prices/balances) -- Zod + @t3-oss/env-core (environment variable validation) -- Biome (linting + formatting) -- Vitest + Testing Library (testing) - -## Project Structure - -``` -src/ - components/ - pageComponents/ # Page-level components (home/, weth/, demos/) - sharedComponents/ # Reusable UI + business components - ui/ # Chakra provider + theme setup - hooks/ # Custom React hooks - generated.ts # Auto-generated by wagmi-cli (do not edit) - providers/ # Web3Provider, TransactionNotificationProvider - routes/ # TanStack Router file-based routes - __root.tsx # Root layout (provider stack + shell) - lib/ - networks.config.ts # Chain + transport configuration - wagmi/ # Wagmi CLI config + custom Suspense plugin - wallets/ # ConnectKit, RainbowKit, Web3Modal configs - constants/ - contracts/ # Contract ABIs + addresses per chain - tokenLists.ts # Token list URLs - common.ts # Shared constants (isDev, includeTestnets) - types/ # TypeScript type definitions - utils/ # Utility functions - subgraphs/ # GraphQL codegen config + queries - env.ts # Zod-validated environment variables - main.tsx # Entry point - routeTree.gen.ts # Auto-generated route tree (do not edit) -``` - -No top-level barrel exports. Import directly from each file/folder path. - -## Provider Stack - -The root layout (`src/routes/__root.tsx`) wraps the app in this order: - -``` -Chakra Provider (theme, color mode) - Web3Provider (WagmiProvider -> QueryClientProvider -> WalletProvider) - TransactionNotificationProvider (tx toast context) - Header / Outlet (page) / Footer -``` - -New providers should be inserted at the appropriate layer based on their dependencies. - -## Code Style - -- **No semicolons** in TypeScript/JavaScript -- **Single quotes** -- 2-space indentation, 100 char line width -- Import organization handled by Biome -- Path aliases: `@/src/*` and `@packageJSON` -- All env vars prefixed with `PUBLIC_` and validated in `src/env.ts` -- JSDoc comments on exported functions/components (follow existing patterns) - -## Styling - -- **Chakra UI props** are the primary styling method. No CSS modules, no Tailwind, no styled-components. -- Theme is defined in `src/components/ui/provider.tsx` using `createSystem` extending `defaultConfig`. -- Use **semantic tokens** for colors: `bg`, `primary`, `text`, `danger`, `ok`, `warning`. These support light/dark mode. Do not hardcode color values. -- Fonts: Manrope (body/heading), Roboto Mono (mono/code). -- Complex components export style objects from a `styles.ts` file, consumed via Chakra's `css` prop or spread into component props. - -## Component Conventions - -- **Simple components:** single file (e.g., `ExplorerLink.tsx`, `SwitchNetwork.tsx`) -- **Complex components:** folder with: - - `index.tsx` -- main component and public API - - `Components.tsx` -- sub-components (styled primitives, layout pieces) - - `styles.ts` -- style objects - - `useComponentName.ts` -- dedicated hook (optional) -- Page components go in `src/components/pageComponents//` -- Shared/reusable components go in `src/components/sharedComponents/` - -## Key Patterns - -### Adding a New Contract - -1. Save ABI in `src/constants/contracts/abis/YourContract.ts` (export as const) -2. Register in `src/constants/contracts/contracts.ts` with name, ABI, and addresses per chain -3. Run `pnpm wagmi-generate` to auto-generate typed hooks in `src/hooks/generated.ts` - -The contracts array uses `as const satisfies ContractConfig[]` for full type inference. Follow this pattern. - -Use `getContract(name, chainId)` to retrieve a typed contract config at runtime (e.g., `getContract('WETH', sepolia.id)` returns `{ abi, address }`). - -### Generated Hook Naming Convention - -`pnpm wagmi-generate` produces hooks in `src/hooks/generated.ts` following this naming pattern: - -- `useReadContractNameFunctionName` -- standard read hook -- `useWriteContractNameFunctionName` -- write hook -- `useSuspenseReadContractNameFunctionName` -- Suspense-enabled read hook -- `useSimulateContractNameFunctionName` -- simulation hook - -Example: contract named `WETH` with function `allowance` generates `useReadWethAllowance`, `useWriteWethApprove`, `useSuspenseReadWethAllowance`, etc. - -### Adding a New Route/Page - -1. Create route file in `src/routes/` using `.lazy.tsx` extension for code-split lazy loading (e.g., `yourpage.lazy.tsx`). Use plain `.tsx` only for routes that must be eagerly loaded. -2. Create page component in `src/components/pageComponents/yourpage/` -3. Route tree auto-generates via TanStack Router plugin - -### Adding a New Network - -1. Import chain from `viem/chains` in `src/lib/networks.config.ts` -2. Add to the appropriate chain array (devChains or prodChains) -3. Add transport entry with optional custom RPC from env -4. Add RPC env var to `src/env.ts` if needed - -### Environment Variables - -- Defined and validated with Zod in `src/env.ts` -- Access via `import { env } from '@/src/env'` (never `import.meta.env` directly) -- New variables MUST be added to both `.env.example` and `src/env.ts` - -### Web3 Status - -- Use `useWeb3Status()` for unified wallet/chain state -- Use `useWeb3StatusConnected()` when wallet connection is guaranteed (throws otherwise) -- Use `withWalletStatusVerifier()` HOC to gate components behind wallet connection - -### Suspense and Error Boundaries - -- Contract read hooks use React Suspense via custom `reactSuspenseRead` wagmi plugin -- `useTokenLists` is Suspense-based -- Wrap async components with `withSuspenseAndRetry()` from `src/utils/suspenseWrapper.tsx` -- this is the primary error boundary pattern. It integrates with TanStack Query's `QueryErrorResetBoundary` for automatic retry on query failures. -- `withSuspenseAndRetry` accepts: `suspenseFallback`, `errorFallback`, `defaultFallbackFormat` ('dialog' | 'default'), `spinnerSize` -- Simpler variant `withSuspense()` available when retry logic is not needed - -### Transaction Handling - -Two components for two different jobs -- both auto-wrap with `withWalletStatusVerifier` (do not double-wrap): - -- **`TransactionButton`** -- for on-chain transactions. Takes `transaction: () => Promise` prop. Attach a `methodId` string property to the function for notification context. Calls `onMined(receipt)` on confirmation. -- **`SignButton`** -- for message signing only (uses `useSignMessage`). Takes `message`, `onSign`, `onError` props. - -`TransactionNotificationProvider` handles toast notifications and tx watching for both. - -### Token Management - -- Token lists configured in `src/constants/tokenLists.ts` -- Fetch with `useTokenLists()` hook (Suspense-based). Tokens are deduplicated by `chainId + address` (lowercased). Native tokens are auto-injected for each configured chain. -- Token prices/balances via `useTokens()` (LI.FI SDK) -- `TokenInput` + `useTokenInput()` for amount input with validation -- For raw bigint/decimal conversion in inputs, use `BigNumberInput` (wraps viem's `parseUnits`/`formatUnits`). Never use `parseFloat` or `Number()` for token amounts. - -### Wallet Provider - -- Default: ConnectKit (configured in `src/lib/wallets/connectkit.config.tsx`) -- To switch: modify `src/providers/Web3Provider.tsx` imports -- Alternatives available: RainbowKit (`rainbowkit.config.tsx`), Web3Modal (`web3modal.config.tsx`) - -## Auto-Generated Files (Do Not Edit, Do Not Commit) - -These files are gitignored and regenerated from source: - -- `src/hooks/generated.ts` -- regenerate with `pnpm wagmi-generate` -- `src/routeTree.gen.ts` -- regenerate with `pnpm routes:generate` -- `src/subgraphs/gql/` -- regenerate with `pnpm subgraph-codegen` - -## Utilities - -- **Number formatting:** Use `formatNumber()` from `src/utils/numberFormat.ts` with the appropriate `NumberType` context (`TokenTx`, `FiatTokenPrice`, `SwapPrice`, `PortfolioBalance`, etc.). Never use raw `.toString()` or `.toFixed()` for user-facing numbers. -- **Explorer links:** `getExplorerLink()` from `src/utils/getExplorerLink.ts` -- **String truncation:** `truncateStringInTheMiddle()`, `getTruncatedHash()` from `src/utils/strings.ts` -- **Native token check:** `isNativeToken()` from `src/utils/address.ts` -- **Hash detection:** `detectHash()` from `src/utils/hash.ts` -- identifies ENS names, transaction hashes, contract addresses, and EOAs - -## Testing - -- Framework: Vitest with jsdom environment -- Test files: colocated as `*.test.ts` / `*.test.tsx` -- Mocking: `vi.mock()` for module mocks -- Component testing: Testing Library + jest-dom matchers -- Run: `pnpm test` (single run) or `pnpm test:watch` +Claude Code reads `CLAUDE.md` natively. If your agent reads `AGENTS.md` (Cursor, Windsurf, etc.), load rules from `CLAUDE.md` -- it is the single source of truth for this repo's conventions and working rules. diff --git a/CLAUDE.md b/CLAUDE.md index cb3ccf5c..edd223a1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,7 +1,5 @@ # dAppBooster -> This file is mirrored as `AGENTS.md` for non-Claude AI agents. Keep both files in sync when making changes. - A repository template / starter-kit for building decentralized applications (dApps). Built by BootNode based on 5+ years of dApp development. Docs: https://docs.dappbooster.dev/ Components: https://components.dappbooster.dev/ ## Requirements @@ -26,9 +24,70 @@ A repository template / starter-kit for building decentralized applications (dAp Three hooks run automatically and will block on failure: - **pre-commit:** lint-staged runs Biome check + Vitest on related files for staged changes -- **commit-msg:** commitlint enforces conventional commit format. Valid types: `feat`, `fix`, `docs`, `test`, `ci`, `refactor`, `perf`, `chore`, `revert`. PR titles are also validated via CI. +- **commit-msg:** commitlint enforces conventional commit format. Valid types: `feat`, `fix`, `docs`, `test`, `ci`, `refactor`, `perf`, `chore`, `revert`, `style`, `build`, `wip`, `release`. PR titles are also validated via CI. - **pre-push:** full `tsc --noEmit` type check (pushes with type errors will be rejected) +## Commit Standards + +Use [Conventional Commits](https://www.conventionalcommits.org/): + +**Format:** `type(scope): subject` + +- **Scope** is optional: `feat: add login` and `feat(auth): add login` are both valid +- **Subject** uses imperative mood, lowercase after the colon, no trailing period +- **Body** (optional): separated by a blank line, explains *what* and *why* + +**Prefixes:** + +| Prefix | Purpose | +|--------|---------| +| `feat` | New feature | +| `fix` | Bug fix | +| `chore` | Maintenance, dependencies, config | +| `docs` | Documentation only | +| `refactor` | Code change that neither fixes a bug nor adds a feature | +| `test` | Adding or updating tests | +| `style` | Formatting, whitespace, semicolons | +| `ci` | CI/CD pipeline changes | +| `perf` | Performance improvement | +| `build` | Build system or external dependencies | +| `revert` | Reverts a previous commit | +| `wip` | Work in progress (avoid on main) | +| `release` | Release-related changes | + +## PR Workflow + +- Every PR must reference an issue (`Closes #N`) +- Mirror the issue's acceptance criteria in the PR +- Self-review your diff before requesting peer review +- Keep PRs small and focused -- one issue, one PR +- PR titles use the same conventional commit format (`feat: add user dashboard`) + +## Label Conventions + +GitHub form dropdowns (like the Severity field in `1-bug.yml`) only work through the web UI. When issues are created via `gh` CLI or REST API, dropdown values become unstructured body text -- not queryable, not consistent. **Labels are the API-reliable mechanism for structured metadata.** + +**Severity** (bugs only): + +| Label | Description | +|-------|-------------| +| `severity: critical` | System down, data loss, or security issue | +| `severity: high` | Broken feature, no workaround | +| `severity: medium` | Broken feature, workaround exists | +| `severity: low` | Cosmetic or minor inconvenience | + +**Priority** (features and epics): + +| Label | Description | +|-------|-------------| +| `priority: high` | Must be addressed in current sprint | +| `priority: medium` | Should be addressed soon | +| `priority: low` | Nice to have, can wait | + +Labels are queryable: `gh issue list --label "severity: high"`, `gh issue list --label "priority: medium"`. + +The `/issue` skill applies these labels automatically when creating issues via CLI. The bug template's severity dropdown is kept for web UI users but is not the source of truth for programmatic workflows. + ## Quick Reference - **Dev server:** `pnpm dev` @@ -229,3 +288,33 @@ These files are gitignored and regenerated from source: - Mocking: `vi.mock()` for module mocks - Component testing: Testing Library + jest-dom matchers - Run: `pnpm test` (single run) or `pnpm test:watch` +- **What to test:** Business logic, API integrations, component behavior +- **What not to test:** Styling, third-party library internals, trivial getters/setters +- **Coverage:** Aim for meaningful coverage, not a number. Cover the paths that matter. + +## Guardrails + +- Do not commit secrets, API keys, or credentials +- Do not modify CI/CD pipelines without team review +- Do not skip tests or linting to make a build pass +- When in doubt, ask -- don't assume + +## Change Strategy + +- Prefer small, focused diffs over broad refactors +- Preserve existing UX unless the task explicitly changes it +- Avoid introducing new patterns when a project pattern already exists +- Update docs only when behavior or workflow changes + +## Validation Checklist + +Run before declaring work done: + +- `pnpm lint` +- `pnpm test` +- `pnpm build` (when feasible for runtime-impacting changes) + +## References + +- [dAppBooster Docs](https://docs.dappbooster.dev/) +- [Component Library](https://components.dappbooster.dev/) From 64e1ff4bef3a435403ca2345fabf133fdb3dec06 Mon Sep 17 00:00:00 2001 From: Gabito Esmiapodo <4015436+gabitoesmiapodo@users.noreply.github.com> Date: Wed, 1 Apr 2026 10:33:23 -0300 Subject: [PATCH 02/14] chore: add wip and release to commitlint allowed types --- commitlint.config.js | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/commitlint.config.js b/commitlint.config.js index 7c4ff4d9..cc1019a3 100644 --- a/commitlint.config.js +++ b/commitlint.config.js @@ -1 +1,24 @@ -export default { extends: ['@commitlint/config-conventional'] } +export default { + extends: ['@commitlint/config-conventional'], + rules: { + 'type-enum': [ + 2, + 'always', + [ + 'build', + 'chore', + 'ci', + 'docs', + 'feat', + 'fix', + 'perf', + 'refactor', + 'release', + 'revert', + 'style', + 'test', + 'wip', + ], + ], + }, +} From 5198fbc7a627a511d4dc81ca40768a1d95c07feb Mon Sep 17 00:00:00 2001 From: Gabito Esmiapodo <4015436+gabitoesmiapodo@users.noreply.github.com> Date: Wed, 1 Apr 2026 10:33:29 -0300 Subject: [PATCH 03/14] chore: add pull request template --- .github/PULL_REQUEST_TEMPLATE.md | 34 ++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..85eec745 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,34 @@ +## Summary + + + +Closes # + +## Changes + + + +- + +## Acceptance criteria + + + +- [ ] + +## Test plan + + + +## Breaking changes + + + +None. + +## Checklist + +- [ ] Self-reviewed my own diff +- [ ] Tests added or updated +- [ ] Docs updated (if applicable) +- [ ] No unrelated changes bundled in From 85c2903b45d85cf37555b2f259f5cdd01b1d29da Mon Sep 17 00:00:00 2001 From: Gabito Esmiapodo <4015436+gabitoesmiapodo@users.noreply.github.com> Date: Wed, 1 Apr 2026 10:33:35 -0300 Subject: [PATCH 04/14] chore: add structured issue templates for bugs, features, epics, and spikes --- .github/ISSUE_TEMPLATE/1-bug.yml | 90 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/2-feature.yml | 75 +++++++++++++++++++ .github/ISSUE_TEMPLATE/3-epic.yml | 103 +++++++++++++++++++++++++++ .github/ISSUE_TEMPLATE/4-spike.yml | 102 ++++++++++++++++++++++++++ 4 files changed, 370 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/1-bug.yml create mode 100644 .github/ISSUE_TEMPLATE/2-feature.yml create mode 100644 .github/ISSUE_TEMPLATE/3-epic.yml create mode 100644 .github/ISSUE_TEMPLATE/4-spike.yml diff --git a/.github/ISSUE_TEMPLATE/1-bug.yml b/.github/ISSUE_TEMPLATE/1-bug.yml new file mode 100644 index 00000000..8d49b4fa --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1-bug.yml @@ -0,0 +1,90 @@ +name: Bug Report +description: Report a bug or unexpected behavior. +title: "" +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to report this. Before opening a new issue, please search existing issues to avoid duplicates. + + - type: textarea + id: description + attributes: + label: Description + description: A clear and concise description of the bug. + placeholder: What happened? + validations: + required: true + + - type: textarea + id: steps + attributes: + label: Steps to reproduce + description: Minimal steps to reproduce the behavior. + placeholder: | + 1. Go to '...' + 2. Click on '...' + 3. Scroll down to '...' + 4. See error + validations: + required: true + + - type: textarea + id: expected + attributes: + label: Expected vs actual behavior + description: What did you expect to happen, and what actually happened? + placeholder: | + **Expected:** ... + **Actual:** ... + validations: + required: true + + - type: input + id: reproduction + attributes: + label: Reproduction link + description: Link to a minimal reproduction (repo, sandbox, deploy). Issues without a reproduction may be closed. + placeholder: https://github.com/... + validations: + required: false + + - type: textarea + id: environment + attributes: + label: Environment + description: Relevant environment details (OS, browser, runtime, package versions). + render: bash + placeholder: | + OS: macOS 15.3 + Node: 22.x + Package: 1.0.0 + validations: + required: false + + - type: markdown + attributes: + value: | + > **Programmatic creation:** When using `gh` CLI or REST API, apply `severity: ` labels instead of using the Severity dropdown (e.g., `--label "severity: high"`). See the Label Conventions section in `AGENTS.md` for the full table. + + - type: dropdown + id: severity + attributes: + label: Severity + description: How much does this impact your work? + options: + - Low — cosmetic or minor inconvenience + - Medium — broken feature, workaround exists + - High — broken feature, no workaround + - Critical — system down, data loss, or security issue + validations: + required: false + + - type: textarea + id: context + attributes: + label: Additional context + description: Screenshots, logs, error messages, or anything else that might help. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/2-feature.yml b/.github/ISSUE_TEMPLATE/2-feature.yml new file mode 100644 index 00000000..79926725 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/2-feature.yml @@ -0,0 +1,75 @@ +name: Feature +description: Propose a new feature or enhancement. +title: "" +labels: ["enhancement"] +body: + - type: markdown + attributes: + value: | + Before opening a feature request, please check if a similar one already exists. + + - type: markdown + attributes: + value: | + > **Priority:** When using `gh` CLI or REST API, apply `priority: ` labels (e.g., `--label "priority: high"`). See the Label Conventions section in `AGENTS.md` for the full table. + + - type: textarea + id: user-story + attributes: + label: User story / Problem statement + description: 'Who needs this and why? Pick the format that fits: "As a [role]..." for new capabilities with a clear user beneficiary; "Currently..." for UX fixes, missing feedback, or when the user role is implicit.' + placeholder: | + As a [role], I want [goal], so that [benefit]. + + — or — + + Currently, ... This is a problem because ... + validations: + required: true + + - type: textarea + id: expected-outcome + attributes: + label: Expected outcome + description: What does success look like? Describe the desired end state from the user's perspective, not the implementation approach. + placeholder: | + When this is done, a user should be able to... + The system should behave as follows... + validations: + required: true + + - type: textarea + id: acceptance-criteria + attributes: + label: Acceptance criteria + description: Specific, testable conditions that verify the expected outcome was achieved. These will be mirrored in the PR checklist. + placeholder: | + - [ ] ... + - [ ] ... + - [ ] ... + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + description: Other approaches you explored and why they were discarded. + validations: + required: false + + - type: textarea + id: technical-notes + attributes: + label: Technical notes + description: Architecture considerations, dependencies, migration concerns, or anything the implementer should know. + validations: + required: false + + - type: textarea + id: context + attributes: + label: Additional context + description: Mockups, screenshots, references, or related issues. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/3-epic.yml b/.github/ISSUE_TEMPLATE/3-epic.yml new file mode 100644 index 00000000..88a980db --- /dev/null +++ b/.github/ISSUE_TEMPLATE/3-epic.yml @@ -0,0 +1,103 @@ +name: Epic +description: Define a large body of work that will be broken into smaller issues. +title: "" +labels: ["epic"] +body: + - type: markdown + attributes: + value: | + Epics capture the vision and scope of a significant effort. They get decomposed into smaller, independently deliverable issues. Invest time here — the quality of the epic sets the ceiling for everything downstream. + + - type: markdown + attributes: + value: | + > **Priority:** When using `gh` CLI or REST API, apply `priority: ` labels (e.g., `--label "priority: high"`). See the Label Conventions section in `AGENTS.md` for the full table. + + - type: textarea + id: objective + attributes: + label: Objective + description: What are we trying to achieve? The high-level goal in one or two sentences. + placeholder: Enable users to... + validations: + required: true + + - type: textarea + id: rationale + attributes: + label: Rationale + description: Why does this matter? Business value, user impact, or technical justification. + placeholder: This is important because... + validations: + required: true + + - type: textarea + id: scope + attributes: + label: Scope + description: What is in scope and, just as importantly, what is out of scope. + placeholder: | + **In scope:** + - ... + + **Out of scope:** + - ... + validations: + required: true + + - type: textarea + id: architecture + attributes: + label: Architecture & technical considerations + description: Key technical decisions, patterns, constraints, or risks. Link to ADRs or design docs if they exist. + validations: + required: false + + - type: textarea + id: dependencies + attributes: + label: Dependencies + description: What blocks this epic or what does this epic block? External services, other teams, infrastructure, etc. + validations: + required: false + + - type: textarea + id: issues + attributes: + label: Issue breakdown + description: Checklist of sub-issues that compose this epic. Can start as a rough draft — the agent or the team will refine and create the actual issues. + placeholder: | + - [ ] #issue or description + - [ ] #issue or description + - [ ] #issue or description + validations: + required: false + + - type: textarea + id: acceptance-criteria + attributes: + label: Acceptance criteria + description: How do we know this epic is done? High-level conditions, not per-issue details. + placeholder: | + - [ ] ... + - [ ] ... + validations: + required: true + + - type: input + id: owner + attributes: + label: Owner / DRI + description: Who is responsible for driving this epic to completion? + placeholder: "@username" + validations: + required: false + + - type: input + id: milestone + attributes: + label: Target milestone + description: Which milestone or release is this epic targeting? + placeholder: v1.0, Q2 2026, Sprint 5... + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/4-spike.yml b/.github/ISSUE_TEMPLATE/4-spike.yml new file mode 100644 index 00000000..94cd18d4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/4-spike.yml @@ -0,0 +1,102 @@ +name: Spike / Research +description: Time-boxed investigation to answer a question or reduce uncertainty. +title: "" +labels: ["spike"] +body: + - type: markdown + attributes: + value: | + A spike is not about delivering code — it's about answering a question. Define the question clearly, time-box the effort, and document findings here. The issue itself becomes the deliverable. + + - type: textarea + id: question + attributes: + label: Research question + description: What question(s) are we trying to answer? Be specific. + placeholder: | + Can we use X to solve Y? + What are the trade-offs between A and B for our use case? + validations: + required: true + + - type: dropdown + id: timebox + attributes: + label: Time-box + description: Maximum time to spend before reporting back, regardless of outcome. + options: + - 2 hours + - Half day + - 1 day + - 2 days + - 3 days + validations: + required: true + + - type: textarea + id: background + attributes: + label: Background / What we already know + description: Context, prior decisions, links to related issues or docs. Prevents the investigator from repeating work. + validations: + required: false + + - type: textarea + id: method + attributes: + label: Investigation approach + description: How should this be investigated? Prototype, docs research, vendor comparison, proof of concept, talk to someone? + placeholder: | + - Read the docs for X + - Build a minimal prototype that tests Y + - Compare options A and B against criteria Z + validations: + required: false + + - type: dropdown + id: output + attributes: + label: Expected output + description: What does "done" look like for this spike? + options: + - Decision (go / no-go) + - Technical recommendation with trade-offs + - Proof of concept / prototype + - Written summary / findings document + - Architecture or design proposal + multiple: true + validations: + required: true + + - type: markdown + attributes: + value: | + --- + **The sections below are filled in after the investigation.** + + - type: textarea + id: findings + attributes: + label: Findings + description: What did you learn? Evidence, data, demos, links. + validations: + required: false + + - type: textarea + id: conclusions + attributes: + label: Conclusions & recommendations + description: Answers to the original question(s). What do you recommend and why? + validations: + required: false + + - type: textarea + id: next-steps + attributes: + label: Next steps + description: Follow-up issues, decisions to make, or work to schedule based on findings. + placeholder: | + - [ ] Create issue for... + - [ ] Discuss with team... + validations: + required: false From 3e3949d99e0ec7b1c45b38fb40560832c0c12cc8 Mon Sep 17 00:00:00 2001 From: Gabito Esmiapodo <4015436+gabitoesmiapodo@users.noreply.github.com> Date: Wed, 1 Apr 2026 10:33:40 -0300 Subject: [PATCH 05/14] chore: add issue creation skill for Claude Code --- .claude/skills/issue/SKILL.md | 64 +++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 .claude/skills/issue/SKILL.md diff --git a/.claude/skills/issue/SKILL.md b/.claude/skills/issue/SKILL.md new file mode 100644 index 00000000..74e8d061 --- /dev/null +++ b/.claude/skills/issue/SKILL.md @@ -0,0 +1,64 @@ +--- +name: issue +description: Use when creating a GitHub issue from a brief -- bug, feature, epic, or spike -- against the repo's GitHub issue templates via gh CLI. +--- + +# /issue + +Create a well-structured GitHub issue using the repo's own templates and `gh` CLI. + +**Core principle:** Templates own the format. Skill owns the behavior. + +## Core Pattern + +1. **Classify** -- Determine type from brief: bug / feature / epic / spike. If unclear, ask once. +2. **Read** -- Load `.github/ISSUE_TEMPLATE/.yml`. Extract all fields, required vs optional, and label. Do this every time -- never reconstruct from memory. +3. **Interview** -- Ask only for missing required fields. If the brief already covers a field, don't re-ask. Scale ceremony to issue weight. +4. **Draft** -- Build title and body. Sections follow template field order using `label` as heading. Omit empty optional fields. +5. **Confirm** -- Show full draft including labels. Wait for explicit approval. Iterate until approved. +6. **Create** -- Write body to temp file, run `gh issue create` with all labels, report issue URL. + +## Template Map + +| Type | File | Type label | Additional labels | +|---------|-----------------|---------------|---------------------------| +| Bug | `1-bug.yml` | `bug` | `severity: ` | +| Feature | `2-feature.yml` | `enhancement` | `priority: ` | +| Epic | `3-epic.yml` | `epic` | `priority: ` | +| Spike | `4-spike.yml` | `spike` | -- | + +## Labels + +Severity and priority are applied as labels, not form dropdowns. See the Label Conventions section in `AGENTS.md` for the full table and descriptions. + +- Bugs get a `severity: ` label (critical / high / medium / low). +- Features and epics get a `priority: ` label (high / medium / low). +- Spikes don't carry severity or priority. +- If the brief doesn't specify a level, ask once. Never default silently. + +## gh Command + +```bash +BODY_FILE=$(mktemp /tmp/gh_issue_body.XXXXXX.md) + +cat > "$BODY_FILE" << 'EOF' + +EOF + +gh issue create \ + --title "" \ + --label "<type-label>" \ + --label "<severity-or-priority-label>" \ # omit for spikes + --body-file "$BODY_FILE" +``` + +Multiple `--label` flags can be chained. The type label is always present. The severity/priority label is added for bugs (severity), features, and epics (priority) -- omit it for spikes. + +Optional flags: `--assignee "<username>"`, `--milestone "<name>"`, `--project "<name>"` + +## Common Mistakes + +- **Skipping the template read** -- Field names and order come from the YAML, not assumptions. Read it every time. +- **Pre-emptively asking for optional fields** -- Required fields are the floor. Let the user volunteer the rest. +- **Creating before confirmation** -- Never run `gh` without explicit approval. Always show the full draft first. +- **Omitting severity/priority labels** -- Form dropdowns do not survive `gh` CLI creation. Always apply these as labels. From b53b3583b3a93c6538a18cb02ee576b9d72881f3 Mon Sep 17 00:00:00 2001 From: Gabito Esmiapodo <4015436+gabitoesmiapodo@users.noreply.github.com> Date: Wed, 1 Apr 2026 11:13:26 -0300 Subject: [PATCH 06/14] docs: add architecture overview and consolidate structural docs Add architecture.md with full project documentation covering tech stack, directory structure, data flow, environment variables, scripts, and domain-specific sections (number precision, provider hierarchy, hook patterns, error handling, smart contract integration, wallet access control). Slim down CLAUDE.md by moving structural/reference content to architecture.md, keeping only operational rules, conventions, and how-to instructions. Add a soft reference so LLMs know to read architecture.md when working on structure-related tasks. --- CLAUDE.md | 127 +---------------------- architecture.md | 269 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 271 insertions(+), 125 deletions(-) create mode 100644 architecture.md diff --git a/CLAUDE.md b/CLAUDE.md index edd223a1..48d28512 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,6 +2,8 @@ A repository template / starter-kit for building decentralized applications (dApps). Built by BootNode based on 5+ years of dApp development. Docs: https://docs.dappbooster.dev/ Components: https://components.dappbooster.dev/ +System architecture, data flow, provider hierarchy, and structural conventions are documented in [architecture.md](./architecture.md). Read it when working on tasks that involve the system's structure or patterns. + ## Requirements - Node 24+ (see `.nvmrc`) @@ -88,75 +90,6 @@ Labels are queryable: `gh issue list --label "severity: high"`, `gh issue list - The `/issue` skill applies these labels automatically when creating issues via CLI. The bug template's severity dropdown is kept for web UI users but is not the source of truth for programmatic workflows. -## Quick Reference - -- **Dev server:** `pnpm dev` -- **Build:** `pnpm build` (runs tsc + vite build) -- **Lint:** `pnpm lint` (Biome) / `pnpm lint:fix` -- **Test:** `pnpm test` / `pnpm test:watch` / `pnpm test:coverage` -- **Generate contract hooks:** `pnpm wagmi-generate` -- **Generate routes:** `pnpm routes:generate` -- **Generate subgraph types:** `pnpm subgraph-codegen` -- **Docs site:** `pnpm docs:dev` - -## Tech Stack - -- React 19 + TypeScript (strict mode, ESM -- `"type": "module"`, no `require()`) -- Vite + SWC (fast JSX transform) -- Wagmi 2 + viem 2 (Ethereum interaction) -- TanStack Router (file-based routing with auto code-splitting) -- TanStack Query (server state / data fetching) -- Chakra UI 3 (component library + theming) -- ConnectKit (default wallet connector; RainbowKit and Web3Modal also available) -- LI.FI SDK (token prices/balances) -- Zod + @t3-oss/env-core (environment variable validation) -- Biome (linting + formatting) -- Vitest + Testing Library (testing) - -## Project Structure - -``` -src/ - components/ - pageComponents/ # Page-level components (home/, weth/, demos/) - sharedComponents/ # Reusable UI + business components - ui/ # Chakra provider + theme setup - hooks/ # Custom React hooks - generated.ts # Auto-generated by wagmi-cli (do not edit) - providers/ # Web3Provider, TransactionNotificationProvider - routes/ # TanStack Router file-based routes - __root.tsx # Root layout (provider stack + shell) - lib/ - networks.config.ts # Chain + transport configuration - wagmi/ # Wagmi CLI config + custom Suspense plugin - wallets/ # ConnectKit, RainbowKit, Web3Modal configs - constants/ - contracts/ # Contract ABIs + addresses per chain - tokenLists.ts # Token list URLs - common.ts # Shared constants (isDev, includeTestnets) - types/ # TypeScript type definitions - utils/ # Utility functions - subgraphs/ # GraphQL codegen config + queries - env.ts # Zod-validated environment variables - main.tsx # Entry point - routeTree.gen.ts # Auto-generated route tree (do not edit) -``` - -No top-level barrel exports. Import directly from each file/folder path. - -## Provider Stack - -The root layout (`src/routes/__root.tsx`) wraps the app in this order: - -``` -Chakra Provider (theme, color mode) - Web3Provider (WagmiProvider -> QueryClientProvider -> WalletProvider) - TransactionNotificationProvider (tx toast context) - Header / Outlet (page) / Footer -``` - -New providers should be inserted at the appropriate layer based on their dependencies. - ## Code Style - **No semicolons** in TypeScript/JavaScript @@ -198,17 +131,6 @@ The contracts array uses `as const satisfies ContractConfig<Abi>[]` for full typ Use `getContract(name, chainId)` to retrieve a typed contract config at runtime (e.g., `getContract('WETH', sepolia.id)` returns `{ abi, address }`). -### Generated Hook Naming Convention - -`pnpm wagmi-generate` produces hooks in `src/hooks/generated.ts` following this naming pattern: - -- `useReadContractNameFunctionName` -- standard read hook -- `useWriteContractNameFunctionName` -- write hook -- `useSuspenseReadContractNameFunctionName` -- Suspense-enabled read hook -- `useSimulateContractNameFunctionName` -- simulation hook - -Example: contract named `WETH` with function `allowance` generates `useReadWethAllowance`, `useWriteWethApprove`, `useSuspenseReadWethAllowance`, etc. - ### Adding a New Route/Page 1. Create route file in `src/routes/` using `.lazy.tsx` extension for code-split lazy loading (e.g., `yourpage.lazy.tsx`). Use plain `.tsx` only for routes that must be eagerly loaded. @@ -228,43 +150,6 @@ Example: contract named `WETH` with function `allowance` generates `useReadWethA - Access via `import { env } from '@/src/env'` (never `import.meta.env` directly) - New variables MUST be added to both `.env.example` and `src/env.ts` -### Web3 Status - -- Use `useWeb3Status()` for unified wallet/chain state -- Use `useWeb3StatusConnected()` when wallet connection is guaranteed (throws otherwise) -- Use `withWalletStatusVerifier()` HOC to gate components behind wallet connection - -### Suspense and Error Boundaries - -- Contract read hooks use React Suspense via custom `reactSuspenseRead` wagmi plugin -- `useTokenLists` is Suspense-based -- Wrap async components with `withSuspenseAndRetry()` from `src/utils/suspenseWrapper.tsx` -- this is the primary error boundary pattern. It integrates with TanStack Query's `QueryErrorResetBoundary` for automatic retry on query failures. -- `withSuspenseAndRetry` accepts: `suspenseFallback`, `errorFallback`, `defaultFallbackFormat` ('dialog' | 'default'), `spinnerSize` -- Simpler variant `withSuspense()` available when retry logic is not needed - -### Transaction Handling - -Two components for two different jobs -- both auto-wrap with `withWalletStatusVerifier` (do not double-wrap): - -- **`TransactionButton`** -- for on-chain transactions. Takes `transaction: () => Promise<Hash>` prop. Attach a `methodId` string property to the function for notification context. Calls `onMined(receipt)` on confirmation. -- **`SignButton`** -- for message signing only (uses `useSignMessage`). Takes `message`, `onSign`, `onError` props. - -`TransactionNotificationProvider` handles toast notifications and tx watching for both. - -### Token Management - -- Token lists configured in `src/constants/tokenLists.ts` -- Fetch with `useTokenLists()` hook (Suspense-based). Tokens are deduplicated by `chainId + address` (lowercased). Native tokens are auto-injected for each configured chain. -- Token prices/balances via `useTokens()` (LI.FI SDK) -- `TokenInput` + `useTokenInput()` for amount input with validation -- For raw bigint/decimal conversion in inputs, use `BigNumberInput` (wraps viem's `parseUnits`/`formatUnits`). Never use `parseFloat` or `Number()` for token amounts. - -### Wallet Provider - -- Default: ConnectKit (configured in `src/lib/wallets/connectkit.config.tsx`) -- To switch: modify `src/providers/Web3Provider.tsx` imports -- Alternatives available: RainbowKit (`rainbowkit.config.tsx`), Web3Modal (`web3modal.config.tsx`) - ## Auto-Generated Files (Do Not Edit, Do Not Commit) These files are gitignored and regenerated from source: @@ -273,14 +158,6 @@ These files are gitignored and regenerated from source: - `src/routeTree.gen.ts` -- regenerate with `pnpm routes:generate` - `src/subgraphs/gql/` -- regenerate with `pnpm subgraph-codegen` -## Utilities - -- **Number formatting:** Use `formatNumber()` from `src/utils/numberFormat.ts` with the appropriate `NumberType` context (`TokenTx`, `FiatTokenPrice`, `SwapPrice`, `PortfolioBalance`, etc.). Never use raw `.toString()` or `.toFixed()` for user-facing numbers. -- **Explorer links:** `getExplorerLink()` from `src/utils/getExplorerLink.ts` -- **String truncation:** `truncateStringInTheMiddle()`, `getTruncatedHash()` from `src/utils/strings.ts` -- **Native token check:** `isNativeToken()` from `src/utils/address.ts` -- **Hash detection:** `detectHash()` from `src/utils/hash.ts` -- identifies ENS names, transaction hashes, contract addresses, and EOAs - ## Testing - Framework: Vitest with jsdom environment diff --git a/architecture.md b/architecture.md new file mode 100644 index 00000000..46889646 --- /dev/null +++ b/architecture.md @@ -0,0 +1,269 @@ +# Architecture Overview + +<!-- Keep this document up to date as the system evolves. It captures structural + knowledge that helps both humans onboarding and agents building context at + session start. Focus on the "shape" of the system — not usage instructions + (that's AGENTS.md) or API docs (that's code comments). --> + +## Tech Stack + +| Category | Technology | Notes | +|----------|-----------|-------| +| Framework | React 19 | StrictMode enabled | +| Language | TypeScript 5 (strict, ESM) | Path aliases: `@/src/*`, `@packageJSON` | +| Blockchain | wagmi 2 + viem 2 | Type-safe Ethereum interaction | +| Data fetching | TanStack Query 5, graphql-request | Suspense-based contract reads | +| Routing | TanStack Router | File-based with auto code-splitting | +| UI | Chakra UI 3 + Emotion | Semantic tokens, light/dark mode | +| Testing | Vitest + Testing Library | jsdom environment, colocated test files | +| Build | Vite 6 + SWC | Manual chunk splitting for vendors | +| Linting | Biome | Format + lint in one tool | +| Env validation | Zod + @t3-oss/env-core | All vars `PUBLIC_` prefixed | + +## Project Structure + +``` +src/ + components/ + pageComponents/ Page-level components (home/, demos/) + sharedComponents/ Reusable UI + business components + ui/ Chakra provider, color mode, toaster, tooltip + hooks/ Custom React hooks + generated.ts Auto-generated by wagmi-cli (do not edit) + providers/ Web3Provider, TransactionNotificationProvider + routes/ TanStack Router file-based routes + __root.tsx Root layout (provider stack + shell) + lib/ + networks.config.ts Chain + transport configuration + wagmi/ Wagmi CLI config + custom Suspense plugin + wallets/ ConnectKit, RainbowKit, Web3Modal, Porto configs + constants/ + contracts/ Contract ABIs + addresses per chain + tokenLists.ts Token list URLs + common.ts Shared constants (isDev, includeTestnets) + types/ TypeScript type definitions (Token via Zod) + utils/ Utility functions + subgraphs/ GraphQL codegen config + queries + env.ts Zod-validated environment variables + main.tsx Entry point + routeTree.gen.ts Auto-generated route tree (do not edit) +``` + +No top-level barrel exports. Import directly from each file/folder path. + +## Key Abstractions + +**Component structure** -- simple components are a single file; complex components use a folder: +``` +ComponentName/ + index.tsx main component and public API + Components.tsx sub-components (styled primitives, layout pieces) + styles.ts style objects consumed via Chakra's css prop + useComponentName.ts dedicated hook (optional) +``` + +**HOC patterns:** +- `withSuspenseAndRetry(Component)` -- primary pattern for async components; wraps in `QueryErrorResetBoundary` + `ErrorBoundary` + `Suspense` for automatic retry on query failures +- `withSuspense(Component)` -- simpler variant without retry +- `withWalletStatusVerifier(Component)` -- gates a component behind wallet connection + chain sync + +**Contract hooks** (auto-generated by wagmi-cli from ABIs): +- `useRead{Contract}{Function}` -- standard read +- `useSuspenseRead{Contract}{Function}` -- Suspense-enabled read (preferred) +- `useWrite{Contract}{Function}` -- write/mutation +- `useSimulate{Contract}{Function}` -- simulation before write + +**Contract lookup:** `getContract(name, chainId)` from `src/constants/contracts/contracts.ts` returns typed `{ abi, address }`. Validates address with `isAddress()`, throws with a helpful message if not found. + +**Utility functions** (`src/utils/`): +- `formatNumber(value, type)` -- user-facing number formatting with `NumberType` contexts (`TokenTx`, `FiatTokenPrice`, `SwapPrice`, `PortfolioBalance`) +- `getExplorerLink(chainId, hash, type)` -- block explorer URLs per chain +- `truncateStringInTheMiddle(str)`, `getTruncatedHash(hash)` -- string display helpers +- `isNativeToken(address)` -- checks against native token address +- `detectHash(str)` -- identifies ENS names, tx hashes, contract addresses, EOAs + +### Data Access Layer + +Four external data paths. Components never call external services directly -- they use hooks. + +1. **Blockchain reads**: `useSuspenseRead{Contract}{Function}` hooks (generated) -> wagmi `readContract` -> TanStack Query cache. All contract reads are Suspense-based via the custom `reactSuspenseRead` wagmi plugin in `src/lib/wagmi/plugins/`. + +2. **Blockchain writes**: `TransactionButton` calls `transaction: () => Promise<Hash>` prop -> wagmi `writeContract` -> `useWaitForTransactionReceipt` -> `TransactionNotificationProvider` toasts (loading, success/revert, explorer link). + +3. **Subgraph queries**: `graphql-request` with typed document nodes from `@graphql-codegen` -> TanStack Query cache. Queries live in `src/subgraphs/queries/`, generated types in `src/subgraphs/gql/`. + +4. **Token data**: LI.FI SDK (`getChains` -> `getTokens` -> `getTokenBalances`) via `useTokens` hook. Token lists fetched in parallel via `useTokenLists` (Suspense-based, sources: 1INCH, CoinGecko, optional Uniswap default list). Tokens deduplicated by `chainId + address`, native tokens auto-injected per chain. + +## Routes + +| Route | Module | Description | +|-------|--------|-------------| +| `/` | `src/components/pageComponents/home/` | Landing page with welcome + feature demos | +| `*` | `src/components/pageComponents/NotFound404.tsx` | Catch-all 404 page | + +File-based routing via TanStack Router. Use `.lazy.tsx` extension for code-split lazy loading; plain `.tsx` only for routes that must eagerly load. Route tree auto-generates to `src/routeTree.gen.ts` via the TanStack Router Vite plugin. + +## Data Flow + +``` +External source Data layer State UI +─────────────── ────────── ───── ── +EVM chains (RPC) ───> wagmi readContract ───> TanStack Query cache ───> Chakra components +EVM chains (write) ───> wagmi writeContract ───> TransactionContext ───> Toast notifications +The Graph subgraphs ───> graphql-request ───> TanStack Query cache ───> Chakra components +LI.FI API (prices/balances) ───> @lifi/sdk ───> TanStack Query cache ───> Chakra components +Token list URLs ───> fetch + Zod validate ───> TanStack Query cache ───> Chakra components +``` + +Caching strategy: +- All async state lives in TanStack Query. Components do not hold async data in local state. +- Token lists: `staleTime: Infinity`, `gcTime: Infinity` (fetched once per session). +- Token balances: refreshed every ~32s (`BALANCE_EXPIRATION_TIME`). +- Contract reads: Suspense-based -- component suspends until data arrives, then cached normally. + +## Environment Variables + +All variables use the `PUBLIC_` prefix and are validated with Zod in `src/env.ts`. Access via `import { env } from '@/src/env'` -- never `import.meta.env` directly. + +**App** + +| Variable | Required | Purpose | +|----------|----------|---------| +| `PUBLIC_APP_NAME` | yes | Application name | +| `PUBLIC_APP_DESCRIPTION` | no | App description | +| `PUBLIC_APP_URL` | no | Canonical URL | +| `PUBLIC_APP_LOGO` | no | Logo URL | +| `PUBLIC_ENABLE_PORTO` | no (default: true) | Enable Porto wallet connector | +| `PUBLIC_INCLUDE_TESTNETS` | no (default: true) | Include testnet chains | +| `PUBLIC_USE_DEFAULT_TOKENS` | no (default: true) | Include Uniswap default token list | + +**RPC endpoints** (all optional -- wagmi falls back to public RPCs) + +`PUBLIC_RPC_MAINNET`, `PUBLIC_RPC_OPTIMISM`, `PUBLIC_RPC_POLYGON`, `PUBLIC_RPC_ARBITRUM`, `PUBLIC_RPC_BASE`, `PUBLIC_RPC_GNOSIS`, `PUBLIC_RPC_SEPOLIA`, `PUBLIC_RPC_OPTIMISM_SEPOLIA`, `PUBLIC_RPC_ARBITRUM_SEPOLIA`, `PUBLIC_RPC_BASE_SEPOLIA`, `PUBLIC_RPC_GNOSIS_CHIADO`, `PUBLIC_RPC_POLYGON_MUMBAI` + +**Wallet / API** + +| Variable | Required | Purpose | +|----------|----------|---------| +| `PUBLIC_WALLETCONNECT_PROJECT_ID` | no (default: '') | WalletConnect project ID | +| `PUBLIC_ALCHEMY_KEY` | no | Alchemy RPC key | +| `PUBLIC_INFURA_KEY` | no | Infura RPC key | +| `PUBLIC_NATIVE_TOKEN_ADDRESS` | no (default: `0x0...0`) | Native token sentinel address | + +**Subgraph** (all-or-nothing: set all or remove subgraph code) + +| Variable | Required | Purpose | +|----------|----------|---------| +| `PUBLIC_SUBGRAPHS_API_KEY` | yes | The Graph API key | +| `PUBLIC_SUBGRAPHS_CHAINS_RESOURCE_IDS` | yes | `chainId:subgraphId:resourceId` comma-separated | +| `PUBLIC_SUBGRAPHS_ENVIRONMENT` | no (default: production) | `development` or `production` | +| `PUBLIC_SUBGRAPHS_DEVELOPMENT_URL` | no | Dev subgraph URL template | +| `PUBLIC_SUBGRAPHS_PRODUCTION_URL` | no | Production subgraph URL template | + +## Scripts + +| Command | Purpose | +|---------|---------| +| `pnpm dev` | Vite dev server + TanStack Router route watching | +| `pnpm build` | `tsc --noEmit` + Vite production build | +| `pnpm test` | Vitest single run | +| `pnpm test:watch` | Vitest watch mode | +| `pnpm test:coverage` | Coverage report (v8) | +| `pnpm lint` | Biome check | +| `pnpm lint:fix` | Biome auto-fix | +| `pnpm wagmi-generate` | Generate typed contract hooks from ABIs into `src/hooks/generated.ts` | +| `pnpm routes:generate` | Generate TanStack Router route tree into `src/routeTree.gen.ts` | +| `pnpm subgraph-codegen` | Generate GraphQL types from subgraph schemas into `src/subgraphs/gql/` | +| `pnpm docs:dev` | Documentation site dev server (vocs) | + +--- + +## Domain-Specific Sections + +### Number / Precision Handling + +Token amounts are always `bigint` internally. Never use `parseFloat`, `Number()`, or `.toFixed()` on token values. + +- **Input**: `BigNumberInput` component wraps `react-number-format` + viem's `parseUnits` / `formatUnits`. The `BigNumberInput` is the only place raw user strings convert to bigint. +- **Display**: `formatNumber(value, type)` from `src/utils/numberFormat.ts` with a `NumberType` context that sets decimal precision and formatting rules: + - `TokenTx` -- token transaction amounts + - `FiatTokenPrice` -- USD prices + - `SwapPrice` -- swap rate display + - `PortfolioBalance` -- portfolio totals +- The bigint -> formatted string conversion only happens at the UI boundary. + +### Provider / Context Hierarchy + +From `src/routes/__root.tsx`, outermost to innermost: + +``` +Chakra Provider (theme system, color mode via next-themes, default: dark) + Web3Provider + WagmiProvider (chains, transports, wallet connectors) + QueryClientProvider (TanStack Query client) + WalletProvider (ConnectKit wallet UI) + TransactionNotificationProvider (tx/signature toast context) + Header + Outlet (page content) + Footer + Devtools (React Query + Router devtools, dev only) + Vercel Analytics + Toaster +``` + +When adding a new provider: place it inside the outermost provider it depends on. Anything needing blockchain access goes inside `Web3Provider`. Anything needing tx notifications goes inside `TransactionNotificationProvider`. + +### Hook Patterns + +**Generated contract hooks** (`src/hooks/generated.ts`, auto-generated by `pnpm wagmi-generate`): +- `useRead{Contract}{Function}` -- standard read, manual refetch +- `useSuspenseRead{Contract}{Function}` -- Suspense read (preferred; component suspends until resolved) +- `useWrite{Contract}{Function}` -- write/mutation +- `useSimulate{Contract}{Function}` -- simulate before write to surface errors early + +**Web3 connection state** (`src/hooks/useWeb3Status.tsx`): +- `useWeb3Status()` -- returns `{ readOnlyClient, appChainId, address, isWalletConnected, isWalletSynced, switchChain, disconnect, ... }` +- `useWeb3StatusConnected()` -- same but throws if wallet is not connected; use inside components that are already gated by `withWalletStatusVerifier` + +**Token hooks**: +- `useTokens()` -- token list + LI.FI prices + account balances, sorted by balance value +- `useTokenLists()` -- Suspense-based parallel fetch of all configured token list URLs +- `useTokenInput()` -- manages selected token + amount state, fetches balance +- `useTokenSearch(tokens, query)` -- filters by name or symbol + +**Suspense convention**: all contract reads use Suspense. Wrap consuming components with `withSuspenseAndRetry()` to handle the loading and error states. + +### Error Handling Patterns + +**`withSuspenseAndRetry(Component)`** (`src/utils/suspenseWrapper.tsx`) -- primary pattern: +- Wraps in `QueryErrorResetBoundary` (TanStack Query) + `ErrorBoundary` + `Suspense` +- On error: shows error UI with a retry button that resets the query and re-renders +- Props: `suspenseFallback`, `errorFallback`, `defaultFallbackFormat` (`'default'` | `'dialog'`), `spinnerSize` + +**`withSuspense(Component)`** -- simpler variant when retry is not needed (no query resets). + +**`defaultFallbackFormat: 'dialog'`** -- renders the error in a modal. Use for critical errors where inline display is disruptive. + +**Transaction errors** -- handled by `TransactionNotificationProvider`. It catches rejected signatures, reverted transactions, and gas estimation failures, and surfaces them as toast notifications with user-friendly messages. Handles transaction replacement (gas bump) and cancellation by the wallet. + +### Smart Contract Integration + +Contracts are registered centrally and consumed via generated hooks. + +1. **ABIs**: stored as `as const` typed exports in `src/constants/contracts/abis/`. +2. **Registration**: `src/constants/contracts/contracts.ts` -- array typed as `ContractConfig<Abi>[]` with `name`, `abi`, and optional `address: { [chainId]: '0x...' }`. +3. **Runtime lookup**: `getContract(name, chainId)` returns `{ abi, address }` -- throws if contract or chain address not found. +4. **Hook generation**: `pnpm wagmi-generate` reads the contracts array and writes `src/hooks/generated.ts`. The custom `reactSuspenseRead` plugin in `src/lib/wagmi/plugins/reactSuspenseRead.ts` adds Suspense variants for all read functions. + +To add a new contract: save the ABI, add it to the contracts array, run `pnpm wagmi-generate`. + +### Wallet Access Control + +**`WalletStatusVerifier`** component (`src/components/sharedComponents/WalletStatusVerifier.tsx`) -- renders a fallback cascade based on wallet state: +1. Not connected -> `ConnectWalletButton` +2. Connected but `walletChainId !== appChainId` -> "Switch to [Network]" button +3. Connected + synced -> renders children + +**`withWalletStatusVerifier(Component)`** HOC -- wraps the component with the above logic. Already applied to `TransactionButton` and `SignButton` -- do not double-wrap. + +Sync check: `isWalletSynced = isWalletConnected && walletChainId === appChainId`. From 9bec715fdfe111e73e5e8496cc9472fed296eaf6 Mon Sep 17 00:00:00 2001 From: Gabito Esmiapodo <4015436+gabitoesmiapodo@users.noreply.github.com> Date: Wed, 1 Apr 2026 11:21:39 -0300 Subject: [PATCH 07/14] chore: sync hotfix and release commit types across all validators --- .github/workflows/conventional-commits-PR-title.yml | 7 ++++--- CLAUDE.md | 3 ++- commitlint.config.js | 1 + 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/conventional-commits-PR-title.yml b/.github/workflows/conventional-commits-PR-title.yml index 15cf4077..4a6740b0 100644 --- a/.github/workflows/conventional-commits-PR-title.yml +++ b/.github/workflows/conventional-commits-PR-title.yml @@ -9,7 +9,8 @@ jobs: runs-on: ubuntu-latest steps: - name: PR Conventional Commit Validation - uses: ytanikin/PRConventionalCommits@1.1.0 + uses: ytanikin/pr-conventional-commits@1.4.0 with: - task_types: '["feat","fix","docs","test","ci","refactor","perf","chore","revert"]' - add_label: 'false' + task_types: '["feat", "fix", "docs", "test", "ci", "refactor", "perf", "chore", "revert", "hotfix", "wip", "build", "style", "release"]' + custom_labels: '{"feat": "feature", "fix": "fix", "docs": "documentation", "test": "test", "ci": "CI/CD", "refactor": "refactor", "perf": "performance", "chore": "chore", "revert": "revert", "hotfix": "hotfix", "wip": "WIP", "build": "build", "style": "style", "release": "release"}' + add_scope_label: 'true' diff --git a/CLAUDE.md b/CLAUDE.md index 48d28512..f782a89f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -26,7 +26,7 @@ System architecture, data flow, provider hierarchy, and structural conventions a Three hooks run automatically and will block on failure: - **pre-commit:** lint-staged runs Biome check + Vitest on related files for staged changes -- **commit-msg:** commitlint enforces conventional commit format. Valid types: `feat`, `fix`, `docs`, `test`, `ci`, `refactor`, `perf`, `chore`, `revert`, `style`, `build`, `wip`, `release`. PR titles are also validated via CI. +- **commit-msg:** commitlint enforces conventional commit format. Valid types: `feat`, `fix`, `docs`, `test`, `ci`, `refactor`, `perf`, `chore`, `revert`, `style`, `build`, `hotfix`, `wip`, `release`. PR titles are also validated via CI. - **pre-push:** full `tsc --noEmit` type check (pushes with type errors will be rejected) ## Commit Standards @@ -54,6 +54,7 @@ Use [Conventional Commits](https://www.conventionalcommits.org/): | `perf` | Performance improvement | | `build` | Build system or external dependencies | | `revert` | Reverts a previous commit | +| `hotfix` | Urgent fix that bypasses the normal release cycle | | `wip` | Work in progress (avoid on main) | | `release` | Release-related changes | diff --git a/commitlint.config.js b/commitlint.config.js index cc1019a3..45f89143 100644 --- a/commitlint.config.js +++ b/commitlint.config.js @@ -11,6 +11,7 @@ export default { 'docs', 'feat', 'fix', + 'hotfix', 'perf', 'refactor', 'release', From 296ef2144b728463b14d273b90305adb73fb0c98 Mon Sep 17 00:00:00 2001 From: Gabito Esmiapodo <4015436+gabitoesmiapodo@users.noreply.github.com> Date: Wed, 1 Apr 2026 11:26:17 -0300 Subject: [PATCH 08/14] ci: add PR self-assignment workflow --- .github/workflows/pr-self-assign.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/workflows/pr-self-assign.yml diff --git a/.github/workflows/pr-self-assign.yml b/.github/workflows/pr-self-assign.yml new file mode 100644 index 00000000..f96a1c22 --- /dev/null +++ b/.github/workflows/pr-self-assign.yml @@ -0,0 +1,15 @@ +name: PR Self-Assignment + +on: + pull_request: + types: [opened, ready_for_review] + +jobs: + assign: + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - uses: hkusu/review-assign-action@v1 + with: + assignees: ${{ github.actor }} # assign pull request author From 3a00be1a3fa0fa8901a76dac6ec5293aaeae52d9 Mon Sep 17 00:00:00 2001 From: Gabito Esmiapodo <4015436+gabitoesmiapodo@users.noreply.github.com> Date: Wed, 1 Apr 2026 11:35:40 -0300 Subject: [PATCH 09/14] docs: update architecture.md reference from AGENTS.md to CLAUDE.md --- architecture.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/architecture.md b/architecture.md index 46889646..90c6da52 100644 --- a/architecture.md +++ b/architecture.md @@ -3,7 +3,7 @@ <!-- Keep this document up to date as the system evolves. It captures structural knowledge that helps both humans onboarding and agents building context at session start. Focus on the "shape" of the system — not usage instructions - (that's AGENTS.md) or API docs (that's code comments). --> + (that's CLAUDE.md) or API docs (that's code comments). --> ## Tech Stack From 03f85a5ee1d8cfb64e52b2c9d31d265bb17c7c7d Mon Sep 17 00:00:00 2001 From: Gabito Esmiapodo <4015436+gabitoesmiapodo@users.noreply.github.com> Date: Wed, 1 Apr 2026 11:35:59 -0300 Subject: [PATCH 10/14] docs: update bug template reference from AGENTS.md to CLAUDE.md --- .github/ISSUE_TEMPLATE/1-bug.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/1-bug.yml b/.github/ISSUE_TEMPLATE/1-bug.yml index 8d49b4fa..b3053186 100644 --- a/.github/ISSUE_TEMPLATE/1-bug.yml +++ b/.github/ISSUE_TEMPLATE/1-bug.yml @@ -66,7 +66,7 @@ body: - type: markdown attributes: value: | - > **Programmatic creation:** When using `gh` CLI or REST API, apply `severity: <level>` labels instead of using the Severity dropdown (e.g., `--label "severity: high"`). See the Label Conventions section in `AGENTS.md` for the full table. + > **Programmatic creation:** When using `gh` CLI or REST API, apply `severity: <level>` labels instead of using the Severity dropdown (e.g., `--label "severity: high"`). See the Label Conventions section in `CLAUDE.md` for the full table. - type: dropdown id: severity From ead606da7b45be64d37d3e83c74c37035fbf045f Mon Sep 17 00:00:00 2001 From: Gabito Esmiapodo <4015436+gabitoesmiapodo@users.noreply.github.com> Date: Wed, 1 Apr 2026 11:36:15 -0300 Subject: [PATCH 11/14] docs: update feature template reference from AGENTS.md to CLAUDE.md --- .github/ISSUE_TEMPLATE/2-feature.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/2-feature.yml b/.github/ISSUE_TEMPLATE/2-feature.yml index 79926725..e1f798fa 100644 --- a/.github/ISSUE_TEMPLATE/2-feature.yml +++ b/.github/ISSUE_TEMPLATE/2-feature.yml @@ -11,7 +11,7 @@ body: - type: markdown attributes: value: | - > **Priority:** When using `gh` CLI or REST API, apply `priority: <level>` labels (e.g., `--label "priority: high"`). See the Label Conventions section in `AGENTS.md` for the full table. + > **Priority:** When using `gh` CLI or REST API, apply `priority: <level>` labels (e.g., `--label "priority: high"`). See the Label Conventions section in `CLAUDE.md` for the full table. - type: textarea id: user-story From 71ebfd631a6e47aa4bb36303ac752da54e006b5b Mon Sep 17 00:00:00 2001 From: Gabito Esmiapodo <4015436+gabitoesmiapodo@users.noreply.github.com> Date: Wed, 1 Apr 2026 11:36:30 -0300 Subject: [PATCH 12/14] docs: update epic template reference from AGENTS.md to CLAUDE.md --- .github/ISSUE_TEMPLATE/3-epic.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/3-epic.yml b/.github/ISSUE_TEMPLATE/3-epic.yml index 88a980db..fa51fa51 100644 --- a/.github/ISSUE_TEMPLATE/3-epic.yml +++ b/.github/ISSUE_TEMPLATE/3-epic.yml @@ -11,7 +11,7 @@ body: - type: markdown attributes: value: | - > **Priority:** When using `gh` CLI or REST API, apply `priority: <level>` labels (e.g., `--label "priority: high"`). See the Label Conventions section in `AGENTS.md` for the full table. + > **Priority:** When using `gh` CLI or REST API, apply `priority: <level>` labels (e.g., `--label "priority: high"`). See the Label Conventions section in `CLAUDE.md` for the full table. - type: textarea id: objective From 5d7552250aec81157937d71156d1b226a08c7995 Mon Sep 17 00:00:00 2001 From: Gabito Esmiapodo <4015436+gabitoesmiapodo@users.noreply.github.com> Date: Wed, 1 Apr 2026 11:37:00 -0300 Subject: [PATCH 13/14] docs: update issue skill reference from AGENTS.md to CLAUDE.md --- .claude/skills/issue/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.claude/skills/issue/SKILL.md b/.claude/skills/issue/SKILL.md index 74e8d061..806f9391 100644 --- a/.claude/skills/issue/SKILL.md +++ b/.claude/skills/issue/SKILL.md @@ -29,7 +29,7 @@ Create a well-structured GitHub issue using the repo's own templates and `gh` CL ## Labels -Severity and priority are applied as labels, not form dropdowns. See the Label Conventions section in `AGENTS.md` for the full table and descriptions. +Severity and priority are applied as labels, not form dropdowns. See the Label Conventions section in `CLAUDE.md` for the full table and descriptions. - Bugs get a `severity: <level>` label (critical / high / medium / low). - Features and epics get a `priority: <level>` label (high / medium / low). From f21234f0f4abf0c638edbe92b00c003d762bd8d4 Mon Sep 17 00:00:00 2001 From: Gabito Esmiapodo <4015436+gabitoesmiapodo@users.noreply.github.com> Date: Wed, 1 Apr 2026 11:53:45 -0300 Subject: [PATCH 14/14] docs: update Node placeholder in bug template to 24.x --- .github/ISSUE_TEMPLATE/1-bug.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/1-bug.yml b/.github/ISSUE_TEMPLATE/1-bug.yml index b3053186..f7863826 100644 --- a/.github/ISSUE_TEMPLATE/1-bug.yml +++ b/.github/ISSUE_TEMPLATE/1-bug.yml @@ -58,7 +58,7 @@ body: render: bash placeholder: | OS: macOS 15.3 - Node: 22.x + Node: 24.x Package: 1.0.0 validations: required: false