Skip to main content

Environment variables

Each app has a commented .env.example. On servers the env file lives at <APP_ROOT>/shared/.env and is linked into every release (see Deployment). NEXT_PUBLIC_* values are baked into the Next.js build: change them, then deploy again.

dyma-api​

The API validates its environment at boot (Joi) and refuses to start when a required value is missing or malformed. Env files: .env is read first, then .env.local; the first file that sets a key wins, and real environment variables (PM2, shell, CI) beat both.

Required​

VariableNotes
DATABASE_URLPostgreSQL 16
REDIS_URLRedis 7 (cache, nonces, pub/sub, BullMQ)
JWT_SECRET≥ 16 chars (openssl rand -hex 32)
CREDENTIALS_MASTER_KEYexactly 64 hex chars (openssl rand -hex 32): AES-256-GCM key of every stored credential. Losing it means re-entering every credential; rotating it needs a re-encryption

Runtime and URLs​

VariableNotes
NODE_ENVunset means production (with a boot warning): no logged OTPs, no Swagger, no mock checkout
PORTdefault 4000 (testnet 4700)
API_PUBLIC_URLpublic API origin; Telegram webhooks and the escrow's on-chain allocation_uri are built from it
AUTH_CALLBACK_BASE_URL, AUTH_FRONTEND_REDIRECT_URLOAuth callbacks
CLIENT_APP_URL, STUDIO_APP_URL, ADMIN_APP_URL, LANDING_APP_URLbare origins (no /app); OAuth redirect allowlist and links in mails and notifications
CORS_ORIGINSonly for browser → API calls; the apps use server-side BFFs
API_DOCStrue forces Swagger at /docs in production

Proxies and the BFF secret​

VariableNotes
TRUSTED_PROXY_HOPShow many X-Forwarded-For entries (from the right) are believed: 1 behind a single nginx (default in production), 2 with a CDN in front, 0 when exposed directly. Too high lets callers spoof their address
BFF_SHARED_SECRET≥ 32 chars, the same value in the API, the client, Studio and the admin console. The Next.js servers relay every API call; with the secret they add X-Dyma-Bff-Key and X-Dyma-Client-IP, and the API rate-limits, audits and passes to Turnstile the visitor's address (compared in constant time). Without it, every signed-out visitor shares the BFF server's rate-limit bucket

Admin, security, queues, files​

VariableNotes
ADMIN_JWT_SECRET≥ 16, distinct from JWT_SECRET in production (blank = derived from it)
ADMIN_BOOTSTRAP_EMAIL, ADMIN_BOOTSTRAP_PASSWORD, ADMIN_BOOTSTRAP_RESETfirst super admin, created by npm run prisma:seed; the seed resets the password only with ADMIN_BOOTSTRAP_RESET=true
TURNSTILE_SECRET_KEYrequired in production (pairs with Studio's site key)
LOGIN_OTP_BYPASS_DOMAINtestnet only — never set it in production. Accounts of that e-mail domain skip the login OTP (used for the seeded demo.dyma.io personas, whose shared password is public)
BULLMQ_PREFIXe.g. dyma-testnet, so two environments sharing a Redis never take each other's jobs
FILES_DRIVER, FILES_LOCAL_DIRs3 or local. Unset: S3 when credentials exist, else the local disk outside production. local stores under FILES_LOCAL_DIR (default ./storage/files) — dev and testnet only
HETZNER_S3_*, FILES_PUBLIC_BASE_URLS3 fallback when no credential is stored in admin
OUTBOUND_ALLOWED_HOSTShosts exempt from the outbound address checks (self-hosted services on a private network)
INDEXER_WEBHOOK_SECRETHMAC secret of POST /v1/webhooks/indexer; empty refuses every call

Wallets and chains​

VariableNotes
DYMA_DEFAULT_KEPLR_CHAIN_IDdefault safrochain-1
DYMA_EXTRA_KEPLR_CHAINSextra chain ids allowed for wallet sign-in
WALLETCONNECT_PROJECT_IDWalletConnect Cloud project id served to the apps
TOKENFACTORY_TRUSTED_ADMINStokenfactory/CW20 admins rated "trusted" by reward-token inspection

Escrow (automatic distribution)​

VariableDefaultNotes
ESCROW_ENABLEDfalseruns the escrow scan (60 s), indexer (30 s) and health (5 min) jobs and opens automatic distribution
ESCROW_SIGNERmnemonicmnemonic (encrypted platform credentials) or kms — see Escrow operations
ESCROW_OPERATOR_KMS_KEY_ID, ESCROW_TICKET_KMS_KEY_ID, ESCROW_PAUSER_KMS_KEY_ID, ESCROW_KMS_REGIONKMS mode
ESCROW_OPERATOR_MNEMONIC, ESCROW_TICKET_MNEMONIC, ESCROW_PAUSER_MNEMONICdev/testnet only, refused when NODE_ENV=production
ESCROW_GAS_PRICE0.075usafused when the chain has no gas price in admin; never below the network minimum (0.05usaf on mainnet)
ESCROW_GAS_ADJUSTMENT1.3simulation × adjustment
ESCROW_COMMIT_BATCH1000entries per commit transaction
ESCROW_DISTRIBUTE_BATCH250payouts per crank transaction (keeps a result near a quarter of a megabyte)
ESCROW_REVIEW_GRACE_HOURS24default review grace (1–72)
ESCROW_OPERATOR_MIN_BALANCE_USAF50000000alert below this operator gas balance (50 SAF)
ESCROW_DEV_HOUR_SECS3600localnet/dev only: seconds in one escrow "hour"; ignored in production

Payments​

VariableDefaultNotes
PAYMENTS_PROVIDERcryptocrypto, stripe (kept, hidden) or mock (refused in production)
PAYMENTS_CHAIN_IDsafrochain-1testnet: safro-testnet-1 (selects the testnet USDC)
PAYMENTS_ACCEPTED_DENOMSnetwork USDCcomma-separated, first is the default
PAYMENTS_TREASURY_ADDRESS—receives plan payments (multisig); without it crypto payments are off
BILLING_TREASURY_ADDRESSES—optional allowlist (comma/space separated): when set, the Safrochain treasury (addr_safro1…) and the Injective treasury (inj1…) saved in Admin → Billing → Settings must each be one of these; a treasury removed from it stops taking payments. Unset: any valid address of the right prefix
PAYMENTS_INVOICE_TTL_MINUTES60payment request lifetime
PAYMENTS_MIN_CONFIRMATIONS1
STRIPE_ENABLED, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRETfalseall three needed before Stripe can be selected

These are defaults: once an admin saves Billing → Settings, the stored settings win (see Billing settings).

Integrations​

GOOGLE_*, TWITTER_* (incl. TWITTER_BEARER_TOKEN), GITHUB_*, YOUTUBE_*, DISCORD_CLIENT_ID, AWS_SES_*, MAIL_LOGO_URL, MAIL_SUPPORT_EMAIL. Most provider credentials are better stored encrypted in Admin → Integrations (they take precedence); SES and Stripe are read from the environment.

dyma-client (earner app)​

VariableWhenNotes
NEXT_PUBLIC_API_URLbuildAPI origin without /v1; also allowed in the CSP
NEXT_PUBLIC_LANDING_URL, NEXT_PUBLIC_STUDIO_URL, NEXT_PUBLIC_DOCS_URLbuildlinks
NEXT_PUBLIC_BASE_PATHbuildleave empty (the app owns its origin's root)
NEXT_PUBLIC_WALLETCONNECT_PROJECT_IDbuildempty disables WalletConnect; when set, the WalletConnect relay, RPC and verify hosts join connect-src and frame-src
NEXT_PUBLIC_CHAIN_HOSTSbuildextra chain RPC/REST origins the browser may call (see below)
CLIENT_APP_URLruntimeOAuth redirectUri = ${CLIENT_APP_URL}/auth/callback
BFF_SHARED_SECRETruntimesame value as the API's; never prefix it with NEXT_PUBLIC_
TRUSTED_PROXY_HOPSruntimeproxies in front of Next that append to X-Forwarded-For (1 behind nginx)

dyma-studio​

Same as the client for NEXT_PUBLIC_API_URL, NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID, NEXT_PUBLIC_CHAIN_HOSTS, BFF_SHARED_SECRET, TRUSTED_PROXY_HOPS, plus:

VariableWhenNotes
STUDIO_APP_URLruntimeOAuth redirectUri = ${STUDIO_APP_URL}/auth/callback
NEXT_PUBLIC_TURNSTILE_SITE_KEYbuildCloudflare Turnstile site key
NEXT_PUBLIC_CLIENT_APP_URLbuildearner app origin (default https://app.dyma.io); signup consent links go to its /legal/terms and /legal/privacy
NEXT_PUBLIC_TERMS_URL, NEXT_PUBLIC_PRIVACY_URLbuildoptional overrides for those two links

dyma-admin​

VariableWhenNotes
NEXT_PUBLIC_API_URLbuildAPI origin without /v1
NEXT_PUBLIC_CLIENT_APP_URLbuildearner app origin for links
BFF_SHARED_SECRETruntimesame value as the API's; the BFF and the sign-in routes then relay each operator's address (per-operator sign-in rate limits and audit)
TRUSTED_PROXY_HOPSruntimeproxies in front of Next that append to X-Forwarded-For (1 behind nginx)

dyma-landing​

VariableWhenNotes
DYMA_API_URLbuild + runtime (server only)public API origin; on the same host use http://127.0.0.1:<api port>
NEXT_PUBLIC_SITE_URL, NEXT_PUBLIC_APP_URL, NEXT_PUBLIC_STUDIO_URL, NEXT_PUBLIC_DOCS_URLbuildno /app suffix
NEXT_PUBLIC_EXPLORER_URLbuildhttps://explorer.safrochain.network
NEXT_PUBLIC_NOINDEXbuildtrue on testnet and previews
LANDING_MIN_FIGURE, REVALIDATE_SECRET, NEXT_PUBLIC_DISCORD_URL, NEXT_PUBLIC_TELEGRAM_URL, NEXT_PUBLIC_CONTRACTS_URLoptional

dyma-docs​

DOCS_URL and DOCS_BASE_URL at build time (GitHub Pages defaults: https://dyma-io.github.io, /dyma-docs/; a subdomain root needs DOCS_BASE_URL=/).

Chain hosts and the Content-Security-Policy​

The client and Studio send a strict CSP. connect-src always allows the app itself, the API and Safrochain's public endpoints (rpc.safrochain.network, api.safrochain.network, rpc.testnet.safrochain.com, rest.testnet.safrochain.com). Any other chain the browser must reach — to broadcast a transaction on another chain or read an escrow there — goes in NEXT_PUBLIC_CHAIN_HOSTS:

NEXT_PUBLIC_CHAIN_HOSTS=https://rpc.osmosis.zone,https://lcd.osmosis.zone

Only exact https:// or wss:// origins are accepted (a leading *. wildcard is allowed); a bare https:, *, paths and keywords are ignored with a build warning, so one careless entry cannot open connect-src to the whole web. Plain http://localhost works in development only.