Skip to content

Local development

Running the stack on your machine. Read the database section first — it is the part that can cause real damage.

Ports

ServicePortCommand
bz-backend5000npm run dev
bz-agent4000npm run dev
defi-dex defi shell5173npm run dev
defi-dex AI shell5174npm run dev:ai
defi-dex AI preview4174npm run preview:ai
These docs5199npm 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=false

Without 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 more

Read 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).

ConsequenceDetail
Quote cache not shared between processesFine for a single local backend
Socket.io Redis adapter disabledsocket.js checks typeof redisPubClient?.publish === 'function' and silently runs without it
Autopilot price series lost on restartdip_buy triggers will not fire until the window refills
LangGraph checkpoints lostMulti-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-alpine

Starting 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                     # :5174

bz-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 models

Do 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 FALLBACK

Set 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 shell

Two 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-sdk

Remember 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:routes

npm 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 passing

Two 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.js

What has no local equivalent

ThingWhy
A staging databaseThere is only production Atlas
bz-tokens, qube-backendServer-only, no local checkout
Netlify prerender behaviourThe trailing-slash and rewrite semantics only exist on Netlify
Socket.io Redis adapterNeeds a real Redis
Backend unit testsThere are none — npm test exits 1

Gotcha collection

GotchaDetail
npx tsc --noEmit shows ~3,300 errorsPre-existing, from node_modules. vite build is the gate
Headless Chrome mobile screenshots look brokenmacOS clamps --window-size to ~500 px wide. Verify at 500 px or above
ethers v5 vs v6defi-dex and bz-backend use v5; swap-sdk uses v6. Not API-compatible
patch-package is a dependencySome node_modules are patched. Check patches/
Marketing routes need three lists updatedThe regex in src/index.tsx, src/MarketingApp.tsx, and src/routes/index.tsx
MainNav throws on marketing pagesWeb3Modal hooks need createWeb3Modal(), which MarketingApp does not run

Internal + developer documentation. Usage figures are point-in-time snapshots — re-verify before publishing them.