14 KiB
AGENTS.md
This file provides guidance for agents working in the Darano monorepo.
Repository Structure
Multi-service monorepo for the Darano financial/crypto platform. Each subdirectory is its own independent git repository:
| Directory | Language | Role |
|---|---|---|
api/ |
Go (Gin) | HTTP REST gateway — the only public-facing service |
auth/ |
Go (gRPC) | Authorization — OTP, JWT, permissions, identity |
wallet/ |
Go (gRPC) | Wallet — assets, transactions, Stellar blockchain, market |
ui/ |
TypeScript/Next.js | Customer-facing frontend |
AdminPanel/ |
Python/Django | Internal admin panel (bypasses api, hits Postgres directly) |
proto/ |
Protobuf | Central schema definitions shared by all services |
DevOps/ |
Docker Compose | Infrastructure — Postgres, Redis, RabbitMQ, MinIO, Traefik |
docs/ |
MkDocs | Documentation site |
Root-level: Procfile (deployment), CLAUDE.md (Claude Code config) — these are the monorepo's coordination files, not service-level.
Service Communication Architecture
Browser/Client → api (REST/HTTP) → auth, wallet/market/alert (gRPC)
AdminPanel ──────────────────────→ Postgres directly (bypasses api)
The api gateway is the only HTTP-facing service. All inter-service communication is gRPC.
API Gateway (api/)
- Entry/composition:
api/main.go→cmd.Execute()(Cobra) →cmd/apiRuntime, which explicitly owns clients, handlers, router, HTTP servers, permission synchronization, and cleanup. - Upstream boundary:
api/application/port.Upstreamscomposes generated service contracts.api/infrastructure/grpcclientimplements lazy connection creation, active-call tracking, configurable idle closure (2-minute default), reconnection, health checks, and explicit shutdown without reflection. - Handlers:
api/interface/http/handler/— one file per domain. Handlers depend onapplication/port.Upstreams. - Routing:
api/interface/http/handler/routing.go— routes grouped into/v1/public/,/v1/client/,/v1/admin/. The admin group is protected but currently empty; internal routes remain disabled. - Middleware chain (order matters): APM → Profiling → Prometheus → CORS → JSON → I18n → Error → optional Logger
- Endpoints:
GET /(health),GET /metrics(Prometheus),GET /swagger/*any,GET /ws(WebSocket) - Profiling:
net/http/pprofis imported (blank import_); runs on a separate port whenProfiling.Enabledin config
Wallet Binary (wallet/)
Single binary hosts multiple sub-services via Cobra subcommands: wallet, market, alert, internal_wallet, stream. These are spawned concurrently via errgroup in cmd/cmdServe/main.go.
Concurrent build safety: The Makefile uses flock on ./.build/air-build.lock to serialize protobuf generation and binary writes when multiple air instances run simultaneously (make dev-wallet, make dev-market, etc. all trigger make build).
Config
- Go services use
knadh/koanf(notfigas older docs may suggest) to parse TOML config files - Global singleton:
config.Cfg— async.Onceensures it's initialized exactly once - Defaults baked into code:
config.goinapi/sets defaultipg_callback_urlandui_server_error_status_urlbefore loading the TOML file (TOML overrides defaults) - Struct tags: use
koanf:"field-name"(not env vars)
Config files reference
| Service | Config file |
|---|---|
| API | ./cfg.toml (or --conf ./config.cfg) |
| Wallet sub-services | ./wallet.cfg.toml, ./market.cfg.toml, ./alert.cfg.toml, ./stream.cfg.toml |
| Internal wallet | ./wallet.internal.cfg.toml |
Commands
Go services (api/, auth/)
cd api/ # or cd auth/
make dep # install buf, protoc plugins, swag, air
make build # fmt + proto gen + binary → ./build/main
make dev # build + hot reload via air
make test # go test ./...
./build/main serve --conf ./cfg.toml
Wallet service (wallet/)
cd wallet/
make build # proto gen + binary
make dev-wallet # hot reload wallet sub-service (.wallet.air.toml)
make dev-market # hot reload market sub-service (.market.air.toml)
make dev-stream # hot reload stream sub-service (.stream.air.toml)
make run-wallet # ./build/main serve wallet -c ./wallet.cfg.toml
make run-market # ./build/main serve market -c ./market.cfg.toml
make run-internal # ./build/main serve internal_wallet -c ./wallet.internal.cfg.toml
make run-alert # ./build/main serve alert -c ./alert.cfg.toml
make run-stream # ./build/main stream -c ./stream.cfg.toml
make grpc-ui-wallet # open grpcui on port 7210 (localhost:8200)
make grpc-ui-market # open grpcui on port 7300 (localhost:8300)
make grpc-ui-alert # open grpcui on port 7400 (127.0.0.1:8400)
Important: When running multiple sub-services concurrently via make run or multiple make dev-* instances, the Makefile uses flock to prevent concurrent protobuf generation or binary overwrites. Don't parallelize make (-j) without flock — it will corrupt stub files.
AdminPanel (AdminPanel/)
cd AdminPanel/
# DB setup (uv virtual env)
uv run python src/manage.py makemigrations
uv run python src/manage.py migrate
make run # runserver on 0.0.0.0:8080
make dev # watch mode using funzzy
make proto # buf generate → proto stubs via betterproto
make build-msg # compile Django messages (i18n)
UI (ui/)
cd ui/
yarn build-proto # generate TS types from protos (buf generate)
yarn dev # Next.js dev server (also runs buf generate)
make dev # same, bound to 0.0.0.0:3000
yarn lint # ESLint
yarn lint:fix # ESLint with auto-fix
yarn build # production build (also runs buf generate)
Protobuf Code Generation
All services depend on generated code. Always run make build-proto (or yarn build-proto for UI) before building any service.
Proto definitions live in proto/ with subdirectories: base/, auth/, wallet/, market/, alert/, errors/. Each service has its own buf.gen.yaml controlling code generation output into domain/stub/go/ (Go) or src/types/stub/ (TypeScript).
Gotcha: The Go Makefiles strip omitempty from all JSON struct tags in .pb.go files after generation. This is intentional — protobuf JSON serialization needs consistent field presence. Do not re-add omitempty manually.
Go Service Layer Pattern
Both auth and wallet follow a layered architecture:
config/— TOML config via koanf.config.Cfgglobal singleton,sync.Onceinitialized.domain/stub/go/— Generated protobuf Go code. Never edit manually.cmd/— Cobra CLI entry points. Subcommands map to serve modes.repository/— Data access. AggregatesIPostgres,IRedis,IService, andIQueue(wallet only) interfaces.core/(wallet) /usecase/(auth) — Business logic. Implements gRPC server interfaces from proto.util/— Shared helpers; no business logic.
In auth, usecase.UseCase interface directly embeds the generated gRPC server interfaces (authv1.AuthorizationServiceServer, authv1.InternalAuthorizationServiceServer).
In wallet, core/ contains sub-packages: walletImp/, marketImp/, alertImp/, cronJobs/.
API Gateway Patterns
Request/Response Flow
HTTP request → interface/http middleware → interface/http/handler → application Upstreams port → infrastructure/grpcclient → backend service
- Response helpers:
interface/http/handler/response.go—JSON()andJSONList[X]()wrap responses intransport.Response{Meta, Data}. Pointer-backed empty lists use theEmptyListsentinel to serialize[]rather thannull. - HTTP-to-gRPC context:
interface/http/handler/contextWithMetadata()copies HTTP headers into incoming and outgoing gRPC metadata. - Handler interface:
interface/http/handler.Serverreceives theapplication/port.Upstreamsboundary and configuration explicitly.
Route conventions
- Public routes:
/v1/public/{domain}/{action}— no JWT needed - Client routes:
/v1/client/{domain}/{action}— JWT required (validated byAuthorizationMiddleware) - Admin routes:
/v1/admin/{domain}/{action}— JWT required (mostly placeholder currently)
UI Architecture
- Next.js App Router under
src/app/. Notable route groups:dashboard/(auth-protected),auth/login/,blog/(MDX content),projects/,receipt/. - Auth:
src/middleware.tsuses next-auth to protect/dashboard/:path*. Exports default fromnext-auth/middleware. - Services:
src/services/— plain functions wrapping axios calls to the api gateway, typed with generated protobuf types. - State:
src/stores/— Zustand stores. - Hooks:
src/hooks/— TanStack Query hooks on top of service functions. - Components: HeroUI + Tailwind CSS with RTL support (Persian/Farsi UI).
- Forms: Formik + Yup (older), react-hook-form (newer).
- Date picker:
@amir04lm26/react-modern-calendar-date-picker(Jalali calendar). - MDX: Blog content uses
.mdxfiles with remark/rehype plugins. - Path alias:
@/*maps to./src/*. - Output:
standalonemode for Docker. Image domains hardcoded innext.config.mjsremote patterns.
Infrastructure
Dev infrastructure: DevOps/dev/compose.yml — PostgreSQL 16, Redis, RabbitMQ, MinIO. Each Go service has its own DB name.
All Go services instrumented with Elastic APM and Prometheus metrics. Traefik handles TLS/routing in production.
Gotchas and Non-Obvious Details
- Config library: Services use
knadh/koanfwith TOML parser, notfig. Tags arekoanf:"field-name". - gRPC connection lifecycle: Connections are lazy — created on first RPC. Active calls prevent idle closure; peers close after the configured inactivity timeout (2-minute default) and reconnect on the next call.
- Peer mapping:
infrastructure/grpcclientmaps configured peers explicitly; the former reflection/enum generator is removed. - Swagger docs: Generated by
swag initand served viainterface/http. URL adapts per environment (prod →api.darano.ir, dev →dev.api.darano.ir, local →localhost:<port>). - Multi-service wallet: The wallet binary serves 5 sub-services.
make runkills existing processes matching the service name pattern viapgrep | xargs killbefore starting. - Concurrent dev: Running multiple
make dev-*targets simultaneously requires theflockserialization in the Makefile. Usingmake -jwithout flock will corrupt the build. - Proto stubs excluded from watchers:
.air.tomlexcludesdomain/stub/from file watching. Air also excludesswagger/andtestdata/. - API testing requests:
api/req/contains JS files for API request testing (axios-based) — not part of the app, useful for manual testing. - Django Admin: Uses
unfoldtheme (AdminPanel admin customizations insites.pyandunfoldconf.py). - Proto generation for AdminPanel: Generates into root directories (
base/,wallet/, etc.) then deletes them withrm -rf. The betterproto generator outputs Python classes directly.
AdminPanel Deep Dive
- Architecture: Django admin panel that bypasses all Go backend services. Connects directly to
core_db(same Postgres as Go services) for read/write. Uses Django DB routers insrc/adminpanel/db/routers.pyto routecoreLogicmodels tocore_dband other apps todefault. - Models:
src/coreLogic/models.pycontainsmanaged = FalseDjango models — manual replicas of Go GORM models generated viainspectdb. Not auto-generated from protos.make protogenerates betterproto Python stubs separately but Django models are maintained by hand. Never edit models.py manually — it drifts from Go services. - Admin classes:
src/coreLogic/admin/— 17 admin files (asset.py,wallets.py,market.py, etc.). All inherit fromMultiDBModelAdmininsrc/utils/base_admin.pywhich handles: multi-database writes (using="core_db"), soft deletes viadeleted_at, asset-level permission filtering, and Jalali date widgets. - Permission system:
src/usermapper/user_perm.py+src/coreLogic/acl.py— admin users get asset-level access control.save_modelchecksuser_perm.can_access_asset()before allowing writes. - Key gotchas:
check_token_policy()inadmin/asset.py:161duplicates validation from wallet service. Changes to asset validation must be made in both places.GENERIC_ASSET_META_VALUEinadmin/asset.py:31is a hardcoded JSON blob — if the wallet service changes asset metadata structure, this must be updated too.- The router has a typo:
no_migartion→ should beno_migration. - All
coreLogicmodels are read viacore_dbdirectly. Any admin write bypasses Go services — no trustline updates, no blockchain operations, no validation from wallet/market services.
Ongoing Refactoring: DDD / Clean Architecture
This repository is being migrated to a unified Domain-Driven Design / Clean Architecture. See REFACTORING-PLAN.md for the full plan.
Go services current state: API, Auth, and Wallet configuration and architecture phases are complete on feat/refactor-v1; use the refactoring tracker and audit for the current package boundaries and verification evidence.
AdminPanel current state: Direct DB access to core_db with no gRPC layer. Business logic duplicated in Django admin classes (check_token_policy, auto_gen instead of delegating to Go services). managed = False models drift from Go GORM models.
Target: All Go services follow domain/ → application/ → infrastructure/ → interface/ with inward dependencies. AdminPanel routes writes through gRPC to Go services while keeping reads from DB for performance.
Migration phases (see plan for details): Config standardization → auth/ restructure → wallet/ restructure → api/ restructure → AdminPanel service layer → shared domain types. Parallel workstreams: Go services restructure can run alongside AdminPanel gRPC client creation.