Files
nick-doc/07 - Development/Environment Variables.md

421 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: Environment Variables
tags: [development]
---
# Environment Variables
Every environment variable read by either repo. Use this as the canonical reference when filling in a new `.env`, debugging missing-config errors, or reviewing a PR that touches config.
Sources scanned:
- Backend: `src/shared/config/index.ts`, `src/app.ts`, every `process.env.*` reference in `src/`, and `.env.sentry.example`.
- Frontend: `.env.development`, `.env.local`, `.env.production`, `.env.sentry.example`, `next.config.ts`, all `process.env.*` references in `src/`.
> [!warning] Many secrets in the checked-in `.env.*` files of the frontend are publicly visible (Alchemy key, WalletConnect ID, Google OAuth client ID, Sentry DSN). Rotate these immediately if the repo leaks. Anything not marked `NEXT_PUBLIC_` is **not** exposed to the browser.
---
## How env is loaded
### Backend
`src/shared/config/index.ts` calls `dotenv.config({ path: '.env.development' })` and then `dotenv.config()` (default `.env`). In Docker dev, `docker-compose.dev.yml` injects `env_file: .env.local` and in production `env_file: .env`. There is **no fallback**: if a required var is missing, the typed access (`process.env.JWT_SECRET!`) yields `undefined` and the service crashes on first use.
### Frontend
Next.js auto-loads `.env`, `.env.local`, `.env.development`, `.env.production` in the standard precedence order. Only variables prefixed `NEXT_PUBLIC_` are exposed to the browser bundle. The production Dockerfile **hard-codes** several `NEXT_PUBLIC_*` values via `ENV` directives at build time so they are baked into the static bundle (see [[Docker Setup#frontend-dockerfile]]).
---
## Database
| Name | Repo | Required | Default | Example | Purpose |
|------|------|----------|---------|---------|---------|
| ~~`MONGODB_URI`~~ | ~~backend~~ | **REMOVED** | — | — | **REMOVED** — MongoDB has been completely removed from the backend (v2.9.12). Do not set this variable. |
| ~~`DB_NAME`~~ | ~~backend~~ | **REMOVED** | — | — | **REMOVED** — Was the Mongo database name; no longer used. |
| `PG_URL` | backend | ✅ **REQUIRED** | — | `postgres://amanat:...@postgres:5432/amanat_dev` | Drizzle runtime DSN. PostgreSQL is the only database layer; this must be set for the backend to start. |
| `MIGRATION_PG_URL` | backend | migration only | — | `postgres://amanat:...@postgres:5432/amanat_dev` | DSN used by backfill/migration scripts. Guarded by non-prod host allowlist. |
PostgreSQL (Drizzle ORM) is the **only** database layer as of v2.9.12. MongoDB and Mongoose have been completely removed. 19 migrations (00000019) have landed covering 32 tables.
The following variables are also **REMOVED** and must not be set: `MONGO_CONNECT_MODE`, `MONGO_URL`, `MONGODB_URI`. Any `.env` file referencing them can have those lines deleted.
---
## Cache / Redis
| Name | Repo | Required | Default | Example | Purpose |
|------|------|----------|---------|---------|---------|
| `REDIS_URI` | backend | ✅ | — | `redis://redis:6379` | Connection string used by `services/redis` |
| `REDIS_PASSWORD` | backend | prod only | — | `super-secret` | Substituted into the prod Redis command line (`--requirepass`) |
In dev, Redis runs without a password. In production the compose entrypoint is `redis-server --requirepass "$REDIS_PASSWORD"` so include `:password@` in `REDIS_URI` accordingly.
---
## Auth / JWT
| Name | Repo | Required | Default | Example | Purpose |
|------|------|----------|---------|---------|---------|
| `JWT_SECRET` | backend | ✅ | — | 64 hex chars | HMAC key for access tokens |
| `JWT_EXPIRES_IN` | backend | ✅ | — | `1h` | Access token lifetime |
| `REFRESH_TOKEN_EXPIRES_IN` | backend | ✅ | — | `30d` | Refresh token lifetime |
| `ADMIN_EMAIL` | backend | optional | `admin@marketplace.com` | — | Email of the initial admin created by `init-admin` |
| `ADMIN_PASSWORD` | backend | optional | `Moji6364` | — | Password for the initial admin |
| `ADMIN_FIRST_NAME` | backend | optional | `Admin` | — | First name for the seeded admin |
| `ADMIN_LAST_NAME` | backend | optional | `User` | — | Last name for the seeded admin |
| `GOOGLE_CLIENT_ID` | backend | optional | — | `...apps.googleusercontent.com` | Verifies Google ID tokens server-side |
> [!warning] Rotate `JWT_SECRET` only during a maintenance window — every active session is invalidated.
---
## Email / SMTP
| Name | Repo | Required | Default | Example | Purpose |
|------|------|----------|---------|---------|---------|
| `SMTP_HOST` | backend | ✅ | — | `smtp.zoho.com` | Outbound mail host |
| `SMTP_PORT` | backend | ✅ | — | `465` | TCP port (numeric) |
| `SMTP_SECURE` | backend | ✅ | — | `true` | `true` for TLS, `false` for STARTTLS |
| `SMTP_USER` | backend | ✅ | — | `no-reply@amn.gg` | SMTP username |
| `SMTP_PASS` | backend | ✅ | — | — | SMTP password (or app password) |
| `SMTP_FROM` | backend | ✅ | — | `"AMN" <no-reply@amn.gg>` | Default `From` header |
---
## Payments — Request Network
Request Network is the current primary payment provider. See [[PRD - Request Network In-House Checkout]], [[Request Network Integration Constraints]], and [[Escrow Flow]].
| Name | Repo | Required | Default | Example | Purpose |
|------|------|----------|---------|---------|---------|
| `REQUEST_NETWORK_ENABLED` | backend | optional | `true` | `true` | Enables `request.network` as an available provider |
| `REQUEST_NETWORK_API_KEY` | backend | ✅ | — | `cli_...` | Request Network API credential |
| `REQUEST_NETWORK_API_BASE_URL` | backend | ✅ | `https://api.request.network` | `https://api.request.network` | Request Network API base URL |
| `REQUEST_NETWORK_ORIGIN` | backend | ✅ | `FRONTEND_URL` | `https://dev.amn.gg` | Origin sent to Request Network API |
| `REQUEST_NETWORK_MERCHANT_REFERENCE` | backend | ✅ | — | `<receiver>@eip155:56#...:<token>` | Encodes receiver, chain, payment reference, and token context |
| `REQUEST_NETWORK_NETWORK` | backend | optional | `bsc` | `bsc` | Default checkout network |
| `REQUEST_NETWORK_PAYMENT_CURRENCY` | backend | optional | `USDT` | `USDC` | Default checkout token symbol |
| `REQUEST_NETWORK_WEBHOOK_CALLBACK_URL` | backend | ✅ | — | `https://dev.amn.gg/api/payment/request-network/webhook` | Provider callback URL |
| `REQUEST_NETWORK_WEBHOOK_SECRET` | backend | ✅ | — | — | HMAC secret for inbound webhook signatures |
| `REQUEST_NETWORK_ALLOW_TEST_WEBHOOKS` | backend | optional | `false` | `false` | Allows explicit Request Network test webhooks only for controlled smoke tests |
| `ESCROW_WALLET_ADDRESS` | backend | ✅ | — | `0xa304…` | Master escrow address used by payments service |
| `RECEIVER_WALLET_ADDRESS` | backend | optional | — | `0x…` | Used by alternative payout flows |
### Historical SHKeeper keys
`SHKEEPER_*` variables may still appear in legacy migration docs or old `.env` files. They are not the current primary checkout path and should not be used for new payment work unless a deliberate legacy-record reconciliation task requires them.
---
## Payments — Provider Selection
| Name | Repo | Required | Default | Example | Purpose |
|------|------|----------|---------|---------|---------|
| `PAYMENT_PROVIDER` | backend | optional | `request.network` | `request.network` | Active provider for new payment intents |
| `PAYMENT_DEFAULT_PROVIDER` | backend | optional | `request.network` | `request.network` | Fallback alias when `PAYMENT_PROVIDER` is unset |
| `PAYMENT_ENABLED_PROVIDERS` | backend | optional | `request.network` | `request.network` | Comma-separated providers allowed at runtime |
| `PAYMENT_ROLLBACK_PROVIDER` | backend | optional | `request.network` | `request.network` | Provider used when selected provider is not enabled |
| `PAYMENT_PROVIDER_MODE` | backend | optional | `live` | `dry-run` | Provider mode: `live`, `dry-run`, or `read-only` |
| `REQUEST_NETWORK_ENABLED` | backend | optional | `false` | `true` | Adds `request.network` to enabled providers when no explicit list is set |
| `PAYMENT_REQUEST_NETWORK_COHORT_PERCENT` | backend | optional | `0` | `10` | Percent of new checkout cohort eligible for Request Network |
| `REQUEST_NETWORK_ALLOW_TEST_WEBHOOKS` | backend | optional | `false` | `false` | Allows `x-request-network-test` webhook bypass only in explicit test mode. Keep false in dev/prod unless running a controlled smoke test. |
| `TRANSACTION_SAFETY_ENABLED` | backend | optional | `true` | `true` | Enables the Transaction Safety Provider gate before Request Network pay-ins are marked completed. |
| `TRANSACTION_SAFETY_REQUIRE_TX_HASH` | backend | optional | `true` | `true` | Blocks completion when provider evidence does not include a transaction hash. |
| `TRANSACTION_SAFETY_REQUIRE_TRANSFER_MATCH` | backend | optional | `true` | `true` | Requires on-chain token/recipient/amount evidence to match the expected payment. |
| `TRANSACTION_SAFETY_MIN_CONFIRMATIONS` | backend | optional | `12` | chain floor | Fallback minimum confirmations for unknown chains. Known chains use built-in acceptance floors unless a higher admin-configured value exists. |
| `RPC_URL_CHAIN_97` | backend | optional | `https://bsc-testnet-rpc.publicnode.com` | `https://bsc-testnet-rpc.publicnode.com` | Overrides the backend verifier RPC for BSC Testnet (`bsc-testnet`, `bnb-testnet`, numeric `97`). |
| `BSC_TESTNET_RPC_URL` / `BNB_TESTNET_RPC_URL` | backend | optional | `RPC_URL_CHAIN_97` fallback | `https://...` | Alternate BSC Testnet RPC override names consumed by the legacy verifier path. |
| `TRANSACTION_SAFETY_AML_PROVIDER` | backend | optional | `none` | `none` | AML/sanctions provider adapter name. Non-`none` values should block until implemented/configured. |
| `PAYMENT_LEDGER_ENFORCEMENT` | backend | optional | `false` | `true` | Enforce ledger gates for release/refund |
| `PAYMENT_RECONCILIATION_ENABLED` | backend | optional | `false` | `true` | Enable scheduled provider reconciliation jobs |
| `TREZOR_SAFEKEEPING_REQUIRED` | backend | optional | `false` | `true` | Optional hardware-signature gate for release/refund confirmation. Only the literal value `true` enforces Trezor proof. |
---
## Payments — AMN Pay Scanner
Backend scanner settings:
| Name | Repo | Required | Default | Example | Purpose |
|------|------|----------|---------|---------|---------|
| `AMN_SCANNER_URL` | backend | required when `amn.scanner` is enabled | — | `http://amn-scanner:8080` | Internal scanner service base URL used by `amnPayAdapter` helpers. |
| `AMN_SCANNER_API_KEY` | backend | prod | — | 64 hex chars | Bearer token sent to scanner when scanner `SCANNER_API_KEY` is configured. |
| `AMN_SCANNER_WEBHOOK_SECRET` | backend | required when `amn.scanner` is enabled | — | 64 hex chars | Shared HMAC key used to verify scanner intent and balance-watch webhooks. |
| `AMN_SCANNER_DEFAULT` | backend | optional | `false` | `true` | Makes AMN scanner the default pay-in provider where provider selection allows it. |
Scanner service settings:
| Name | Repo | Required | Default | Example | Purpose |
|------|------|----------|---------|---------|---------|
| `SCANNER_API_KEY` | scanner | prod | — | 64 hex chars | Bearer token required for all scanner endpoints except `/health`. |
| `BALANCE_WATCH_TICK_SEC` | scanner | optional | `60` | `60` | How often the balance-watch scheduler queries for due watches. |
| `BALANCE_WATCH_BATCH_SIZE` | scanner | optional | `50` | `50` | Max due balance watches processed per scheduler tick. |
| `DB_PATH` | scanner | optional | `./scanner.db` | `/data/scanner.db` | SQLite state path for intents, checkpoints, and balance watches. |
| `CHAINS_JSON_PATH` | scanner | optional | `./supported-chains.json` | `/app/supported-chains.json` | Chain registry path. |
| `TOKENS_JSON_PATH` | scanner | optional | `./tokens.json` | `/app/tokens.json` | Token registry path used for `token`/`tokenSymbol` balance requests. |
| `SCANNER_ENABLED_CHAINS` | scanner | optional | all configured chains | `56,1,97` | Restricts scanner startup to selected chain ids; dev includes chain 97 for BSC Testnet testing. |
| `RPC_BSC` / `RPC_ETH` / `RPC_POLYGON` / `RPC_ARB` / `RPC_BASE` | scanner | optional | chain config | provider URL | EVM RPC overrides used by intent scanners and `balanceOf` reads. |
Direct-address balance checks and watches currently support EVM ERC-20 only. Backend code should use `checkScannerTokenBalance`, `createScannerBalanceWatch`, and `stopScannerBalanceWatch` from `amnPayAdapter.ts`.
---
## Repository Mode Flags (Migration Layer)
> [!warning] These flags are **obsolete** as of v2.9.12. MongoDB and Mongoose have been completely removed. The repository factory returns Drizzle (PostgreSQL) repos exclusively. All domain stores are Postgres-only. The values `mongo`, `dual`, and `DualWrite*` are no longer valid — **only `postgres` is valid**, and it is the hardcoded default. These env vars are ignored at runtime and should not be set.
| Name | Repo | Required | Default | Example | Purpose |
|------|------|----------|---------|---------|---------|
| ~~`REPO_DEFAULT`~~ | ~~backend~~ | **OBSOLETE** | — | — | **REMOVED** — Only `postgres` is valid; the factory always returns Drizzle repos. |
| ~~`REPO_USER`~~ | ~~backend~~ | **OBSOLETE** | — | — | **REMOVED**`mongo` and `dual` modes no longer exist. |
| ~~`REPO_PAYMENT`~~ | ~~backend~~ | **OBSOLETE** | — | — | **REMOVED**`mongo` and `dual` modes no longer exist. |
| ~~`REPO_POINTS`~~ | ~~backend~~ | **OBSOLETE** | — | — | **REMOVED**`mongo` and `dual` modes no longer exist. |
| ~~`REPO_MARKETPLACE`~~ | ~~backend~~ | **OBSOLETE** | — | — | **REMOVED**`mongo` and `dual` modes no longer exist. |
---
## Payments — Oracle Quoting / Depeg Protection
| Name | Repo | Required | Default | Example | Purpose |
|------|------|----------|---------|---------|---------|
| `ORACLE_QUOTING_ENABLED` | backend | optional | `false` | `true` | Enables server-authoritative seller-offer quoting on `/api/payment/request-network/intents`. |
| `PRICE_ORACLE_PROVIDERS` | backend | optional | `chainlink,offchain_fx` | `chainlink,offchain_fx` | Ordered provider list used by the quote engine. |
| `ORACLE_MAX_STALENESS_S` | backend | optional | `120` | `120` | Rejects stale FX/token rates. |
| `ORACLE_DISAGREE_BPS` | backend | optional | `100` | `100` | Maximum allowed provider disagreement before the quote is blocked. |
| `DEPEG_HARD_CAP_BPS` | backend | optional | `500` | `500` | Blocks automatic quoting beyond this stablecoin depeg. |
| `QUOTE_VALIDITY_S` | backend | optional | `90` | `90` | Quote expiry window. |
| `REQUOTE_RECONFIRM_BPS` | backend | optional | `50` | `50` | Frontend/backend threshold for buyer reconfirmation after a material re-quote. |
| `OFFCHAIN_FX_URL` | backend | conditional | — | `https://fx.example/rates` | Required when `offchain_fx` is enabled for fiat currencies without Chainlink coverage. |
| `OFFCHAIN_FX_REQUEST_TIMEOUT_MS` | backend | optional | `8000` | `8000` | HTTP timeout for the off-chain FX provider. |
| `CHAINLINK_RPC_1` | backend | conditional | — | `https://...` | Ethereum RPC for Chainlink stablecoin/USD reads. |
| `CHAINLINK_RPC_56` | backend | conditional | — | `https://...` | BSC RPC for Chainlink stablecoin/USD reads. |
---
## Payments — Wallet UI (frontend)
| Name | Repo | Required | Default | Example | Purpose |
|------|------|----------|---------|---------|---------|
| `NEXT_PUBLIC_DEPAY_INTEGRATION_ID` | frontend | legacy only | — | `1330e2d3-…` | Historical DePay widget integration ID |
| `NEXT_PUBLIC_ESCROW_WALLET_ADDRESS` | frontend | ✅ | — | `0xa304…` | Escrow address shown to buyers in the wallet flow |
| `NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID` | frontend | ✅ | — | `283b54dd…` | WalletConnect v2 project ID |
| `NEXT_PUBLIC_ALCHEMY_API_KEY_MAINNET` | frontend | ✅ | — | — | Alchemy RPC for mainnet |
| `NEXT_PUBLIC_ALCHEMY_API_KEY_POLYGON` | frontend | ✅ | — | — | Alchemy RPC for Polygon |
| `NEXT_PUBLIC_ALCHEMY_API_KEY_SEPOLIA` | frontend | optional | — | — | Alchemy RPC for Sepolia (testing) |
---
## OAuth
| Name | Repo | Required | Default | Example | Purpose |
|------|------|----------|---------|---------|---------|
| `GOOGLE_CLIENT_ID` | backend | optional | — | `…apps.googleusercontent.com` | Verifies Google ID tokens server-side |
| `NEXT_PUBLIC_GOOGLE_CLIENT_ID` | frontend | ✅ for Google login | — | `…apps.googleusercontent.com` | Client ID used by Google Identity Services in the browser |
---
## OpenAI
| Name | Repo | Required | Default | Example | Purpose |
|------|------|----------|---------|---------|---------|
| `OPENAI_API_KEY` | backend | ✅ for AI features | — | `sk-…` | Used by `services/ai` |
| `OPENAI_DEFAULT_MODEL` | backend | ✅ for AI | `gpt-4o-mini` | `gpt-4o` | Default chat model |
| `OPENAI_MODEL` | backend | optional | falls back to default | `gpt-4o` | Per-call override read in legacy paths |
| `OPENAI_MAX_TOKENS` | backend | ✅ for AI | `1024` | `4096` | Hard cap per request |
| `OPENAI_TEMPERATURE` | backend | ✅ for AI | `0.7` | `0.2` | Decoded as float |
---
## App URLs
| Name | Repo | Required | Default | Example | Purpose |
|------|------|----------|---------|---------|---------|
| `FRONTEND_URL` | backend | ✅ | — | `http://localhost:8083` | Used by CORS, Socket.IO `origin`, password-reset email links |
| `BACKEND_URL` | backend | optional | derived from `PORT` | `http://localhost:5001` | Used in webhook callbacks & emails |
| `API_URL` | backend | optional | `${BACKEND_URL}/api` | `http://localhost:5001/api` | Self-reference for outbound webhooks |
| `PORT` | backend | ✅ | — | `5001` | HTTP listen port |
| `TRUST_PROXY` | backend | optional | auto-on in prod | `true` | Enables `app.set('trust proxy', 1)` for Nginx |
| `NEXT_PUBLIC_APP_URL` | frontend | ✅ | — | `http://localhost:8083` | Self-URL used in metadata + OG tags |
| `NEXT_PUBLIC_APP_NAME` | frontend | optional | `AMN` | `ایسکرو آنلاین` | Display name in nav / titles |
| `NEXT_PUBLIC_APP_VERSION` | frontend | optional | `package.json` | `2.8.94` | Shown in the version logger |
| `NEXT_PUBLIC_API_URL` | frontend | ✅ | — | `http://localhost:5001/api` | Axios base URL |
| `NEXT_PUBLIC_API_BASE_URL` | frontend | optional | derived | `http://localhost:5001` | Used by a few legacy callers |
| `NEXT_PUBLIC_BACKEND_URL` | frontend | ✅ | — | `http://localhost:5001` | Used by file URL builders |
| `NEXT_PUBLIC_SERVER_URL` | frontend | optional | mirror of backend URL | `http://localhost:5001` | Server-side rendering fallback |
| `NEXT_PUBLIC_SOCKET_URL` | frontend | ✅ | — | `http://localhost:5001` | Socket.IO endpoint |
| `NEXT_PUBLIC_ASSETS_DIR` | frontend | optional | `""` | `/assets` | Prefix for static asset URLs |
| `NEXT_PUBLIC_MAPBOX_API_KEY` | frontend | optional | — | `pk.…` | Mapbox token for delivery map |
---
## Rate limiting / files
| Name | Repo | Required | Default | Example | Purpose |
|------|------|----------|---------|---------|---------|
| `RATE_LIMIT_WINDOW_MS` | backend | ✅ | — | `900000` | Express-rate-limit window |
| `RATE_LIMIT_MAX_REQUESTS` | backend | ✅ | — | `100` | Requests allowed per window per IP |
| `MAX_FILE_SIZE` | backend | ✅ | — | `10485760` (10 MB) | Multer upload cap (bytes) |
| `UPLOAD_PATH` | backend | optional | `/app/uploads` | `/var/uploads` | Disk path for uploads (mounted volume) |
---
## Feature flags / seeding
| Name | Repo | Required | Default | Example | Purpose |
|------|------|----------|---------|---------|---------|
| `AUTO_SEED_ON_START` | backend | optional | `false` | `true` | Re-seeds users/addresses/templates on boot if `users` is empty |
| `SEED_USERS` | backend | optional | `false` | `true` | One-shot user seeding flag honoured by `seedUsers.ts` |
| `FORCE_SEED_TEMPLATES` | backend | optional | `false` | `true` | Re-creates request templates even if some exist |
| `BUILD_STATIC_EXPORT` | frontend | optional | `false` | `true` | Static export build (currently unused) |
| `NEXT_PUBLIC_IS_DEVELOPMENT` | frontend | optional | `false` | `true` | Shows the dev-only banner & debug helpers |
| `NEXT_PUBLIC_ENABLE_DEBUG` | frontend | optional | `false` | `true` | Verbose console logging in the browser |
| `NEXT_TELEMETRY_DISABLED` | frontend | optional | `0` | `1` | Disables Next.js telemetry |
---
## Passkey / WebAuthn (frontend)
| Name | Repo | Required | Default | Example | Purpose |
|------|------|----------|---------|---------|---------|
| `NEXT_PUBLIC_PASSKEY_RP_NAME` | frontend | optional | `Amn` | `Amn` | Relying-party display name |
| `NEXT_PUBLIC_PASSKEY_RP_ID` | frontend | optional | `localhost` | `amn.gg` | Relying-party origin host |
| `NEXT_PUBLIC_PASSKEY_ORIGIN` | frontend | optional | derived | `https://amn.gg` | Allowed origin for the WebAuthn challenge |
---
## Sentry
| Name | Repo | Required | Default | Example | Purpose |
|------|------|----------|---------|---------|---------|
| `NEXT_PUBLIC_SENTRY_DSN` | frontend | optional | — | `https://…ingest.sentry.io/…` | Browser & server Sentry DSN |
| `SENTRY_ORG` | frontend | build-time | — | `manawenuz` | Used by `@sentry/nextjs` source-map upload |
| `SENTRY_PROJECT` | frontend | build-time | — | `escrow-frontend` | Sentry project slug |
| `SENTRY_AUTH_TOKEN` | frontend | build-time | — | — | Auth token for source-map upload (CI secret) |
| `SENTRY_SUPPRESS_INSTRUMENTATION_FILE_WARNING` | frontend | optional | — | `1` | Silences known dev warning |
| `SENTRY_SUPPRESS_GLOBAL_ERROR_HANDLER_FILE_WARNING` | frontend | optional | — | `1` | Silences known dev warning |
| `SENTRY_DSN` | backend | optional | — | `https://…ingest.sentry.io/…` | Backend Sentry DSN (set in `src/config/sentry.ts`) |
The backend Sentry init runs **before** any other import (`src/app.ts` line 1) so DSN must be present in the env at process start.
---
## Quick `.env.local` template (backend, dev)
```bash
NODE_ENV=development
PORT=5001
TRUST_PROXY=false
# Database (PostgreSQL only — MongoDB removed in v2.9.12)
PG_URL=postgres://amanat:secret@postgres:5432/amanat_dev
# Cache
REDIS_URI=redis://redis:6379
# Auth
JWT_SECRET=<openssl rand -hex 32>
JWT_EXPIRES_IN=1h
REFRESH_TOKEN_EXPIRES_IN=30d
# URLs
FRONTEND_URL=http://localhost:8083
BACKEND_URL=http://localhost:5001
# Rate limit
RATE_LIMIT_WINDOW_MS=900000
RATE_LIMIT_MAX_REQUESTS=100
# Files
MAX_FILE_SIZE=10485760
UPLOAD_PATH=/app/uploads
# SMTP
SMTP_HOST=smtp.example.com
SMTP_PORT=465
SMTP_SECURE=true
SMTP_USER=
SMTP_PASS=
SMTP_FROM="AMN <no-reply@amn.gg>"
# Payments
PAYMENT_PROVIDER=request.network
PAYMENT_ENABLED_PROVIDERS=request.network
PAYMENT_ROLLBACK_PROVIDER=request.network
REQUEST_NETWORK_ENABLED=true
REQUEST_NETWORK_API_KEY=
REQUEST_NETWORK_API_BASE_URL=https://api.request.network
REQUEST_NETWORK_ORIGIN=https://dev.amn.gg
REQUEST_NETWORK_MERCHANT_REFERENCE=
REQUEST_NETWORK_NETWORK=bsc
REQUEST_NETWORK_PAYMENT_CURRENCY=USDC
REQUEST_NETWORK_WEBHOOK_CALLBACK_URL=https://dev.amn.gg/api/payment/request-network/webhook
REQUEST_NETWORK_WEBHOOK_SECRET=
REQUEST_NETWORK_ALLOW_TEST_WEBHOOKS=false
PAYMENT_LEDGER_ENFORCEMENT=true
PAYMENT_RECONCILIATION_ENABLED=false
TREZOR_SAFEKEEPING_REQUIRED=false
# OpenAI (optional)
OPENAI_API_KEY=
OPENAI_DEFAULT_MODEL=gpt-4o-mini
OPENAI_MAX_TOKENS=1024
OPENAI_TEMPERATURE=0.7
# Seeding
AUTO_SEED_ON_START=true
# Wallets
ESCROW_WALLET_ADDRESS=0xa3049825c0785095EEd5E7976E0E539466c84044
ADMIN_PAYOUT_WALLET_ADDRESS=
# Derived destinations (per-(buyer, sellerOffer) RN ephemeral wallets — Task #7)
# Backend ONLY needs the xpub. The master seed must live in KMS/Trezor.
DERIVED_DESTINATION_XPUB=
# Only set DERIVED_DESTINATION_XPRIV when DERIVED_DESTINATION_SWEEP_SIGNER=hot-key
# (dev shortcut). For prod, leave this blank and use the Trezor flow (Task #11).
DERIVED_DESTINATION_XPRIV=
DERIVED_DESTINATION_BASE_PATH=m/44'/60'/0'
DERIVED_DESTINATION_CHAIN_ID=56
DERIVED_DESTINATION_SWEEP_SIGNER=build-only
DERIVED_DESTINATION_MIN_SWEEP_AMOUNT=0
DERIVED_DESTINATION_SWEEP_INTERVAL_MS=300000
DERIVED_DESTINATION_SWEEP_AUTOSTART=true
# Master sweep wallet private key (pays gas for permit() + transferFrom() on non-BSC
# chains; sends BNB gas top-ups on BSC). Should be a dedicated low-balance hot wallet
# — NOT the same key used for escrow release/refund.
SWEEP_MASTER_PRIVKEY=
# BSC gas top-up thresholds (in BNB). If derived address BNB balance is below MIN, top up by TOP_UP.
SWEEP_GAS_MIN_BNB=0.001
SWEEP_GAS_TOP_UP_BNB=0.002
# AMN Pay Scanner (replaces Request Network for pay-in detection)
AMN_SCANNER_URL=
AMN_SCANNER_API_KEY=
AMN_SCANNER_WEBHOOK_SECRET=
AMN_SCANNER_DEFAULT=false
# Oracle quoting / stablecoin depeg protection
# Keep disabled until feeds and the off-chain FX source are configured.
ORACLE_QUOTING_ENABLED=false
PRICE_ORACLE_PROVIDERS=chainlink,offchain_fx
ORACLE_MAX_STALENESS_S=120
ORACLE_DISAGREE_BPS=100
DEPEG_HARD_CAP_BPS=500
QUOTE_VALIDITY_S=90
REQUOTE_RECONFIRM_BPS=50
OFFCHAIN_FX_URL=
OFFCHAIN_FX_REQUEST_TIMEOUT_MS=8000
CHAINLINK_RPC_1=
CHAINLINK_RPC_56=
# OAuth
GOOGLE_CLIENT_ID=
```
> [!tip] Generate `JWT_SECRET` deterministically per environment so you don't accidentally invalidate sessions when restarting. Store it in your team's secret manager.
> [!warning] `DERIVED_DESTINATION_XPRIV` is a development-only shortcut. In production, set `DERIVED_DESTINATION_SWEEP_SIGNER=build-only` and pair with Task #11 Trezor signing so the master seed never sits on the backend host.