Architecture
How the NestJS backend and Next.js frontend are organized, and the guard/filter chain every request runs through.
Botanary is two repositories with a strict division of labor: the backend builds and relays, the frontend signs. Neither side crosses that line, and the codebase layout reflects it directly.
Backend: one module per API surface
The backend is a NestJS app. Its root AppModule imports one module per off-chain service surface -
Session, Account, Devices, Balance, Delegation, Activity, Audit, Rules, Simulation, Money, Gas,
Notifications, Panic, Recovery, Depeg, UserOps, Agent, Waitlist, Marketplace, Market, Launchpad,
Markets - plus foundation modules for config, ports, data, and policy. On disk, that's a src/modules/* folder per surface
(account, activity, agent, audit, balance, delegation, depeg, devices, gas,
holdings, launchpad, market, marketplace, markets, money, notifications, panic,
recovery, rules, session, simulation, userops, waitlist), alongside shared layers:
common/, config/, data/, domain/, indexer/, policy/, and ports/.
Two global guards run on every request, in order: a ThrottlerGuard for rate limiting, then a
SessionGuard for auth (see Authentication for what it checks). A
global ApiErrorFilter renders every non-2xx response through one envelope:
{ error: { code, message, declineReason?, details?, requestId } }. Domain errors carry their own
HTTP status, code, and (where relevant) decline reason; other framework exceptions map by status
(401 to unauthorized, 404 to not_found, 409 to conflict, 429 to rate_limited, anything else to
validation_error); anything unexpected becomes a logged 500 internal.
Bootstrapping enables CORS with credentials, sets the API prefix from config (excluding a short list
of unprefixed paths, health among them), installs a global validation pipe (whitelist: true,
transform: true, 422 on a failed validation), and serves interactive Swagger UI from the committed
OpenAPI file - see The API seam.
ports/: everything external is an interface
External services - the bundler, the paymaster, the RPC provider, Privy, price oracles, and more -
are defined as interfaces under ports/, each with a fake implementation under ports/fakes and, for
adapters wired to a real vendor, a real/*.adapter.ts implementation. Which set gets wired up is an
adapter-selection concern, not something individual modules decide for themselves - a module talks to
the interface and never knows whether it's a fake or a real vendor underneath. This is what lets the
entire local dev stack run against fakes with no external dependencies at all - see
Local development.
Frontend: generated client, design system, feature surfaces
The frontend is Next.js 15 on the App Router, React 19, TypeScript in strict mode (including
noUncheckedIndexedAccess), built with pnpm on Node 22. Its layout mirrors the same build-then-sign
split: src/api holds the generated client and hooks (nothing hand-written), src/mocks holds the
MSW handlers and fixtures that let the app run with no backend at all, src/ui is an in-repo design
system, src/features/* holds the actual feature surfaces (Home, Delegations, the grant wizard,
Send/Receive/Swap, Activity, Settings, Notifications, Onboarding, session lock), and src/app is the
Next App Router shell.
Every state-changing action is a build, sign, relay flow - the API never signs; the owner signs client-side. This is the one rule the whole architecture is built to enforce.
What's real and what's coming
Not every feature you'll find wired up on the frontend has a backend behind it yet. A few v2 surfaces
- a chat copilot, mandate mode in the grant wizard, an owner-set depeg rule, and a yield surface - are built end-to-end on the frontend but gated behind a "coming soon" flag in the real build, because their backend endpoints don't exist yet. If you're exploring the frontend codebase, that flag is the signal to check before assuming an endpoint exists.