# 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 (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 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

```bash
# 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):**
```bash
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__/`.

---

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