> For the complete documentation index, see [llms.txt](https://docs.trover.tech/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.trover.tech/engineering/production-environment.md).

# Production environment checklist

## Vercel (frontend only)

Set these for the Production environment. Public variables are compiled into the browser bundle:

```dotenv
NEXT_PUBLIC_API_URL=https://api.trover.tech
NEXT_PUBLIC_SITE_URL=https://trover.tech
NEXT_PUBLIC_DOCS_URL=https://docs.trover.tech
NEXT_PUBLIC_EXPLORER_URL=https://robinhoodchain.blockscout.com
NEXT_PUBLIC_DEXSCREENER_URL=https://dexscreener.com/robinhood
NEXT_PUBLIC_X_HANDLE=@troverobinhood
NEXT_PUBLIC_BOT_X_HANDLE=@troverobinhood
NEXT_PUBLIC_X_URL=https://x.com/troverobinhood
NEXT_PUBLIC_PRIVY_APP_ID=<Privy production app ID>
NEXT_PUBLIC_PRIVY_BASE_DOMAIN=<Privy custom/base domain; leave empty unless enabled>
NEXT_PUBLIC_CONTRACT_ADDRESS=<Trover token contract address>
NEXT_PUBLIC_TRADING_URL=<full Uniswap ETH-to-Trover swap URL>
NEXT_PUBLIC_CLAIM_DISTRIBUTOR_ADDRESS=<deployed stock claim contract; leave empty until deployed>
```

In the Privy Dashboard login-logo field, use:

```
https://trover.tech/trover-privy-login.png
```

The repository serves this as a 180×90 PNG (the dashboard’s recommended 2:1 ratio). It becomes available as soon as Vercel deploys this revision.

Enable Privy's `wallet.private_key_export` webhook event for `https://api.trover.tech/v1/privy/webhook`. Trover records only the export event and public wallet address for auditability; it never receives, logs, or stores a user's private key.

The web app sends a restrictive Privy-compatible CSP. During an SDK or wallet integration rollout, set `WEB_CSP_REPORT_ONLY=true` for the Vercel build, inspect browser CSP violations while exercising X login, embedded-wallet creation, WalletConnect, bridge, and trade flows, then set it back to `false` to enforce. Do not add broad `https:` or `wss:` sources merely to silence a violation: add the exact documented origin.

After changing a `NEXT_PUBLIC_*` value, redeploy the frontend. Never put an app secret, API secret, access token, admin code, database URL, authorization key, or private key in Vercel's frontend variables.

## Privy dashboard

* Allowed origins: `https://trover.tech`, `https://www.trover.tech`, and `https://dex.trover.tech`.
* Remove preview, localhost, and old domains from Privy's production allowed domains. Keep local/staging domains in separate Privy apps.
* If HttpOnly cookies are enabled, set the cookie base domain to `trover.tech`, complete every DNS record, and keep Privy's verification record DNS-only until verification succeeds.
* Enable X and wallet login methods.
* Use only the X OAuth scopes required by the product. Do not enable token return unless a backend feature genuinely needs the user’s X token.
* Offer passkeys/authenticator MFA and recovery for higher-value users without making it a prerequisite for ordinary wallet connection or low-friction trading.

### Existing-wallet automation

An existing Privy embedded or smart wallet does not need to reconnect X or create a new account. After logging in, the user opens Account and selects **Enable automatic execution** once. Privy then records the user-approved delegated signer and Trover can use the enabled action channels without asking again for each action.

An external wallet cannot give Trover durable signing authority through a single ordinary wallet signature: its provider still owns the private key. The Account screen now explains this and offers **Create automated Trover wallet**. The user can move only the funds they want automated into that user-owned Privy wallet, then enable automatic execution once. This does not require relinking their X account.

Before testing either flow in production, enable Privy delegated actions (or session signers) for the production app in the Privy Dashboard. The Account screen opens Privy's one-time consent and stops with an actionable message after 45 seconds if that approval cannot open; it never leaves the user on an indefinite connecting state.

* If invisible CAPTCHA remains enabled, Cloudflare Turnstile/hCaptcha must be allowed. The frontend CSP includes both providers.
* Set the webhook to `https://api.trover.tech/v1/privy/webhook` and install its signing key on the backend.

## Backend server

These values are server-only:

```dotenv
NODE_ENV=production
WEB_ALLOWED_ORIGINS=https://trover.tech,https://www.trover.tech
ADMIN_API_KEY=<random high-entropy internal-panel code>
FIELD_ENCRYPTION_KEY=<32-byte secret>
OPENAI_API_KEY=<secret>
OPENAI_MODEL=gpt-5.6-luna
OPENAI_INPUT_USD_PER_1M=1
OPENAI_OUTPUT_USD_PER_1M=6

PRIVY_APP_ID=<same production app ID as Vercel>
PRIVY_APP_SECRET=<secret>
PRIVY_AUTHORIZATION_KEY=<Privy delegated-wallet authorization key, if used>
PRIVY_WEBHOOK_SIGNING_KEY=<Privy webhook signing key>

X_API_KEY=<secret>
X_API_SECRET=<secret>
X_BEARER_TOKEN=<secret>
X_CLIENT_ID=<OAuth 2 client ID>
X_CLIENT_SECRET=<OAuth 2 client secret>
X_USER_ACCESS_TOKEN=<@troverobinhood user access token>
X_USER_REFRESH_TOKEN=<refresh token>
X_OAUTH1_ACCESS_TOKEN=<long-lived @troverobinhood access token>
X_OAUTH1_ACCESS_TOKEN_SECRET=<long-lived @troverobinhood access token secret>
X_WEBHOOK_SECRET=<the same Consumer/API Secret used by the selected X Client App>
X_WEBHOOK_ID=<registered webhook ID>
BOT_X_USER_ID=<numeric user ID for @troverobinhood>
BOT_X_USERNAME=troverobinhood
PROJECT_X_USERNAME=troverobinhood
FOUNDER_X_USERNAME=seyidmain

ENABLE_X_ACTIVITY_WEBHOOKS=true
ENABLE_POLLING_FALLBACK=false
ENABLE_X_SEARCH_BACKFILL=false
ENABLE_X_THREAD_BACKFILL=false
ENABLE_MARKET_RADAR=false
ENABLE_OFFICIAL_X_TRENDS=false
ENABLE_TREND_POSTS=false
ENABLE_PROACTIVE_POSTS=false

PLATFORM_FEE_BPS=50
PLATFORM_FEE_WALLET=0x4b14412a1cb6f30622f25A6706Ff26ec953Ec486
TREASURY_WALLET_ADDRESS=0x4b14412a1cb6f30622f25A6706Ff26ec953Ec486
FEE_ACCUMULATOR_PRIVATE_KEY=<server-only signer for the address above>
TOKEN_FEE_ACCUMULATOR_WALLET_ADDRESS=0xdaf20dd5B1A15d930aE05f2082a6571bD277cc7C
TOKEN_FEE_ACCUMULATOR_PRIVATE_KEY=<server-only signer for that address>
TREASURY_BUY_THRESHOLD_USDG=2000
TREASURY_BUYBACK_PERCENT=20
TREASURY_BUYBACK_TARGET_COUNT=3
TREASURY_BUY_ENABLED=false

CLAIM_DISTRIBUTOR_ADDRESS=<stock distributor; empty until deployed>
ETH_CLAIM_DISTRIBUTOR_ADDRESS=<native ETH distributor; empty until deployed>
TOKEN_FEE_CLAIM_PERCENT=20
TOKEN_FEE_GAS_RESERVE_ETH=0.001

# Deployed stack (deploy-stack.mjs prints these). Empty until mainnet; the
# contract-balance-watch job is a no-op while they are empty.
OPENSEA_REVENUE_ROUTER_ADDRESS=
LEAGUE_VAULT_ADDRESS=
AGENT_PROGRESS_ADDRESS=
PLANET_RENDERER_ADDRESS=
CONTRACT_BALANCE_ALERT_ETH=0.01

# NFT market. Chain first; OpenSea is enrichment and fallback.
OPENSEA_ENABLED=true
OPENSEA_API_KEY=<secret, 120 calls/min shared by api and worker>
OPENSEA_CHAIN=robinhood
NFT_DISCOVERY_CHAINS=            # extra chains to keep discovery lists warm for; leave unset
NFT_REALTIME_ENABLED=true
NFT_REALTIME_SHADOW_MODE=false
ROBINHOOD_NFT_WS_URLS=<chainstack wss>,<tenderly wss>
ROBINHOOD_NFT_ARCHIVE_RPC_URLS=<free archive https>,<dwellir https>|rps=0.5
ROBINHOOD_LOG_RPC_URLS=<public>,<tenderly>,<blockmachine>,<solidrpc>,<alchemy>|lanes=log|cu=15M
ROBINHOOD_HEAD_RPC_URLS=<chainstack>,<publicnode>
ROBINHOOD_RPC_URL=<chainstack>
ROBINHOOD_SIGNER_RPC_URL=<alchemy>
NFT_ONCHAIN_MAX_LAG_BLOCKS=1500000   # default 216000 (~6 h); raised so the backfill lane never abandons a gap

# Worker headroom (compose defaults are 3g / 2048).
TROVER_WORKER_MEMORY_LIMIT=6g
TROVER_WORKER_MAX_OLD_SPACE_MB=4096
```

RPC list entries take `url|rps=N|lanes=log|cu=15M` options; see `RPC-COST-CONTROL.md` before adding an endpoint. `NFT_ONCHAIN_MAX_LAG_BLOCKS` is 1,500,000 on the server since 2026-09-07 (a 257k-block mint gap had been abandoned under the default). To re-scan a span, set the Redis gap marker `trover:nft-mint-gap:v1:<chain>` rather than editing the cursor row.

Keep `TREASURY_BUY_ENABLED=false` until the fee signer, production routes, asset allowlist, Safe, and both distributors have been verified with a small-value end-to-end transaction.

### Updating the backend environment

Vercel variables do not reach the API or worker running on the dedicated server. Update the server environment over SSH:

```bash
ssh -i /Users/user/.ssh/id_ed25519_trevor_server -p 22022 teztap@217.64.28.103   # alias: trevor-server
cd /home/teztap/trover
cp .env ".env.backup.$(date +%Y%m%d%H%M%S)"
chmod 600 .env
nano .env
```

An env-only change needs a recreate, not a build:

```bash
docker compose -f docker-compose.yml -f docker-compose.production.yml -f docker-compose.nft-realtime.yml up -d --force-recreate --no-deps api worker nft-realtime
docker ps --format '{{.Names}} {{.Status}}' | grep trover
curl --fail https://api.trover.tech/health/ready
```

A code deploy is launched detached under `nohup` on the server, one at a time, with both base compose files and the realtime file when the worker changed; `docker compose logs --since` is broken on this host, use `--tail`. The exact commands, the migration variant and the checks are in `runbook.md` under "Deploy the backend".

### Postgres and the host

Applied with `ALTER SYSTEM` (reload-safe): `wal_compression=on`, `max_wal_size=4GB`, `checkpoint_timeout=15min`. Recommended and pending a restart: `shared_buffers=4GB`, `wal_buffers=64MB`, `effective_cache_size=24GB`. The role and database are both `meow`.

The 8 vCPU host shares its disk with another application's Postgres that has written more than twice what ours has (8.8 TB against 3.9 TB); I/O wait sits at 20 to 40%. That is the cause of ten-minute builds, statement timeouts on the API's 15 s limit and `LWLock:WALWrite` waits in the worker. Details and the incident checks are in `runbook.md`.

Keep the X API key, API secret, bearer token, and OAuth tokens from one X Client App together. `X_WEBHOOK_SECRET` is not a separately invented value: X computes CRC and delivery signatures with that Client App's Consumer/API Secret. The API also falls back to `X_API_SECRET` when `X_WEBHOOK_SECRET` is empty.

### Public X eligibility and official cashtags

The worker ignores its own `@troverobinhood` output. Other public posts are eligible only when they explicitly mention `@troverobinhood`, reply directly to the bot, or reply to/quote a post owned by the bot or project account. DMs retain their existing behavior.

Set `PROJECT_X_USERNAME` to the project account whose posts count as owned. Enable both `ENABLE_POLLING_FALLBACK=true` and `ENABLE_X_THREAD_BACKFILL=true` when replies and quotes that omit the bot mention must be discovered. The thread scan is part of the polling job and uses X reads, so keep the monthly read limit configured. Explicit mentions continue to arrive through the activity webhook or polling fallback.

Internal operators can confirm `$TICKER` to Robinhood Chain token-address mappings in `/internal`. The mapping is copied into the immutable agent run only when an eligible public X post contains the literal cashtag. Bare ticker words and DMs do not activate the registry. Deactivation affects future runs only; downstream contract validation, simulations, limits, confirmations, and execution policy remain authoritative.

### Adaptive trade slippage

Quotes start at `TRADE_DEFAULT_SLIPPAGE_PERCENT` (0.5% by default). When Uniswap explicitly returns `SLIPPAGE_TOO_LOW`, the hosted venue requotes at one bounded intermediate level and then at the customer's maximum. It does not increase slippage for unrelated simulation failures. The selected attempt, attempted levels, and ceiling are retained with the quote for auditing.

Each profile starts with a 5% maximum and can lower it from the account settings. `TRADE_MAX_SLIPPAGE_PERCENT` is the server-wide absolute ceiling and defaults to 10%. Set `ROUTE_QUOTE_DEADLINE_MS=15000` so bounded retries have time to complete. No route may exceed the customer ceiling, and a successful simulation remains required before submission.

## X MCP (operator workstation only)

The hosted X MCP bridge is not a production worker and performs no background polling. Configure:

```dotenv
X_MCP_URL=https://api.x.com/mcp
X_MCP_CLIENT_ID=<may reuse X_CLIENT_ID>
X_MCP_CLIENT_SECRET=<may reuse X_CLIENT_SECRET>
X_MCP_REDIRECT_URI=http://localhost:8080/callback
X_MCP_USERNAME=troverobinhood
```

Register the exact callback URL in the X developer app, then run `pnpm x:mcp` while logged into `@troverobinhood` to complete OAuth.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.trover.tech/engineering/production-environment.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
