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

418 lines
24 KiB
Markdown

---
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 |
| `PG_URL` | backend | conditional | — | `postgres://amanat:...@postgres:5432/amanat_dev` | Drizzle runtime DSN. Required before importing PG-backed code such as `quoteRepo`; does not cut over app domains by itself. |
| `MIGRATION_PG_URL` | backend | migration only | — | `postgres://amanat:...@postgres:5432/amanat_dev` | DSN used by backfill/migration scripts. Guarded by non-prod host allowlist. |
In `docker-compose.production.yml` the Mongo service is `mongodb` and is reachable as `mongodb://mongodb:27017` from the backend container.
> [!warning] Postgres cutover flags
> `REPO_*` flags exist in the backend repository factory, but broad services still call Mongoose models directly on `integrate-main-into-development@3a50dc4`. Do not assume setting `REPO_DEFAULT=pg` or `REPO_PAYMENT=pg` fully moves live traffic to Postgres without service wiring and verification. See [[Postgres Runtime Cutover Status]].
---
## 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. |
| `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. |
| `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)
| Name | Repo | Required | Default | Example | Purpose |
|------|------|----------|---------|---------|---------|
| `REPO_DEFAULT` | backend | optional | `mongo` | `dual` | Fallback repository mode for domains that do not set their own flag. Current broad runtime services are not yet wired through the factory. |
| `REPO_USER` | backend | optional | `REPO_DEFAULT` or `mongo` | `dual` | Intended user/auth repository mode. Requires service wiring before it affects normal requests. |
| `REPO_PAYMENT` | backend | optional | `REPO_DEFAULT` or `mongo` | `dual` | Intended payment/ledger repository mode. Current payment APIs still call Mongoose directly. |
| `REPO_POINTS` | backend | optional | `REPO_DEFAULT` or `mongo` | `dual` | Intended points/referral repository mode. Current points service still calls Mongoose directly. |
| `REPO_MARKETPLACE` | backend | optional | `REPO_DEFAULT` or `mongo` | `dual` | Intended purchase request / seller offer repository mode. Current marketplace services still call Mongoose directly. |
---
## 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` | `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=<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.