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>
This commit is contained in:
Siavash Sameni
2026-06-12 11:42:18 +04:00
parent 18073afb52
commit e52ffce48a
18 changed files with 2619 additions and 1102 deletions

View File

@@ -2,375 +2,293 @@
## 1. Overview
**amn-backend** is the Express 5 / TypeScript API server powering the Amanat escrow marketplace (`dev.amn.gg`). It handles all buyer-seller escrow workflow logic, crypto payment processing across multiple chains and providers, real-time socket events, authentication, admin tooling, and the in-progress Mongo→PostgreSQL migration.
`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.10.5** |
| Status | Activeproduction at `dev.amn.gg` |
| Current version | **2.11.43** |
| Status | Productionreceiving 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` |
| Runtime port | 8083 (production Docker), 8080 (dev Docker), 5001 (dev default) |
| Database | PostgreSQL (Drizzle ORM) — sole persistence layer as of v2.9.12 |
| Node version | 22 (`.nvmrc`) |
PostgreSQL is the sole active database. MongoDB references remain in some env-var config for the dual-write seam during migration, but no Mongo-backed stores remain active in normal operation (all 11 repository domains use Drizzle repos).
| Dev stack host | `root@89.58.32.32` — Arcane project `escrow-dev` |
---
## 2. Tech Stack
| Layer | Technology |
|---|---|
| Framework | Express 5 (TypeScript) |
| Runtime | Node.js 22 |
| Primary DB | PostgreSQL via Drizzle ORM (`drizzle-orm ^0.45.2`, `pg ^8.21.0`) |
| Migrations | Drizzle Kit (`drizzle-kit ^0.31.1`) — 19 landed SQL migrations |
| Session / Cache | Redis (`ioredis`) with Socket.IO pub-sub adapter |
| Realtime | Socket.IO with Redis adapter (seller/buyer rooms) |
| Auth | JWT (`jsonwebtoken`), Google OAuth, WebAuthn passkeys (`@simplewebauthn/server`), Telegram Mini App initData |
| Crypto payments | Request Network, amn.scanner (in-house), DePay, SHKeeper |
| Rate limiting | In-memory (express-rate-limit) — Redis adapter planned |
| AI integration | OpenAI (listing descriptions, moderation) |
| Email | Nodemailer via Resend SMTP |
| Telegram | Bot webhook + Mini App session + identity linking |
| Security | Helmet, CORS, Cloudflare Turnstile CAPTCHA, HMAC webhook verification |
| Containerization | Docker (Dockerfile.prod, Dockerfile.dev) |
| CI/CD | Woodpecker CI (4 pipelines) |
| 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, graceful shutdown
├── cluster.ts # Node.js cluster mode entry point (multi-core)
├── controllers/ # HTTP request handlers — thin layer, delegate to services
├── db/ # Drizzle/Postgres layer
│ ├── schema/ # Per-table Drizzle schema files + index.ts barrel
│ ├── migrations/ # 19 numbered SQL migration files (00000018)
└── repositories/ # Drizzle repos, factory.ts, backfill scripts, verify utilities
├── infrastructure/
── socket/ # Socket.IO server init, room helpers, emit wrappers
├── models/ # Removed — replaced by Drizzle schemas in db/schema/
├── routes/ # Express Router definitions (mounted in app.ts)
│ ├── amnScannerWebhookRoutes.ts
│ ├── blogRoutes.ts
│ ├── disputeRoutes.ts
── pointsRoutes.ts
├── scripts/ # CLI utilities (seed:users, seed:categories, backfill, etc.)
├── seeds/ # Seed data fixtures (Postgres-capable, store-aware, idempotent)
├── services/ # Feature domain services (self-contained per domain)
│ ├── address/ # Address management
├── admin/ # Admin-only operations, AML config, break-glass, data cleanup
── ai/ # OpenAI integration (descriptions, moderation)
│ ├── auth/ # JWT, OAuth, Passkey, Telegram, password reset
├── blockchain/ # Web3 read/verify helpers
│ ├── blog/ # Posts, categories, comments
│ ├── chat/ # Conversations, messages, attachments
│ ├── config/ # Runtime config service
│ ├── delivery/ # Delivery tracking
│ ├── dispute/ # Dispute lifecycle, evidence, mediator assignment
│ ├── email/ # Nodemailer transport + templates
│ ├── file/ # Multer uploads, MIME validation
│ ├── health/ # Health check endpoint logic
│ ├── marketplace/ # PurchaseRequest, SellerOffer, Template, Shop
│ ├── notification/ # Templates, delivery, mark-as-read
│ ├── payment/ # Payment orchestration + provider adapters + ledger
│ │ ├── adapters/ # Provider-neutral adapter interface + registry
│ │ ├── amnScanner/ # amn.scanner in-house pay-in detection
│ │ ├── ledger/ # Internal funds ledger (available / held / releasable)
│ │ ├── migration/ # Legacy data backfill utilities
│ │ ├── observability/ # Logging and incident controls
│ │ ├── orchestration/ # High-level payment flow coordination
│ │ ├── priceOracle/ # Chainlink + off-chain FX oracle, depeg protection
│ │ ├── reconciliation/ # Webhook + status reconciliation per provider
│ │ ├── request-network/# Request Network routes and webhook signature
│ │ ├── requestNetwork/ # Request Network service logic
│ │ ├── safety/ # Transaction Safety Provider + confirmation thresholds
│ │ ├── tokens/ # On-chain token registry / decimals lookup
│ │ └── wallets/ # Derived destination wallets + sweep orchestration
│ ├── points/ # Loyalty points, levels, redemption
│ ├── redis/ # Redis client, cache helpers, pub-sub
│ ├── telegram/ # Bot webhook, Mini App session, identity linking, notifications
│ ├── trezor/ # Trezor hardware-wallet signing for admin approvals
│ └── user/ # Profile, preferences, addresses
├── shared/
│ ├── config/index.ts # Centralised typed env-var loader
│ ├── middleware/ # authMiddleware, errorHandler, roleGuard, validators
│ ├── types/ # Cross-cutting TypeScript types
│ └── utils/response-handler.ts # Standard success/error response envelope
└── utils/ # Pure utilities (logger, currencyUtils, etc.)
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
```
Each service folder is self-contained: `<feature>Service.ts`, `<feature>Controller.ts`, `<feature>Routes.ts`, `<feature>Validation.ts`. This design allows future extraction to microservices with minimal coupling.
---
## 4. Key Services / Modules
| Module | Description |
| Service path | Description |
|---|---|
| `services/auth/` | JWT issuance/refresh, Google OAuth, WebAuthn passkeys, Telegram initData verification, password reset |
| `services/marketplace/` | Core escrow domain: PurchaseRequest, SellerOffer, Template, Shop lifecycle |
| `services/payment/` | Payment orchestration, provider adapters, internal ledger, reconciliation |
| `services/payment/ledger/` | Double-spend guard: tracks available / held / releasable balances per payment |
| `services/payment/wallets/` | Derived destination address derivation (xpub) + sweep orchestration |
| `services/payment/priceOracle/` | Chainlink + off-chain FX oracle for multi-currency pricing + stablecoin depeg protection |
| `services/payment/safety/` | Transaction Safety Provider: confirmation thresholds, tx hash and transfer match enforcement |
| `services/payment/amnScanner/` | In-house blockchain scanner webhook adapter (replaces Request Network for pay-in detection) |
| `services/payment/requestNetwork/` | Request Network pay-in routes, webhook signature verification, invoice creation |
| `infrastructure/socket/` | Socket.IO server init, buyer/seller room management, emit helpers |
| `services/redis/` | Redis client wrapper, pub-sub channel helpers, session cache |
| `services/chat/` | Conversations and message threading between buyer and seller |
| `services/dispute/` | Dispute lifecycle: open, evidence, mediator, resolution |
| `services/admin/` | Admin RBAC operations: AML config, break-glass, dispute management, data cleanup |
| `services/telegram/` | Bot webhook handler, Mini App session auth, Telegram identity linking, push notifications |
| `services/trezor/` | Trezor hardware-wallet approval gate for high-value admin actions (break-glass overrideable) |
| `services/notification/` | In-app notification templates, delivery, mark-as-read |
| `services/ai/` | OpenAI integration: AI-assisted listing descriptions and content moderation |
| `services/email/` | Nodemailer transport via Resend SMTP, HTML email templates |
| `services/points/` | Loyalty points engine, tier levels, redemption |
| `services/blog/` | Blog posts, categories, comments |
| `services/file/` | Multer-based file upload handler, MIME validation, upload path management |
| `services/blockchain/` | Low-level Web3 read helpers: balance checks, tx confirmation polling |
| `db/repositories/` | Drizzle ORM repository layer for all 11 domain entities |
| `seeds/` | Idempotent Postgres seed fixtures for users, categories, shops, configs |
| `scripts/` | CLI backfill, migration verify, seeding, and maintenance scripts |
| `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 routes are mounted under `/api/*`. See [[03 - API Reference/API Overview]] for the full endpoint reference.
All API routes are mounted under `/api/`. The table below lists top-level route groups.
Key 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 |
| Prefix | Domain |
|---|---|
| `/api/auth/*` | Registration, login, OAuth, passkeys, Telegram auth |
| `/api/payment/*` | Payment CRUD, status polling, provider webhooks |
| `/api/payment/request-network/*` | Request Network webhook + invoice endpoints |
| `/api/amn-scanner/*` | amn.scanner webhook receiver |
| `/api/marketplace/*` | Purchase requests, seller offers, templates |
| `/api/chat/*` | Conversations, messages, attachments |
| `/api/dispute/*` | Dispute lifecycle |
| `/api/admin/*` | Admin operations (role-gated) |
| `/api/notification/*` | In-app notifications |
| `/api/blog/*` | Blog posts and comments |
| `/api/points/*` | Loyalty points |
| `/api/user/*` | User profile, preferences |
| `/health` | Docker healthcheck + active store mode listing |
**Rate limits (active):**
| Scope | Limit |
|---|---|
| Auth endpoints | 10 req / 15 min |
| Payment endpoints | 30 req / 15 min |
| AI endpoints | 20 req / 15 min |
| Global | 100 req / 15 min |
| `GET /api/payment/:id` | Exempt (polling route) |
| RN + Telegram webhooks | Exempt from global limiter |
Full per-endpoint details: [[03 - API Reference/API Overview]]
---
## 6. Database
### PostgreSQL (primary — active)
### PostgreSQL (primary)
- **ORM:** Drizzle ORM (`drizzle-orm ^0.45.2`)
- **Driver:** `pg ^8.21.0`
- **Migrations:** 19 SQL files under `src/db/migrations/` (00000018), managed by `drizzle-kit`
- **Schemas:** per-table files in `src/db/schema/`, exported via `index.ts` barrel
- **Repositories:** `src/db/repositories/` — one Drizzle repo per domain; `factory.ts` provides DI
- **Connection:** `PG_URL` env var (`postgres://user:pass@host:5432/db`)
- **Migrations run:** `npx drizzle-kit migrate` (or via `drizzle.config.ts`)
- 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 (legacy — migration in progress)
### MongoDB (retired)
MongoDB and Mongoose were removed at the code level as of v2.9.12. The `MONGO_CONNECT_MODE` env var and `*_STORE` vars remain for the dual-write seam but all active domain stores use Drizzle exclusively. Remaining migration work:
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.
- Backfill execution for remaining legacy records
- Per-domain read cutover verification
- Chat domain normalization (current blocker)
- Full runtime coupling severance
See [[PRD - Mongo Retirement (Full Nuke).md]] and [[MIGRATION_TODO.md]] for status.
The `DATABASE_URL` / `POSTGRES_URL` aliases are accepted for compatibility; prefer `PG_URL`.
---
## 7. Auth Model
| Method | Mechanism |
|---|---|
| Password | bcrypt hashed, JWT access + refresh token pair |
| Google OAuth | OAuth 2.0 code flow via `google-auth-library` |
| WebAuthn / Passkeys | `@simplewebauthn/server` — RP ID: `dev.amn.gg` |
| Telegram Mini App | initData HMAC verification (bot token), replay window: 120 s, TTL: 24 h |
| Telegram Bot | Webhook secret token header verification |
| Sessions | Stateless JWT; refresh token stored in Redis |
| CAPTCHA | Cloudflare Turnstile, triggered after 3 failed login attempts from same IP |
### JWT
**RBAC roles:** `admin`, `buyer`, `seller`, `resolver`, `guard`
- 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')`
`roleGuard(role)` middleware is applied per-route after `authMiddleware`. The admin role unlocks break-glass, AML config, dispute management, and data cleanup endpoints.
### WebAuthn / Passkey
**Trezor safekeeping:** when `TREZOR_SAFEKEEPING_REQUIRED=true`, high-value admin actions (release, refund, payout) require a Trezor-signed approval message. Break-glass overrides this for 1 hour and fires a Telegram alarm.
- 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)
- **Adapter:** Redis pub-sub (`@socket.io/redis-adapter`) — scales across multiple backend instances
- **Init:** `infrastructure/socket/socketService.ts` — attaches to the HTTP server after Express bootstraps
- **Room model:**
- `buyer:<userId>`buyer-facing events (payment status, offer updates, cart)
- `seller:<userId>` — seller-facing events (new requests, offer accepted)
- Admin rooms for dispute/notification broadcasts
- **Auth:** Socket handshake verified with JWT before room join
- **Known issue:** Global payment broadcasts previously wiped all users' carts (fixed in frontend v2.8.4 with a provider gate). Backend room-scoping is an open follow-up item.
Key emitted events (non-exhaustive):
| Event | Direction | Description |
|---|---|---|
| `payment:status` | Server → client | Payment state change (pending → confirmed → released) |
| `offer:new` | Server → seller | New purchase request from buyer |
| `offer:accepted` | Server → buyer | Seller accepted the offer |
| `notification:new` | Server → client | In-app notification delivery |
| `dispute:update` | Server → both | Dispute state change |
| `chat:message` | Server → both | New chat message in conversation |
- 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
The payment layer uses a provider-neutral adapter interface (`services/payment/adapters/`). All providers register in the adapter registry. The ledger (`services/payment/ledger/`) enforces double-spend prevention across all providers.
| Provider | Type | Chains | Status |
| Provider | Type | Chains / Tokens | Notes |
|---|---|---|---|
| **amn.scanner** | In-house blockchain scanner | ETH, BSC, Base, TON | Active — default for new payments when `AMN_SCANNER_DEFAULT=true` |
| **Request Network** | Decentralized payment protocol | BSC (USDC/USDT) + ETH | Active — legacy in-flight payments; webhook-driven |
| **DePay** | Widget-based crypto payments | Multi-chain | Available via adapter |
| **SHKeeper** | Self-hosted crypto gateway | Bitcoin + EVM | Available via adapter |
| **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 flow
### Payment orchestration
1. Buyer creates intent (`POST /api/payment`) → provider adapter creates invoice / watch address
2. Provider webhook arrives → HMAC-verified → reconciliation service updates ledger
3. Escrow holds funds → seller fulfills → admin/resolver releases or refunds
4. Ledger enforces: held → releasable → released (no double-spend)
- `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
### amn.scanner specifics
### Price Oracle
- Webhook endpoint: `POST /api/amn-scanner/webhook`
- HMAC verification via `AMN_SCANNER_WEBHOOK_SECRET`
- Discriminator field: `payload.event` (not `eventType`) — always check this field
- Provider scoped by `provider: "amn.scanner"` in payment records
- Read token decimals on-chain, not from registry
### Request Network specifics
- Webhook endpoint: `POST /api/payment/request-network/webhook`
- Webhook secret: `REQUEST_NETWORK_WEBHOOK_SECRET`
- Network: BSC mainnet, currency: USDC
- Canonical proxy addresses differ per chain (ETH: `0x370DE2…`, Base: `0x189219…`) — probe before trusting
### Safety layer
- `TRANSACTION_SAFETY_MIN_CONFIRMATIONS=12` (default)
- Requires tx hash match and on-chain transfer match before releasing funds
- AML screening: `none` (default), `ofac` (OFAC SDN list, local, free), or `chainalysis`
### Price oracle / depeg protection
- Providers: Chainlink + off-chain FX (`OFFCHAIN_FX_URL`)
- Chains: ETH (RPC via `CHAINLINK_RPC_1`), BSC (via `CHAINLINK_RPC_56`)
- Depeg hard cap: `DEPEG_HARD_CAP_BPS` (default 500 bps = 5%)
- Oracle max staleness: `ORACLE_MAX_STALENESS_S=120`
- Currently disabled (`ORACLE_QUOTING_ENABLED=false`) — enable after FX feeds are configured
- 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 pipelines in `backend/.woodpecker/`:
Four Woodpecker pipeline files under `.woodpecker/`:
### `production.yml` — primary deploy pipeline
| 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) |
Trigger: `push` to `main`/`master` · Platform: `linux/arm64`
### Production pipeline steps
| Step | Description |
|---|---|
| `get-version` | Reads `package.json` version, writes `dev-<version>` to `.tags` |
| `typecheck` | `npm ci` + `npm run typecheck` — gates image build on clean TypeScript (cached npm on host) |
| `build-and-deploy` | `docker build -t git.tbs.amn.gg/escrow/backend:dev` locally on the agent, then `docker compose up -d --no-deps --pull never backend` — no registry push, image stays local |
| `notify` | Posts plain-text result to Telegram via `scripts/ci/tg-notify.cjs` (no parse_mode) |
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)
> No registry push on production pipeline — agent is co-located with the stack; pushing large images over Tailscale times out.
### escrow-multi stack
### `development.yml` — parked
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`.
Trigger: `event: cron` (no cron configured — effectively disabled). Targets legacy `git.manko.yoga` registry and retired Arcane deploy. Use `manual.yml` for manual playground builds.
### Version bump requirement
### `manual.yml` — manual build playground
Trigger: manual. Builds and pushes to `git.tbs.amn.gg/escrow/backend`. Used for testing the pipeline independently.
### `cleanup.yml` — image cleanup
Trigger: scheduled/manual. Removes old image tags from the registry.
**Important CI notes:**
- Always bump `package.json` version before pushing a CI-triggering commit — otherwise the build tag doesn't change and the deployed image may be stale.
- CI green does not guarantee the image was pushed — verify `git.tbs.amn.gg` has the `dev-<version>` tag before trusting the deploy.
- Woodpecker eats `${VAR}` in commands — use `$VAR` or `$$VAR`; prefer plugins over raw curl for notifications.
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
# Clone and install
# 1. Clone
git clone git@git.tbs.amn.gg:escrow/backend.git
cd backend
# 2. Install dependencies
npm install
# Copy environment file
cp .env.example .env.local
# Edit .env.local — set PG_URL, REDIS_URI, JWT_SECRET at minimum
# 3. Copy and populate env
cp .env.example .env.development
# Edit .env.development — minimum required: PG_URL, REDIS_URI, JWT_SECRET, FRONTEND_URL
# Start dependencies (Postgres + Redis)
docker compose -f docker-compose.local.yml up -d
# 4. Start Postgres and Redis (Docker)
docker compose up -d postgres redis
# Run DB migrations
# 5. Run migrations
npx drizzle-kit migrate
# Start dev server (hot-reload)
# 6. Start dev server (seeds run automatically if SEED_USERS=true)
npm run dev
# → listens on http://localhost:5001
# Server starts on process.env.PORT
# OR run in dev Docker
docker compose -f docker-compose.dev.yml up
# → listens on http://localhost:8080
# Seed database
npm run seed:users
npm run seed:categories
```
**Typecheck (required before push):**
```bash
# 7. Type-check only (no run)
npm run typecheck
```
A pre-push git hook blocks the push on tsc errors. If a parallel agent's mid-refactor tree has errors, use explicit `git add <path>` — never `git add -A`.
**Run tests:**
```bash
npm test
```
Test files live in `__tests__/`.
> 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`.
---
@@ -378,89 +296,130 @@ Test files live in `__tests__/`.
| Variable | Description |
|---|---|
| `NODE_ENV` | `production` / `development` / `test` |
| `PORT` | HTTP listen port (default 5001) |
| `TRUST_PROXY_HOPS` | Number of reverse-proxy hops in front of app |
| `FRONTEND_URL` | Allowed CORS origin for frontend |
| `BACKEND_URL` | Public backend base URL |
| `PG_URL` | PostgreSQL connection string |
| `POSTGRES_USER` | Postgres username (Docker init) |
| `POSTGRES_PASSWORD` | Postgres password (Docker init) |
| `POSTGRES_DB` | Postgres database name (Docker init) |
| `MONGO_CONNECT_MODE` | `always` / `never` / `optional` — Mongo connection behavior (legacy) |
| `REDIS_URI` | Redis connection URI |
| `JWT_SECRET` | HS256 signing secret for access tokens |
| `JWT_EXPIRES_IN` | Access token TTL (e.g. `7d`) |
| `REFRESH_TOKEN_EXPIRES_IN` | Refresh token TTL (e.g. `30d`) |
| `ADMIN_EMAIL` | Bootstrap admin account email |
| `ADMIN_PASSWORD` | Bootstrap admin account password |
| `SEED_USERS` | `true` to auto-seed users on dev boot |
| `SEED_PASSWORD_ADMIN` | Admin seed account password |
| `SEED_PASSWORD_SUPPORT` | Support seed account password |
| `SEED_PASSWORD_BUYER` | Buyer seed account password |
| `SEED_PASSWORD_SELLER` | Seller seed account password |
| `GOOGLE_CLIENT_ID` | Google OAuth client ID |
| `GOOGLE_CLIENT_SECRET` | Google OAuth client secret |
| `WEBAUTHN_RP_ID` | WebAuthn relying party ID (e.g. `dev.amn.gg`) |
| `WEBAUTHN_RP_NAME` | WebAuthn relying party display name |
| `WEBAUTHN_RP_ORIGIN` | WebAuthn allowed origin |
| `SMTP_HOST` | SMTP server host |
| `SMTP_PORT` | SMTP server port |
| `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 username |
| `SMTP_PASS` | SMTP password |
| `SMTP_FROM` | From address for outgoing email |
| `RESEND_WEBHOOK_SECRET` | Resend inbound webhook signing secret (`whsec_…`) |
| `TURNSTILE_SECRET_KEY` | Cloudflare Turnstile server-side secret (empty = CAPTCHA disabled) |
| `RATE_LIMIT_WINDOW_MS` | Rate limit window in milliseconds |
| `RATE_LIMIT_MAX_REQUESTS` | Max requests per window (global) |
| `MAX_FILE_SIZE` | Upload max file size in bytes |
| `UPLOAD_PATH` | Server-side upload directory |
| `PAYMENT_PROVIDER_MODE` | `live` / `test` |
| `PAYMENT_LEDGER_ENFORCEMENT` | `true` to enforce double-spend ledger guard |
| `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 |
| `REQUEST_NETWORK_ENABLED` | Enable Request Network provider |
| `REQUEST_NETWORK_API_KEY` | Request Network API key |
| `REQUEST_NETWORK_NETWORK` | Target chain (`bsc`, `eth`, etc.) |
| `REQUEST_NETWORK_WEBHOOK_SECRET` | HMAC secret for RN webhook verification |
| `AMN_SCANNER_URL` | amn.scanner service base URL |
| `AMN_SCANNER_WEBHOOK_SECRET` | HMAC secret for scanner webhook verification |
| `AMN_SCANNER_DEFAULT` | `true` to make amn.scanner the default provider |
| `ORACLE_QUOTING_ENABLED` | Enable on-chain oracle pricing + depeg protection |
| `PRICE_ORACLE_PROVIDERS` | Comma-separated oracle providers (`chainlink,offchain_fx`) |
| `ORACLE_MAX_STALENESS_S` | Max oracle data age in seconds |
| `DEPEG_HARD_CAP_BPS` | Stablecoin depeg hard cap in basis points |
| `OFFCHAIN_FX_URL` | Off-chain FX rate source URL (required for IRR/TRY) |
| `CHAINLINK_RPC_1` | Private RPC override for Chainlink on ETH mainnet |
| `CHAINLINK_RPC_56` | Private RPC override for Chainlink on BSC |
| `DERIVED_DESTINATION_XPUB` | xPub for derived payment address derivation |
| `DERIVED_DESTINATION_SWEEP_SIGNER` | Sweep signing mode: `build-only` / `hot-key` / `kms` / `trezor` |
| `DERIVED_DESTINATION_SWEEP_INTERVAL_MS` | Sweep cron interval in ms (0 = disabled) |
| `SWEEP_MASTER_PRIVKEY` | Master sweep wallet private key (gas funder) |
| `TREZOR_SAFEKEEPING_REQUIRED` | `true` to require Trezor approval for admin actions |
| `TRANSACTION_SAFETY_ENABLED` | Enable transaction safety layer |
| `TRANSACTION_SAFETY_MIN_CONFIRMATIONS` | Minimum on-chain confirmations before release |
| `TRANSACTION_SAFETY_AML_PROVIDER` | AML provider: `none` / `ofac` / `chainalysis` |
| `CHAINALYSIS_API_KEY` | Chainalysis API key (when AML provider = chainalysis) |
| `TELEGRAM_BOT_TOKEN` | Telegram bot token |
| `TELEGRAM_WEBHOOK_SECRET_TOKEN` | Telegram webhook secret token header value |
| `TELEGRAM_INITDATA_MAX_AGE_SEC` | Max age for Telegram initData (default 86400 s) |
| `TG_NOTIFY_CHATS` | Comma-separated Telegram chat IDs for CI/admin notifications |
| `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 | Notes |
| Issue | Status | Reference |
|---|---|---|
| Mongo→PG migration incomplete | In progress | Chat normalization is the current blocker; read cutover and backfill exec pending |
| Backend room-scoping for socket events | Open | Frontend provider gate is in place (v2.8.4); backend should scope payment events to `seller:<id>` rooms to prevent cross-user leakage |
| Rate limit counters are in-memory | Open | Not shared across instances; Redis adapter planned for distributed deployments |
| Oracle quoting disabled | Open | `ORACLE_QUOTING_ENABLED=false`; requires FX feed configuration before enabling |
| amn.scanner multi-seller + multi-chain gap | Open | Current scanner watches one chain; multi-seller and multi-chain support not yet verified |
| Woodpecker development.yml parked | Known | Targets legacy registry; needs repointing to `git.tbs.amn.gg` and new Arcane deploy before re-enabling |
| Trezor safekeeping off by default | By design | `TREZOR_SAFEKEEPING_REQUIRED=false`; must be enabled explicitly in production once admin xpub is registered |
| Request Network canonical proxy addresses | Known | RN's CREATE2 canonical-address claim is false for ETH and Base — probe actual address before trusting |
| JSON assets not copied to dist/ | Fixed (requires postbuild) | `tsc` does not copy `.json` files; explicit `postbuild` copy step required for any `fs.readFileSync` on JSON assets |
| Parallel agent push conflicts | Operational | mojtaba agent pushes to same branches; always `git fetch --rebase` before pushing; expect version-bump conflicts |
| 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` |