Skip to content

Latest commit

 

History

History
225 lines (157 loc) · 11.8 KB

File metadata and controls

225 lines (157 loc) · 11.8 KB

dAppBooster

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. Read it when working on tasks that involve the system's structure or patterns.

Requirements

  • Node 24.15.0+ (enforced via engines.node in package.json; .nvmrc names the version we develop and test on). pnpm-workspace.yaml sets engineStrict: true, so pnpm install refuses to run on anything older.
  • pnpm 12.4.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 in two passes, then gitleaks on the staged changes. The first pass (.lintstagedrc.format.mjs) writes: Biome formats and fixes. The second (.lintstagedrc.mjs) only reads: Vitest runs the tests related to the staged files. They are split because lint-staged runs globs concurrently, so a single pass let a reformat land while another task was reading the same file.
  • 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: pnpm typecheck, then gitleaks over the commits being pushed. The second scan is the backstop for a bypassed pre-commit.

Commit Standards

Use Conventional Commits:

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
hotfix Urgent fix that bypasses the normal release cycle
wip Work in progress (avoid on main)
release Release-related changes

PR Workflow

  • main is the only long-lived branch. Branch off it, open the PR against it, delete the branch after merge. There is no develop.

  • Releases are a tag on main plus a GitHub release, not a separate branch

  • Every PR must reference an issue (Closes #N)

    No related issue? Use No related issue. as the first line of the Summary section.

  • 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)

  • Use /sdlc:create-pr to create PRs -- it reads the template and fills every section automatically

Label Conventions

GitHub form dropdowns (like the Priority field in issue templates) 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.

Priority (bugs, features, and epics):

Label Description
priority: critical Blocking work, system down, or security issue
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 "priority: high".

The /sdlc:create-issue skill applies these labels automatically when creating issues via CLI. Bug, feature, and epic templates include a Priority dropdown for web UI users, but labels are the source of truth for programmatic workflows.

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/<pagename>/
  • 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<Abi>[] 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 }).

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

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

Dependency Policy

pnpm-workspace.yaml holds parts of the tree to one version, and renovate.json disables the matching major updates so nobody re-proposes them by accident. Each hold has a reason:

  • wagmi stays on 2. connectkit and rainbowkit have no wagmi 3 release. @reown/appkit-adapter-wagmi and porto leave @wagmi/core and @wagmi/connectors as open peers, which pnpm fills with 3.x and 8.x on a fresh resolve, so the overrides block pins both to the exact versions wagmi 2 depends on. Two copies means two connector registries and a build that cannot resolve @wagmi/core/tempo. Renovate leaves those two alone entirely: they move when wagmi moves.
  • graphql stays on 16. @bootnodedev/db-subgraph asks for ^16, and a second copy of graphql in the tree breaks schema identity checks at runtime.
  • TypeScript stays on 6. TypeScript 7 is the Go-native compiler and no longer ships the JS compiler API typedoc reads, so pnpm typedoc:build cannot run on it.
  • @graphql-codegen/cli is overridden to ^7 so @bootnodedev/db-subgraph, which asks for ^5, uses the copy we do.

Renovate itself opens one batched pull request a week, waits three days after a release before proposing it, and never auto-merges. It does nothing until the Renovate GitHub App is installed on the repo.

Secret Scanning (gitleaks)

scripts/install-gitleaks.sh downloads the version named in .gitleaks-version into bin/ (gitignored) and checks its sha256. The git hooks and CI all call it, so everyone runs the same version against the same rules.

Accepted findings live in .gitleaksignore, one fingerprint per line. Today there is one: the published Anvil test account private key, in a test file on an old branch. A new secret produces a new fingerprint and still fails the scan.

Dead Code (knip)

pnpm knip reports files, exports and dependencies nothing reaches. knip.json treats the template's public surface as entry points -- shared components, hooks, the wagmi CLI config, the alternative wallet configs -- because those are meant to be unused until a project picks them up.

Unused files, unused dependencies and unlisted dependencies fail. Unused exports, unused types and duplicate named/default exports are reported as warnings: what is left is components and helpers the template offers on purpose, plus the export const X / export default X pair those components use.

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
  • 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 typecheck
  • pnpm test
  • pnpm knip
  • pnpm build (when feasible for runtime-impacting changes)

CI runs the same commands on every pull request, plus a gitleaks scan of the whole history and commitlint over the PR's commits and title. The typecheck job also builds the typedoc reference and the docs site, and is skipped on pull requests from forks because it needs the subgraph secrets.

References