Appearance
Local development
Running the stack on your machine. Read the database section first — it is the part that can cause real damage.
Ports
| Service | Port | Command |
|---|---|---|
bz-backend | 5000 | npm run dev |
bz-agent | 4000 | npm run dev |
defi-dex defi shell | 5173 | npm run dev |
defi-dex AI shell | 5174 | npm run dev:ai |
defi-dex AI preview | 4174 | npm run preview:ai |
| These docs | 5199 | npm run dev in docs/ |
All of these are already in the backend CORS allowlist
http://localhost:5173, :5174, :5000, :3000, :3001, :3002 and :3039 are allowlisted in bz-backend/index.js, so a local frontend can talk to production API with no CORS change.
The database
Local dev points at production Atlas by default
bz-backend's local .env uses the production MONGO_URI, and Atlas Network Access includes 223.233.0.0/16 for rotating developer ISP IPs. Your local backend reads and writes production data.
There is no staging database in the current setup.
Two consequences worth internalising:
1. Set CRON=false before you start
bash
# bz-backend/.env
CRON=falseWithout it your laptop becomes a second CSIP and Autopilot executor, using the same PRIVATE_KEY_EXECUTOR as production, competing with the real cron and submitting duplicate transactions.
2. The loose scripts at the repo root write to production
bz-backend/ has dozens of one-off maintenance scripts committed at the top level:
fix_admin_and_txs.js delete_remaining_anomalies.js
migrate_exempt.js check_affected_users.js
check_all_tx_apr26.js fix_other_damage.js
approve_existing_admins.js …and ~25 moreRead any of these before running it
Several delete or bulk-update documents. They were written for a specific incident and are not idempotent. scripts/ contains another ~60, a mixture of read-only checks (checkRelayerDeployed.js, check-contract-owner.js) and mutations (curate-token-order.js, enableChain.js, enableRelayerFees.js).
Redis
Local dev falls back to an in-memory stub when Redis is not available (or DISABLE_REDIS is set).
| Consequence | Detail |
|---|---|
| Quote cache not shared between processes | Fine for a single local backend |
| Socket.io Redis adapter disabled | socket.js checks typeof redisPubClient?.publish === 'function' and silently runs without it |
| Autopilot price series lost on restart | dip_buy triggers will not fire until the window refills |
| LangGraph checkpoints lost | Multi-turn chat context resets |
Running a real Redis locally is a one-liner and worth it if you are working on quotes or Autopilot:
bash
docker run -d -p 6379:6379 redis:7-alpineStarting the stack
bash
# terminal 1 — backend
cd bz-backend && npm install && npm run dev # :5000
# terminal 2 — agent (only if working on chat)
cd bz-agent && npm install && npm run dev # :4000
# terminal 3 — frontend
cd defi-dex && npm install && npm run dev # :5173
# or, for the AI shell
cd defi-dex && npm run dev:ai # :5174bz-agent needs PostgreSQL with PGVector for the FALLBACK intent's knowledge base. Without it, fallback answers degrade but nothing crashes.
Minimum environment
You do not need every variable. To get a working local stack:
bz-backend/.env
bash
PORT=5000
CRON=false # ← do this
MONGO_URI=… # production Atlas
REDIS_URL=redis://localhost:6379
AGENT_URL=http://localhost:4000
# only if you are working on the relevant feature:
TX_SIGNER=… # swap signing
POL_TX_SIGNER=… # Polygon swap signing
ONE_INCH_KEY=… # 1inch routes
OPEN_OCEAN_API_KEY=… # also powers gas prices
UNIZEN_API_KEY=…
ETHERSCAN_API_KEY=… # auditor, token safety, approvals, wallet card
OPENAI_API_KEY=… # auditor, token safety modelsDo not put PRIVATE_KEY_EXECUTOR in a local .env unless you need it
With CRON=false nothing uses it. Omitting it removes the possibility of a stray script signing with the production executor key.
bz-agent/.env
bash
PORT=4000
LLM_OPENAI_API_KEY=…
MONGO_URI=…
REDIS_URL=redis://localhost:6379
# PostgreSQL for PGVector, if working on FALLBACKSet all three Google variables or none
LLM_GOOGLE_PROJECT_ID, LLM_GOOGLE_CLIENT_EMAIL and LLM_GOOGLE_PRIVATE_KEY are checked together. With one missing, the conversation node silently falls back to the cheaper extraction model instead of the conversation tier — so answers get worse with no error.
defi-dex/.env
bash
VITE_SITE=defi # or `ai`
VITE_API_URL=http://localhost:5000/api
# VITE_GA_MEASUREMENT_ID intentionally unset — analytics is a no-op without it.env.ai holds the AI shell's values and is used by --mode ai.
Running both frontends at once
bash
npm run dev # :5173 defi shell
npm run dev:ai # :5174 AI shellTwo real separate origins, which is what you need to test the domain split properly. Both ports are allowlisted server-side.
Branch on IS_AI_SITE, never on window.location
src/config/site.ts reads VITE_SITE. Sniffing location produces server/client divergence in the prerender, which Google reads as cloaking.
Working on swap-sdk locally
The SDK is a git dependency, so a local change is not picked up automatically. Two options:
Option A — npm link (fast iteration):
bash
cd swap-sdk && npm run build && npm link
cd ../bz-backend && npm link swap-sdkRemember npm run build after every SDK edit — consumers import from dist/.
Option B — build and copy (closer to reality):
bash
cd swap-sdk && npm run build
rm -rf ../bz-backend/node_modules/swap-sdk/dist
cp -r dist ../bz-backend/node_modules/swap-sdk/npm install in the consumer wipes a link
And it reinstalls the pinned tag. Re-link after any install.
Building
bash
# frontend, full production build (114 prerendered pages + OG images)
cd defi-dex && npm run build
# frontend, app only (fast)
npm run build:app
# AI shell
npm run build:ai # includes the mandatory rewrite-ai-shell.mjs
# route sanity check
npm run check:routesnpm run build is slow because it does three things
vite build, then prerender-seo.mjs (114 pages, sitemap, llms.txt), then generate-og.mjs (per-page PNGs via resvg). Use build:app when you only need to know that the bundle compiles.
Contract work
bash
cd bz-backend/contract-deployment
npx hardhat compile
npx hardhat test test/autopilot.test.cjs # 61 passingTwo Arbitrum network definitions, two different signers
arbitrum signs with BACKEND_PRIVATE_KEY (the Autopilot owner); arbitrum_dca_owner signs with DCA_OWNER_KEY (the executor, which is the DCA vault's owner). Same chain id. Choose deliberately — an onlyOwner call from the wrong signer just reverts.
Verification commands
bash
# backend — is the agent reachable?
curl http://localhost:5000/api/defi/quotes?fromChainId=42161&toChainId=42161&…
# a read-only production check
cd bz-backend && node scripts/checkRelayerDeployed.js
# policy + auth + engine tests (no chain access)
node scripts/test-policy.js
node scripts/test-wallet-auth.js
node scripts/test-autopilot-engine.jsWhat has no local equivalent
| Thing | Why |
|---|---|
| A staging database | There is only production Atlas |
bz-tokens, qube-backend | Server-only, no local checkout |
| Netlify prerender behaviour | The trailing-slash and rewrite semantics only exist on Netlify |
| Socket.io Redis adapter | Needs a real Redis |
| Backend unit tests | There are none — npm test exits 1 |
Gotcha collection
| Gotcha | Detail |
|---|---|
npx tsc --noEmit shows ~3,300 errors | Pre-existing, from node_modules. vite build is the gate |
| Headless Chrome mobile screenshots look broken | macOS clamps --window-size to ~500 px wide. Verify at 500 px or above |
| ethers v5 vs v6 | defi-dex and bz-backend use v5; swap-sdk uses v6. Not API-compatible |
patch-package is a dependency | Some node_modules are patched. Check patches/ |
| Marketing routes need three lists updated | The regex in src/index.tsx, src/MarketingApp.tsx, and src/routes/index.tsx |
MainNav throws on marketing pages | Web3Modal hooks need createWeb3Modal(), which MarketingApp does not run |