Add 10 - Services/ docs for all sub-projects: backend, frontend, scanner, deployment (new), update amanat-assist. Update Scanner Architecture, Telegram Mini App flow, and Activity Log. Add payment safety edge cases.
24 KiB
Backend Service — amn-backend
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.
| Field | Value |
|---|---|
| Current version | 2.10.5 |
| Status | Active — production at dev.amn.gg |
| 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).
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) |
| 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) |
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 (0000–0018)
│ └── 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.)
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 |
|---|---|
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 |
5. API Surface Summary
All routes are mounted under /api/*. See 03 - API Reference/API Overview for the full endpoint reference.
Key route groups:
| 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 |
6. Database
PostgreSQL (primary — active)
- ORM: Drizzle ORM (
drizzle-orm ^0.45.2) - Driver:
pg ^8.21.0 - Migrations: 19 SQL files under
src/db/migrations/(0000–0018), managed bydrizzle-kit - Schemas: per-table files in
src/db/schema/, exported viaindex.tsbarrel - Repositories:
src/db/repositories/— one Drizzle repo per domain;factory.tsprovides DI - Connection:
PG_URLenv var (postgres://user:pass@host:5432/db) - Migrations run:
npx drizzle-kit migrate(or viadrizzle.config.ts)
MongoDB (legacy — migration in progress)
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:
- 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.
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 |
RBAC roles: admin, buyer, seller, 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.
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.
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 |
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 |
|---|---|---|---|
| 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 |
Payment flow
- Buyer creates intent (
POST /api/payment) → provider adapter creates invoice / watch address - Provider webhook arrives → HMAC-verified → reconciliation service updates ledger
- Escrow holds funds → seller fulfills → admin/resolver releases or refunds
- Ledger enforces: held → releasable → released (no double-spend)
amn.scanner specifics
- Webhook endpoint:
POST /api/amn-scanner/webhook - HMAC verification via
AMN_SCANNER_WEBHOOK_SECRET - Discriminator field:
payload.event(noteventType) — 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), orchainalysis
Price oracle / depeg protection
- Providers: Chainlink + off-chain FX (
OFFCHAIN_FX_URL) - Chains: ETH (RPC via
CHAINLINK_RPC_1), BSC (viaCHAINLINK_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
10. CI/CD (Woodpecker)
Four pipelines in backend/.woodpecker/:
production.yml — primary deploy pipeline
Trigger: push to main/master · Platform: linux/arm64
| 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) |
No registry push on production pipeline — agent is co-located with the stack; pushing large images over Tailscale times out.
development.yml — parked
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.
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.jsonversion 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.gghas thedev-<version>tag before trusting the deploy. - Woodpecker eats
${VAR}in commands — use$VARor$$VAR; prefer plugins over raw curl for notifications.
11. Local Development Quick-Start
# Clone and install
git clone git@git.tbs.amn.gg:escrow/backend.git
cd backend
npm install
# Copy environment file
cp .env.example .env.local
# Edit .env.local — set PG_URL, REDIS_URI, JWT_SECRET at minimum
# Start dependencies (Postgres + Redis)
docker compose -f docker-compose.local.yml up -d
# Run DB migrations
npx drizzle-kit migrate
# Start dev server (hot-reload)
npm run dev
# → listens on http://localhost:5001
# 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):
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:
npm test
Test files live in __tests__/.
12. Environment Variables
| 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 |
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 |
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 |
13. Known Issues / Open Items
| Issue | Status | Notes |
|---|---|---|
| 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 |