--- 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 | ✅ | — | `mongodb://mongodb:27017` | Mongo connection string (no auth in dev) | | `DB_NAME` | backend | ✅ | — | `marketplace` | Database name appended to the URI | In `docker-compose.production.yml` the Mongo service is `mongodb` and is reachable as `mongodb://mongodb:27017` from the backend container. --- ## 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" ` | Default `From` header | --- ## Payments — SHKeeper SHKeeper is the crypto payment gateway. See [[Payment Flow]] and [[SHKeeper Integration]] in the architecture section. | Name | Repo | Required | Default | Example | Purpose | |------|------|----------|---------|---------|---------| | `SHKEEPER_BASE_URL` | backend | ✅ | — | `https://shkeeper.example.com` | Base API URL | | `SHKEEPER_API_URL` | backend | ✅ | — | `https://shkeeper.example.com/api/v1` | Versioned API URL | | `SHKEEPER_API_KEY` | backend | ✅ | — | — | `X-Shkeeper-Api-Key` header | | `SHKEEPER_WEBHOOK_SECRET` | backend | ✅ | — | — | HMAC secret for inbound webhook signatures | | `SHKEEPER_CALLBACK_SECRET` | backend | ✅ | — | — | Older alias for webhook secret; some payloads still use it | | `SHKEEPER_ALLOWED_TOKENS` | backend | optional | `USDT,USDC` | `USDT,USDC,BTC` | Comma-separated list of accepted tokens | | `SHKEEPER_NETWORKS` | backend | optional | `bsc,polygon` | `bsc,polygon,eth` | Networks enabled in checkout | | `SHKEEPER_ENVIRONMENT` | backend | optional | `production` | `sandbox` | Switches SHKeeper sandbox vs prod behaviour | | `SHKEEPER_FORCE_PAYOUT_DEMO` | backend | optional | `false` | `true` | Skips real-chain payout; demo-confirms after 5s | | `SHKEEPER_FORCE_REAL` | backend | optional | `false` | `true` | Forces real-chain even in dev/sandbox | | `ADMIN_PAYOUT_WALLET_ADDRESS` | backend | ✅ for payouts | — | `0xAc23…` | Wallet that receives platform fees / payouts | | `ESCROW_WALLET_ADDRESS` | backend | ✅ | — | `0xa304…` | Master escrow address used by payments service | | `RECEIVER_WALLET_ADDRESS` | backend | optional | — | `0x…` | Used by alternative payout flows | --- ## Payments — Provider Selection | Name | Repo | Required | Default | Example | Purpose | |------|------|----------|---------|---------|---------| | `PAYMENT_PROVIDER` | backend | optional | `shkeeper` | `request.network` | Active provider for new payment intents | | `PAYMENT_DEFAULT_PROVIDER` | backend | optional | `shkeeper` | `shkeeper` | Fallback alias when `PAYMENT_PROVIDER` is unset | | `PAYMENT_ENABLED_PROVIDERS` | backend | optional | `shkeeper` | `shkeeper,request.network` | Comma-separated providers allowed at runtime | | `PAYMENT_ROLLBACK_PROVIDER` | backend | optional | `shkeeper` | `shkeeper` | 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` | `12` | Minimum chain confirmations required by the Transaction Safety Provider. | | `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 — DePay / Web3 (frontend) | Name | Repo | Required | Default | Example | Purpose | |------|------|----------|---------|---------|---------| | `NEXT_PUBLIC_DEPAY_INTEGRATION_ID` | frontend | ✅ for DePay | — | `1330e2d3-…` | 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` | `1.0.2` | 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 MONGODB_URI=mongodb://mongodb:27017 DB_NAME=marketplace # Cache REDIS_URI=redis://redis:6379 # Auth JWT_SECRET= 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 " # SHKeeper (set when ready) PAYMENT_PROVIDER=shkeeper PAYMENT_ENABLED_PROVIDERS=shkeeper PAYMENT_ROLLBACK_PROVIDER=shkeeper REQUEST_NETWORK_ENABLED=false PAYMENT_LEDGER_ENFORCEMENT=false PAYMENT_RECONCILIATION_ENABLED=false TREZOR_SAFEKEEPING_REQUIRED=false SHKEEPER_BASE_URL= SHKEEPER_API_URL= SHKEEPER_API_KEY= SHKEEPER_WEBHOOK_SECRET= SHKEEPER_CALLBACK_SECRET= SHKEEPER_FORCE_PAYOUT_DEMO=true # 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 # 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.