Files
nick-doc/10 - Services/backend.md
Siavash Sameni e52ffce48a docs: sync vault with codebase state (2026-06-12)
- Update backend, frontend, scanner, deployment, amanat-assist service docs
- Update System Overview, Scanner Architecture, Telegram Mini App flow
- Update 10 - Services/README.md
- Add Tenant data model, Tenant API reference, Tenant Storefront Flow
- Add Multi-Shop Branch Project Scan (2026-06-10)
- Add tenant.md service doc
- Append activity log entry
- Reflects archived/search/stats route fix and new E2E test suite

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 11:42:18 +04:00

426 lines
25 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Backend Service — amn-backend
## 1. Overview
`amn-backend` is the Express 5 / TypeScript API server that powers the Amanat escrow marketplace. It is the single authoritative backend for the dev.amn.gg (escrow-dev) and multi.amn.gg (escrow-multi) stacks.
| Field | Value |
|---|---|
| Current version | **2.11.43** |
| Status | Production — receiving active feature development |
| Runtime | Node ≥ 22 |
| Framework | Express 5 (TypeScript) |
| Primary DB | PostgreSQL via Drizzle ORM |
| Mongo status | **Removed** — Mongoose was fully stripped; PostgreSQL is the sole persistence layer |
| Repo | `git@git.tbs.amn.gg:escrow/backend.git` |
| Dev stack host | `root@89.58.32.32` — Arcane project `escrow-dev` |
---
## 2. Tech Stack
| Layer | Technology | Notes |
|---|---|---|
| HTTP framework | Express 5 | Async error propagation built in |
| Language | TypeScript (strict) | tsc gate on every CI push |
| Runtime | Node ≥ 22 | Also used for CI typecheck step |
| Database | PostgreSQL 15 via Drizzle ORM (`drizzle-orm ^0.45.2`, `pg ^8.21.0`) | Single source of truth; 19+ migrations landed |
| Auth | JWT (access + refresh) + WebAuthn/Passkey + Google OAuth | `JWT_SECRET`, `REFRESH_TOKEN_EXPIRES_IN` |
| Session / Mini App | Telegram Mini App `initData` verification | `TELEGRAM_WEBAPP_URL` |
| Realtime | Socket.IO with Redis adapter (`@socket.io/redis-adapter`) | Room-scoped events |
| Cache / Pub-Sub | Redis | `REDIS_URI` |
| Rate limiting | express-rate-limit (in-memory; Redis adapter planned) | Auth 10/15 min, payment 30/15 min, AI 20/15 min, global 100/15 min |
| Security headers | Helmet | CSP, X-Frame-Options, etc. |
| File uploads | Multer | MIME validation, `UPLOAD_PATH` |
| Email | Nodemailer (SMTP) + Resend | `SMTP_*` / `RESEND_API_KEY` |
| Price oracle | Chainlink + OffchainFX | Depeg protection, `ORACLE_MAX_STALENESS_S` |
| AML | Chainalysis + OFAC SDN | `CHAINALYSIS_API_KEY`, `OFAC_SDN_URL` |
| AI | OpenAI | Descriptions, moderation |
| CI | Woodpecker CI | `.woodpecker/*.yml` |
| Process model | Node cluster | `CLUSTER_WORKERS` workers + master |
---
## 3. Directory Structure
```
backend/
├── src/
│ ├── app.ts # Express bootstrap: middleware chain, route registration, server creation
│ ├── cluster.ts # Node cluster master — forks CLUSTER_WORKERS child processes
│ ├── controllers/ # Thin HTTP handlers that delegate to services
│ ├── db/ # Drizzle/Postgres layer
│ │ ├── schema/ # Per-table Drizzle schema files + index.ts barrel
│ │ ├── migrations/ # Numbered SQL migration files (00000018+)
│ │ └── repositories/ # DrizzleXxxRepo classes + factory.ts
│ ├── infrastructure/
│ │ └── socket/ # Socket.IO server init, room helpers, emit wrappers
│ ├── models/ # Legacy placeholder (Mongoose removed; schemas now in db/schema/)
│ ├── routes/ # Standalone Express Router files (dispute, blog, points, amn-scanner webhook)
│ ├── scripts/ # CLI utilities — seed:users, seed:categories, tg-notify.cjs (CI)
│ ├── seeds/ # Fixture data for local dev (Postgres-capable, idempotent)
│ ├── services/ # Domain service modules (see §4)
│ ├── shared/
│ │ ├── config/index.ts # Typed env-var loader — single import for all config
│ │ ├── middleware/ # authMiddleware, roleGuard, errorHandler, validators
│ │ ├── types/ # Cross-cutting TypeScript types and enums
│ │ └── utils/response-handler.ts # Standard success/error envelope
│ └── utils/ # Pure utility functions: logger, currencyUtils, etc.
├── .woodpecker/ # CI pipeline definitions (cleanup, development, manual, production)
├── Dockerfile.prod # Multi-stage production image
└── drizzle.config.ts # Drizzle Kit configuration
```
---
## 4. Key Services / Modules
| Service path | Description |
|---|---|
| `services/auth/` | JWT issue/refresh, Google OAuth, WebAuthn/Passkey registration and assertion, password reset |
| `services/user/` | User profile CRUD, preferences, address book |
| `services/marketplace/` | PurchaseRequest, SellerOffer, RequestTemplate, ShopSettings — core escrow marketplace |
| `services/payment/` | Payment orchestration: provider adapters, internal ledger (available/held/releasable), reconciliation, safety confirmations |
| `services/payment/adapters/` | Provider-neutral adapter interface + registry; plugs in DePay, SHKeeper, amn.scanner, Request Network |
| `services/payment/requestNetwork/` | Request Network pay-in creation, in-house checkout rehydration, HMAC-verified webhook |
| `services/payment/wallets/` | HD-derived destination addresses, sweep orchestration, gas top-up |
| `services/payment/ledger/` | Funds ledger tracking available / held / releasable balances per payment |
| `services/payment/safety/` | Transaction Safety Provider: AML screening, min-confirmation thresholds |
| `services/blockchain/` | Web3 read helpers: balance checks, tx verification across ETH / BSC / Base / TON |
| `services/chat/` | Conversations, messages, attachments |
| `services/dispute/` | Dispute lifecycle: open, evidence upload, mediator assignment, release-hold |
| `services/notification/` | Template-based notification delivery (in-app + Telegram); mark-as-read |
| `services/telegram/` | Bot webhook handler, Mini App `initData` verification, identity link/unlink, seller notifications |
| `services/points/` | Loyalty points accrual, levels, referrals, redemption |
| `services/blog/` | Blog posts, categories, comments (public read / admin write) |
| `services/digital-goods/` | Encrypted digital-goods delivery; key stored under `DIGITAL_GOODS_ENC_KEY` |
| `services/file/` | Multer multipart upload, MIME validation, static serving under `/uploads` |
| `services/email/` | Nodemailer SMTP + Resend transport, templated emails |
| `services/ai/` | OpenAI-backed request description generation and content moderation |
| `services/redis/` | Redis client singleton, cache helpers, pub-sub wrappers |
| `services/admin/` | Admin-only endpoints: data cleanup (provider-scoped), confirmation thresholds, awaiting-confirmation view |
| `services/collection/` | Collection management (multi-seller feature) |
| `services/delivery/` | Delivery tracking and status |
| `infrastructure/socket/` | Socket.IO server attached to HTTP server; Redis adapter for multi-process pub-sub |
---
## 5. API Surface Summary
All API routes are mounted under `/api/`. The table below lists top-level route groups.
| Mount path | Service module | Auth | Purpose |
|---|---|---|---|
| `/api/auth` | `services/auth/authRoutes.ts` | mixed | Login, register, refresh, OAuth, passkey |
| `/api/user` / `/api/users` | `services/user/userRoutes.ts` | JWT | Profile, preferences |
| `/api/address` | `services/user/addressRoutes.ts` | JWT | Address CRUD |
| `/api/marketplace/requests` | `services/marketplace/` | JWT | PurchaseRequest CRUD |
| `/api/marketplace/offers` | `services/marketplace/` | JWT (seller) | SellerOffer CRUD |
| `/api/marketplace/templates` | `services/marketplace/` | JWT (seller) | RequestTemplate CRUD |
| `/api/marketplace/categories` | `services/marketplace/` | public read | Category list |
| `/api/marketplace/shop-settings` | `services/marketplace/shopSettingsController.ts` | JWT (seller) | Shop profile |
| `/api/payment` | `services/payment/paymentControllerRoutes.ts` | JWT | Payment CRUD, status, export |
| `/api/payment/decentralized` | `services/payment/decentralizedPaymentRoutes.ts` | mixed | Legacy/manual Web3 save and verify |
| `/api/payment/request-network` | `services/payment/requestNetwork/` | mixed + HMAC | RN pay-in, checkout rehydrate, webhook |
| `/api/payment/derived-destinations` | `services/payment/wallets/` | JWT (admin) | HD address list, sweeps, cron config |
| `/api/telegram` | `services/telegram/telegramRoutes.ts` | mixed | Mini App session, bot webhook, identity link |
| `/api/chat` | `services/chat/chatRoutes.ts` | JWT | Conversations, messages |
| `/api/notification` | `services/notification/` | JWT | List, mark-as-read |
| `/api/disputes` | `services/dispute/` | JWT | Dispute CRUD, evidence, release-hold |
| `/api/blog` | `services/blog/blogRoutes.ts` | mixed | Public reads, admin writes |
| `/api/points` | `services/points/pointsRoutes.ts` | JWT | Points, levels, referrals |
| `/api/ai` | `services/ai/aiRoutes.ts` | JWT | OpenAI helpers |
| `/api/files` | `services/file/fileRoutes.ts` | JWT | Multipart upload |
| `/api/email` | `services/email/emailRoutes.ts` | JWT | Email dispatch |
| `/api/trezor` | `services/trezor/trezorRoutes.ts` | JWT | Trezor hardware-wallet ops |
| `/api/admin/cleanup` | `services/admin/dataCleanupRoutes.ts` | JWT (admin) | Data cleanup (must be provider-scoped) |
| `/api/admin/rn/networks` | `services/payment/requestNetwork/networkRegistryRoutes.ts` | JWT (admin) | RN chain/token registry |
| `/api/admin/settings/confirmation-thresholds` | `services/admin/confirmationThresholdRoutes.ts` | JWT (admin) | Runtime confirmation thresholds |
| `/health` | `app.ts` | public | Docker healthcheck; surfaces active Postgres store modes |
Full per-endpoint details: [[03 - API Reference/API Overview]]
---
## 6. Database
### PostgreSQL (primary)
- Driver: `pg ^8.21.0` via Drizzle ORM (`drizzle-orm ^0.45.2`)
- Connection: `PG_URL` (primary pool); `PG_VITAL_URL` / `PG_NONVITAL_URL` for split-pool configuration
- Pool tuning: `PG_POOL_MAX`, `PG_POOL_SIZE`, `PG_NONVITAL_POOL_MAX`
- Migrations: numbered SQL files in `src/db/migrations/` (00000018+), applied via Drizzle Kit (`npx drizzle-kit migrate`)
- Repositories: `DrizzleXxxRepo` classes in `src/db/repositories/`; factory pattern via `factory.ts`
- Seeds: idempotent Postgres-capable seed scripts under `src/seeds/`; auto-run on start when `AUTO_SEED_ON_START=true`
### MongoDB (retired)
MongoDB and Mongoose have been **fully removed** from the runtime. The `MONGODB_URI` and `MIGRATION_MONGO_URL` env vars exist only for optional data backfill tooling in `src/db/repositories/migration/`. No Mongo connection is established at server boot. `MONGO_CONNECT_MODE=never` is the effective runtime mode.
The `DATABASE_URL` / `POSTGRES_URL` aliases are accepted for compatibility; prefer `PG_URL`.
---
## 7. Auth Model
### JWT
- Access tokens signed with `JWT_SECRET`; expiry controlled by `JWT_EXPIRES_IN`
- Refresh tokens with `REFRESH_TOKEN_EXPIRES_IN`; stored and rotated server-side
- `authMiddleware` in `shared/middleware/` verifies tokens and attaches `req.user`
- Role-based access via `roleGuard('admin' | 'seller' | 'buyer' | 'resolver' | 'guard')`
### WebAuthn / Passkey
- Passkey registration and assertion handled in `services/auth/`
- Enables passwordless login on supported clients
### Google OAuth
- `GOOGLE_CLIENT_ID` enables Google OAuth 2.0 sign-in
### Telegram Mini App
- Mini App sessions verified via Telegram `initData` HMAC in `services/telegram/`
- Identity linking ties a Telegram user to a platform account
- `TELEGRAM_WEBAPP_URL` controls the allowed Mini App origin
### Rate limits on auth endpoints
- Login: 10 requests per 15-minute window (`LOGIN_RATE_LIMIT_ENABLED` to toggle)
- Cloudflare Turnstile CAPTCHA support: `TURNSTILE_SECRET_KEY`
---
## 8. Realtime (Socket.IO)
- Socket.IO server is attached to the HTTP server at bootstrap (`infrastructure/socket/socketService.ts`)
- Redis adapter (`@socket.io/redis-adapter`) enables pub-sub across Node cluster workers
- **Room conventions:**
- `user:<userId>` — personal notifications, payment status updates
- `payment:<paymentId>` — scoped payment lifecycle events (added in v2.8.4 to prevent global cart-wipe)
- `dispute:<disputeId>` — dispute chat and status
- `chat:<conversationId>` — chat messages
- **Key emitted events:** `payment:update`, `notification:new`, `dispute:update`, `chat:message`, `offer:update`
- Server verifies JWT on `connection` and room join; frontend must join the correct room after authenticating
---
## 9. Payment Providers
| Provider | Type | Chains / Tokens | Notes |
|---|---|---|---|
| **amn.scanner** | In-house on-chain scanner | ETH, BSC (USDT/USDC) | Bearer auth via `AMN_SCANNER_API_KEY`; webhook secret `AMN_SCANNER_WEBHOOK_SECRET`; provider tag `"amn.scanner"` |
| **Request Network** | Decentralized invoicing | ETH, Base (USDC/DAI) | `REQUEST_NETWORK_*` env block; HMAC webhook signature; canonical proxy addresses differ per chain (ETH `0x370DE2…`, Base `0x189219…`) |
| **SHKeeper** | Self-hosted crypto gateway | BTC, ETH, BNB, USDT, others | `SHKEEPER_NETWORK`, `SHKEEPER_NETWORKS`, `SHKEEPER_ALLOWED_TOKENS` |
| **DePay** | Web3 payment widget | EVM chains | Legacy path; `PAYMENT_CALLBACK_SECRET` |
| **Derived Destinations** | HD-wallet receive addresses | ETH / BSC | `DERIVED_DESTINATION_XPUB/XPRIV`; sweep orchestration runs on configurable interval |
### Payment orchestration
- `PAYMENT_PROVIDER_MODE` selects active provider(s) at runtime
- Internal ledger tracks `available`, `held`, and `releasable` balances per payment record
- Transaction Safety Provider: AML screening (Chainalysis / OFAC SDN), minimum on-chain confirmation thresholds configurable at runtime (`TRANSACTION_SAFETY_MIN_CONFIRMATIONS`, `TRANSACTION_SAFETY_AML_PROVIDER`)
- `GET /api/payment/:id` is exempt from the payment rate limiter (polling-safe)
- Cleanup endpoints must always be scoped by `provider:` to avoid wiping unrelated payment records
### Price Oracle
- Chainlink + OffchainFX feeds; `ORACLE_MAX_STALENESS_S` sets maximum acceptable quote age
- Depeg protection rejects or flags stablecoin payments when peg deviation exceeds threshold
- `ORACLE_BYPASS_ENABLED=true` disables staleness check (dev/test only)
---
## 10. CI/CD (Woodpecker)
Four Woodpecker pipeline files under `.woodpecker/`:
| File | Trigger | Purpose |
|---|---|---|
| `production.yml` | push to `main` / `master` | Typecheck → build Docker image locally on host → `docker compose up -d backend` |
| `development.yml` | cron (parked) | Was the dev-stack auto-deploy; currently inactive |
| `manual.yml` | manual trigger | Builds image to `git.tbs.amn.gg` registry (escrow-dev stack ignores registry pulls) |
| `cleanup.yml` | scheduled | Housekeeping tasks (prune old images, stale data) |
### Production pipeline steps
1. **get-version** — reads `package.json` version, writes `dev-<version>` to `.tags`
2. **typecheck**`npm ci` (cached at `/opt/woodpecker-cache/backend-npm`) then `npm run typecheck`; push is blocked if tsc errors exist
3. **build-and-deploy**`docker build -f Dockerfile.prod -t escrow-backend-local:dev .` on the agent co-located with the stack; then `docker compose up -d --no-deps --pull never backend`
4. **notify**`node scripts/ci/tg-notify.cjs` posts success/failure to Telegram (no `parse_mode` to avoid HTML/Markdown breakage)
### escrow-multi stack
The `escrow-multi` stack (branch `feature/white-label-shops`) uses `.woodpecker/multi.yml`. Always deploy via `git push forgejo feature/white-label-shops` — never via manual SSH or rsync. Woodpecker CLI credentials are in `~/CascadeProjects/escrow/.env`.
### Version bump requirement
Bump `package.json` version before every CI-triggering push, or the deployed image will not be distinguishable from the previous build. See memory note `version_bump_before_ci.md`.
---
## 11. Local Development Quick-Start
```bash
# 1. Clone
git clone git@git.tbs.amn.gg:escrow/backend.git
cd backend
# 2. Install dependencies
npm install
# 3. Copy and populate env
cp .env.example .env.development
# Edit .env.development — minimum required: PG_URL, REDIS_URI, JWT_SECRET, FRONTEND_URL
# 4. Start Postgres and Redis (Docker)
docker compose up -d postgres redis
# 5. Run migrations
npx drizzle-kit migrate
# 6. Start dev server (seeds run automatically if SEED_USERS=true)
npm run dev
# Server starts on process.env.PORT
# 7. Type-check only (no run)
npm run typecheck
```
> The pre-push git hook runs a full `tsc` check. If a parallel agent's mid-refactor tree is checked out, this hook may block your push. Stage only your specific files — never `git add -A` blindly. See memory note `backend_prepush_tsc_hook.md`.
---
## 12. Environment Variables
| Variable | Description |
|---|---|
| `PORT` | HTTP listen port |
| `NODE_ENV` | `development` / `production` / `test` |
| `FRONTEND_URL` | Allowed CORS origin (frontend base URL) |
| `BACKEND_URL` | Self-referential base URL (used for webhook callback construction) |
| `PG_URL` | Primary Postgres connection string |
| `PG_VITAL_URL` | Postgres connection for vital (write-path) pool |
| `PG_NONVITAL_URL` | Postgres connection for non-vital (read-path) pool |
| `PG_POOL_MAX` | Max connections in primary pool |
| `PG_POOL_SIZE` | Pool size alias |
| `PG_NONVITAL_POOL_MAX` | Max connections in non-vital pool |
| `DATABASE_URL` / `POSTGRES_URL` | Compatibility aliases for `PG_URL` |
| `REDIS_URI` | Redis connection string (sessions, pub-sub, Socket.IO adapter) |
| `JWT_SECRET` | HMAC secret for JWT signing |
| `JWT_EXPIRES_IN` | Access token TTL (e.g. `15m`) |
| `REFRESH_TOKEN_EXPIRES_IN` | Refresh token TTL (e.g. `7d`) |
| `GOOGLE_CLIENT_ID` | Google OAuth 2.0 client ID |
| `TELEGRAM_WEBAPP_URL` | Allowed Telegram Mini App origin |
| `TG_NOTIFY_BOT_TOKEN` | Telegram bot token for CI/admin notifications |
| `TG_NOTIFY_CHATS` | Comma-separated Telegram chat IDs for notifications |
| `SMTP_HOST` | SMTP server hostname |
| `SMTP_PORT` | SMTP port |
| `SMTP_SECURE` | `true` for TLS |
| `SMTP_USER` | SMTP auth username |
| `SMTP_PASS` | SMTP auth password |
| `SMTP_FROM` | From address for outbound email |
| `RESEND_API_KEY` | Resend email API key |
| `RESEND_WEBHOOK_SECRET` | Resend webhook signature secret |
| `PAYMENT_PROVIDER_MODE` | Active payment provider(s) |
| `PAYMENT_CALLBACK_SECRET` | DePay callback HMAC secret |
| `AMN_SCANNER_URL` | amn.scanner service base URL |
| `AMN_SCANNER_API_KEY` | Bearer token for amn.scanner API |
| `AMN_SCANNER_WEBHOOK_SECRET` | HMAC secret for amn.scanner webhook verification |
| `REQUEST_NETWORK_API_BASE_URL` | Request Network API base URL |
| `REQUEST_NETWORK_API_KEY` | Request Network API key |
| `REQUEST_NETWORK_CLIENT_ID` | RN client identifier |
| `REQUEST_NETWORK_NETWORK` | RN chain name (`mainnet` / `sepolia` / etc.) |
| `REQUEST_NETWORK_RECEIVER_ADDRESS` | Merchant wallet for RN payments |
| `REQUEST_NETWORK_PAYMENT_CURRENCY` | Payment token symbol |
| `REQUEST_NETWORK_PAYMENT_TOKEN_ADDRESS` | Payment token contract address |
| `REQUEST_NETWORK_INVOICE_CURRENCY` | Invoice denomination currency |
| `REQUEST_NETWORK_WEBHOOK_CALLBACK_URL` | RN webhook delivery URL |
| `REQUEST_NETWORK_WEBHOOK_SECRET` | HMAC secret for RN webhook |
| `REQUEST_NETWORK_MERCHANT_REFERENCE` | RN merchant reference string |
| `REQUEST_NETWORK_ORIGIN` | RN request origin header |
| `RN_API_KEY` | Alias for `REQUEST_NETWORK_API_KEY` |
| `RN_API_URL` | Alias for `REQUEST_NETWORK_API_BASE_URL` |
| `RN_CLIENT_ID` | Alias for `REQUEST_NETWORK_CLIENT_ID` |
| `RN_WEBHOOK_SECRET` | Alias for `REQUEST_NETWORK_WEBHOOK_SECRET` |
| `SHKEEPER_NETWORK` | SHKeeper primary network identifier |
| `SHKEEPER_NETWORKS` | SHKeeper supported networks (comma-separated) |
| `SHKEEPER_ALLOWED_TOKENS` | Token allowlist for SHKeeper |
| `DERIVED_DESTINATION_XPUB` | HD wallet extended public key for address derivation |
| `DERIVED_DESTINATION_XPRIV` | HD wallet extended private key (for sweep signing) |
| `DERIVED_DESTINATION_BASE_PATH` | BIP-44 derivation base path |
| `DERIVED_DESTINATION_CHAIN_ID` | EVM chain ID for derived address sweeps |
| `DERIVED_DESTINATION_MIN_SWEEP_AMOUNT` | Minimum balance to trigger a sweep |
| `DERIVED_DESTINATION_SWEEP_INTERVAL_MS` | Sweep polling interval in milliseconds |
| `DERIVED_DESTINATION_SWEEP_BALANCE_CONCURRENCY` | Parallel balance-check concurrency |
| `DERIVED_DESTINATION_SWEEP_SIGNER` | Sweep transaction signing mode |
| `DERIVED_DESTINATION_SWEEP_AUTOSTART` | Auto-start sweep cron on boot |
| `SWEEP_MASTER_PRIVKEY` | Master private key for sweep gas top-up |
| `SWEEP_GAS_MIN_BNB` | Minimum BNB balance before gas top-up is triggered |
| `SWEEP_GAS_TOP_UP_BNB` | Amount of BNB to top up for sweep gas |
| `ESCROW_WALLET_ADDRESS` | Platform escrow wallet address |
| `RECEIVER_WALLET_ADDRESS` | Platform receiver wallet address |
| `INFURA_KEY` | Infura RPC key (ETH mainnet) |
| `BSC_RPC_URL` | BSC mainnet RPC endpoint |
| `BSC_TESTNET_RPC_URL` | BSC testnet RPC endpoint |
| `BNB_TESTNET_RPC_URL` | BNB testnet RPC endpoint |
| `RPC_URL_CHAIN_56` | BSC mainnet RPC (chain ID 56) |
| `RPC_URL_CHAIN_97` | BSC testnet RPC (chain ID 97) |
| `ENABLE_TESTNET_CHAINS` | Enable testnet chain support |
| `TRANSACTION_SAFETY_AML_PROVIDER` | AML provider: `chainalysis` / `ofac` / `none` |
| `TRANSACTION_SAFETY_MIN_CONFIRMATIONS` | Default minimum on-chain confirmations |
| `CHAINALYSIS_API_KEY` | Chainalysis KYT API key |
| `OFAC_SDN_URL` | OFAC SDN list endpoint |
| `AML_CHECK_COST_USD` | Cost per AML check (for billing/reporting) |
| `ORACLE_MAX_STALENESS_S` | Maximum age (seconds) for oracle price quotes |
| `ORACLE_BYPASS_ENABLED` | Disable oracle staleness check (`true` in dev/test only) |
| `DIGITAL_GOODS_ENC_KEY` | AES encryption key for digital goods delivery |
| `TREZOR_SAFEKEEPING_REQUIRED` | Require Trezor safekeeping confirmation |
| `TURNSTILE_SECRET_KEY` | Cloudflare Turnstile CAPTCHA secret |
| `RATE_LIMIT_WINDOW_MS` | Rate limit window in milliseconds |
| `RATE_LIMIT_MAX_REQUESTS` | Max requests per window (global limiter) |
| `RATE_LIMIT_BYPASS_IPS` | Comma-separated IPs exempt from rate limiting |
| `LOGIN_RATE_LIMIT_ENABLED` | Enable/disable login rate limiter |
| `TRUST_PROXY_HOPS` | `trust proxy` hop count for X-Forwarded-For behind Traefik |
| `UPLOAD_PATH` | Filesystem path for uploaded files (default `/app/uploads`) |
| `MAX_FILE_SIZE` | Maximum upload size in bytes |
| `CLUSTER_WORKERS` | Number of Node cluster worker processes |
| `SEED_USERS` | Seed default dev users on start |
| `AUTO_SEED_ON_START` | Auto-run all seeds on process start |
| `SEED_DIGITAL_GOODS_ON_START` | Seed digital goods fixtures on start |
| `SEED_MOCK_SHOPS_ON_START` | Seed mock shop fixtures on start |
| `FORCE_SEED_TEMPLATES` | Force re-seed request templates even if already present |
| `SEED_PASSWORD_SELLER` | Password for seeded seller account |
| `SEED_PASSWORD_MOCK_SELLER` | Password for seeded mock seller account |
| `SEED_PASSWORD_SUPPORT` | Password for seeded support account |
| `ADMIN_EMAIL` | Seeded admin user email |
| `ADMIN_PASSWORD` | Seeded admin user password |
| `ADMIN_FIRST_NAME` | Seeded admin first name |
| `ADMIN_LAST_NAME` | Seeded admin last name |
| `MIGRATION_MONGO_URL` | Mongo URL used only by migration/backfill tooling (not runtime) |
| `MIGRATION_PG_URL` | Postgres URL used by migration tooling (may differ from `PG_URL`) |
| `MONGODB_URI` | Legacy Mongo URI retained for backfill scripts only |
| `DB_NAME` | Database name (legacy config field) |
> Store-mode env vars (`AUTH_STORE`, `USER_STORE`, `BLOG_STORE`, etc.) were part of the dual-write migration scaffolding. All domains are now Postgres-only; these can be left unset or set to `postgres`.
---
## 13. Known Issues / Open Items
| Issue | Status | Reference |
|---|---|---|
| Rate limit counters are in-memory | Not multi-process safe across cluster workers; Redis adapter planned | `backend_rate_limits.md` |
| `pgId` vs legacy `_id` mismatch | Auth `_id` is a legacy ObjectId; marketplace FKs use Postgres UUID (`pgId`); match offers on `pgId` | `pgid_vs_legacy_id.md` |
| Socket.IO room scoping for payments | Backend room-scoping for payment events is an open follow-up (frontend gate added in v2.8.4) | `cart_wipe_global_socket_events.md` |
| Performance is WAN-bound | Profiling shows 300800ms on external routes = WAN RTT (~235ms); server-side is 312ms; PG migration does not fix this | `perf_is_network_bound_not_db.md` |
| RN webhook `event` field | Request Network sends discriminator as `payload.event` not `eventType`; parser must include `event` in fallback chain | `rn_webhook_event_field.md` |
| RN canonical proxy addresses per chain | ETH `0x370DE2…`, Base `0x189219…` — not the same CREATE2 address; always probe before using hardcoded addresses | `rn_proxy_addresses_per_chain.md` |
| JSON assets not copied to dist | `tsc` does not copy `.json` files; any `fs.readFileSync` on JSON needs explicit `postbuild` copy step | `feedback_json_assets_copy_to_dist.md` |
| Woodpecker `${VAR}` template collision | Woodpecker eats `${VAR}` in commands; use `$VAR` or `$$VAR` | `woodpecker_template_collision.md` |
| CI silent build fail | Green CI does not guarantee image was pushed to registry; verify `dev-<version>` tag exists before trusting | `woodpecker_silent_build_fail.md` |
| Admin cleanup must be provider-scoped | Any payment cleanup query must filter by `provider:` or it silently destroys multi-seller/RN records | `feedback_payment_cleanup_provider_filter.md` |
| Store-mode env vars | Legacy dual-write `*_STORE` vars still present in codebase but are no-ops; can be pruned in a future cleanup | — |
| Mongo backfill tooling | `MIGRATION_MONGO_URL` / `MONGODB_URI` retained for backfill scripts only; server never connects to Mongo at runtime | `mongo_retirement_status.md` |