Rename title/intro to be generic, add a 'Verifying changes' section, coding conventions, and an auto-generated files table. Add AGENTS.md as a symlink to CLAUDE.md so other agents pick up the same guide.
7.9 KiB
Agent Guide
This file provides guidance to AI coding agents working with this repository.
What is ThunderHub
ThunderHub is a Lightning Network node manager. It provides a web UI and GraphQL API for managing LND nodes — channels, payments, invoices, forwards, peers, chain transactions, and Boltz submarine swaps. It integrates with Amboss for node analytics and health monitoring.
Commands
npm run build # Build NestJS server + Vite client
npm run build:nest # Build server only
npm run build:client # Build client only
npm run start:dev # Dev: NestJS watch + Vite dev server (concurrent)
npm run lint # ESLint with auto-fix
npm run lint:check # ESLint without fix (used in CI)
npm run test # Jest (rootDir: src/, matches *.spec.ts)
npm run test -- --testPathPattern="channels" # Run tests matching a pattern
npm run test:e2e # E2E tests (config: test/jest-e2e.json)
npm run generate # GraphQL codegen — requires server running at localhost:3000; not for CI
Note: Package manager is npm, not pnpm/yarn. Node version: see .nvmrc.
Verifying changes
After making code changes, run these in order to confirm correctness:
npm run lint:check # Check for lint errors (no auto-fix)
npm run test # Run unit tests
npm run build # Confirm the full build succeeds
Run a targeted test when touching a specific module:
npm run test -- --testPathPattern="<module-name>"
Architecture
Monorepo with two apps under src/:
src/server/— NestJS backend (GraphQL API via Apollo, code-first schema)src/client/— React frontend (Vite, Apollo Client, React Router)
Each has its own tsconfig.json. The root tsconfig only includes src/server. The client tsconfig at src/client/tsconfig.json is strict mode with @/* aliased to ./src/*.
Server (src/server/)
Entry: main.ts bootstraps NestJS with Helmet, Winston logger, and optional BASE_PATH prefix.
Root module: app.module.ts wires up GraphQL (Apollo Driver, code-first), static file serving, JWT auth context, dataloaders, and scheduled tasks.
Config: config/configuration.ts is the single place all env vars are read. Returns a typed ConfigType. Uses @nestjs/config (global). Env files: .env.local overrides .env.
Module layout under modules/:
api/— GraphQL resolvers organized by domain. Each subdomain (channels, invoices, wallet, boltz, amboss, etc.) has*.module.ts,*.resolver.ts,*.types.ts, and optionally*.helpers.ts.node/— LND abstraction layer.NodeServiceis the facade resolvers call; it resolves the account by user ID, then delegates toLndService.LndServicewraps thelightningnpm package directly.accounts/— In-memory account store.AccountsServiceimplementsOnModuleInit: at startup, reads SSO config and account config files, creates authenticated LND gRPC connections (authenticatedLndGrpc), and storesEnrichedAccountobjects (account data +lndhandle) in a map keyed by account hash.security/— Three global guards registered asAPP_GUARD:GqlAuthGuard(JWT via passport),RolesGuard,GqlThrottlerGuard. Key decorators:@Public()(skip auth),@Roles(...),@CurrentUser()(extractsUserIdfrom GQL context).dataloader/— Creates per-requestDataLoaderinstances for batching Amboss API lookups (nodesLoader,edgesLoader). Injected into GraphQL context.fetch/— HTTP client with optional SOCKS proxy (Tor) support.graphqlFetchWithProxy()for external GraphQL APIs.sub/— LND event subscriptions (invoices, payments, forwards, channels, backups) usingasync.auto()with retry logic. Emits events toSseServicefor real-time client updates.sse/— Server-sent events endpoint for pushing LND events to the client.
Request flow (server)
HTTP request → GqlAuthGuard (JWT validation via passport) → RolesGuard → ThrottlerGuard
→ Resolver receives @CurrentUser() with { id: accountHash }
→ Resolver calls NodeService.method(user.id, ...)
→ NodeService looks up EnrichedAccount by hash (in-memory map)
→ NodeService delegates to LndService.method(account, ...)
→ LndService calls lightning library with account.lnd handle
Async error helpers (utils/async.ts)
to<T>(promise)— Awaits and returns result, throws on errortoWithError<T>(promise)— Returns[data, undefined] | [undefined, error]tuple (Go-style)
The lightning library returns errors as arrays [title, string, { err }]; lnd.helpers.ts has a dedicated to() that transforms these.
Client (src/client/src/)
- Styling: Hybrid — styled-components (with
styled-themingfor dark/light) + Tailwind CSS. New components use shadcn/ui (New York style, configured incomponents.jsonat repo root). - Imports: Use
@/path alias (maps tosrc/client/src/). - Config: Runtime config fetched from server at
/api/configon bootstrap (not build-time env vars). - Context pattern: Dual-context for state + dispatch with custom hooks (
useXState(),useXDispatch()). Contexts: Config, Price, SSE, Chat, Dash, Notification. - GraphQL: Operations in
graphql/queries/andgraphql/mutations/asgqltemplate literals. Codegen generates__generated__/*.generated.tsxfiles with typed Apollo hooks next to each operation file. - Real-time: SSE (
EventSource) for receiving LND events;useListenerhook triggers Apollo cache refetches and toast notifications. - Icons: Lucide React.
GraphQL workflow
- Schema is code-first: resolvers define it via NestJS/GraphQL decorators.
schema.gqlis auto-generated in dev mode. - Client operations are
.tsfiles withgqltemplate literals insrc/client/src/graphql/. - Run
npm run generate(requires server running atlocalhost:3000) to produce typed hooks in__generated__/directories. - Do not edit
schema.gqlor*.generated.tsx— they are auto-generated.
Pre-commit hooks
Husky runs lint-staged on commit for *.ts and *.tsx files:
prettier --writejest --bail --findRelatedTests --passWithNoTestseslint --fix
Prettier config
Single quotes, trailing commas (es5), 2-space tabs, 80 char width, no parens on single arrow params. See .prettierrc.
Coding conventions
Server
- Use
toWithError<T>(promise)(fromutils/async.ts) for LND calls that may fail — returns[data, undefined] | [undefined, error]. Useto<T>(promise)when you want to throw on error. - The
lightninglibrary errors are arrays; use the dedicatedto()inlnd.helpers.ts(not the generic one) when wrapping rawlndcalls. - Tests are co-located as
*.spec.tsnext to the file under test.
Client
- New UI components must use shadcn/ui (New York style). Do not add new styled-components.
- Use the
@/import alias for all client-side imports (maps tosrc/client/src/). - Follow the dual-context pattern (
useXState()/useXDispatch()) for new shared state. - Icons: use Lucide React only.
Auto-generated files — do not edit
The following files are generated automatically and must not be edited by hand:
| File/pattern | Generated by |
|---|---|
schema.gql |
NestJS GraphQL (code-first, on server start) |
src/client/src/graphql/**/__generated__/*.generated.tsx |
npm run generate (GraphQL codegen) |
To regenerate: start the dev server (npm run start:dev) then run npm run generate.