Files
nick-doc/10 - Services/backend.md
Siavash Sameni 67244223ec docs: add sub-project service docs + sync vault 2026-06-08
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.
2026-06-08 16:23:00 +04:00

24 KiB
Raw Blame History

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)
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)

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.)

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/ (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)

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

  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)

amn.scanner specifics

  • 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

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.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.

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