Appearance
Runtime topology
Hosts, processes, ports, data stores and the realtime layer.
Production
Everything server-side runs on one GCP VM, replacing earlier Azure and Kamatera boxes.
| Property | Value |
|---|---|
| Host | bz-prod / 34.14.156.239 (static) |
| Project | blazpay-latest |
| Zone | asia-south1-c |
| SSH user | bisht, key ~/.ssh/google_compute_engine |
| Fallback | gcloud compute ssh bz-prod --zone=asia-south1-c |
| Process manager | PM2 |
Processes
| PM2 app | Port | Directory | Branch | Build on deploy |
|---|---|---|---|---|
bz-backend | 5000 | /home/bisht/bz-backend | bishtNew | no |
bz-agent | 4000 | — | main | yes |
bz-tokens | 5003 | — | server-only, no local checkout | — |
qube-backend | 3001 | — | server-only, no local checkout | — |
PM2 config for the backend is bz-backend/ecosystem.config.cjs: single instance, fork mode, autorestart, max_memory_restart: 1G, kill_timeout and listen_timeout both 10 s, logs at /home/bisht/.pm2/logs/bz-backend-{error,out}.log with merged logs and a timestamped format.
Single instance, fork mode — and that is deliberate
The backend holds in-process cron schedulers. Running it in PM2 cluster mode would start N copies of every cron, so dcaCron would submit the same batch N times. If you ever need horizontal scaling, the crons must move out of the API process first.
Frontend
Netlify, two sites from one repo:
| Site | Build | Output | Config |
|---|---|---|---|
defi.blazpay.com | npm run build | dist/ | netlify.toml |
ai.blazpay.com | npm run build:ai | dist-ai/ | netlify.ai.toml |
Auto-deploys from the optimized branch. main is roughly 80 commits behind and effectively stale.
Data stores
MongoDB
| Property | Value |
|---|---|
| Deployment | MongoDB Atlas |
| Collections | 130+ across models/ and v1/models/ |
| Driver | Mongoose 9 |
| Network access | Includes 223.233.0.0/16 for rotating developer ISP IPs |
Local development points at production Atlas by default. See the warning under local development.
A second database, agents, holds the external copy-trading engine's copytrade_* collections. It is not part of bz-backend.
Redis
Four distinct uses, all on the same instance:
| Use | Key shape | TTL |
|---|---|---|
| Quote cache | meta.id → { data, meta, restProps } | 5 min |
| Fee summary cache | fees:summary:v1 | 5 min |
| Autopilot price series | rolling series per token | rolling window |
| Socket.io adapter | pub/sub channels | — |
LangGraph checkpointer (bz-agent) | per session | — |
Local dev falls back to an in-memory Redis stub
That is fine for the API, but it means the quote cache is not shared between processes and the Socket.io Redis adapter is disabled — socket.js checks typeof redisPubClient?.publish === 'function' and silently runs without it.
PostgreSQL + PGVector
Used only by bz-agent, for the vector knowledge base behind the FALLBACK intent. Accessed through pg and @langchain/community.
Realtime
Socket.io on the backend, with a Redis adapter when available.
services/socket.js
├─ default namespace chat + payments (chatAndPay.service.js)
├─ priceNamespace price streaming
├─ predictionNamespace prediction market updates
└─ poker initPokerSocket → pokerSocketHandler.jsAuth is middleware/authSocketMiddleware.js. onlineUsers is tracked in-process.
EventEmitter.defaultMaxListeners is raised to 25
The Redis adapter's Bus adds roughly two listeners per namespace. With five namespaces the Node default of 10 emits a spurious max-listeners warning, so socket.js raises the limit at the top of the file. Do not "fix" that line.
SOCKET_DEBUG_GUIDE.md in bz-backend is the troubleshooting reference. CORS for Socket.io is configured separately from REST CORS, and was the one real gap the AI domain split had to close.
Cron processes
All in-process in bz-backend, started in index.js behind CRON != 'false':
| Cron | Frequency | Purpose |
|---|---|---|
cronBatchDeposit (dcaCron.js) | every minute | CSIP execution + auto-claims |
autopilotCron | tick | Autopilot trigger evaluation |
attendanceCron | daily | Streaks and attendance |
discordBoosterCron | tick | Discord booster bonuses |
initPredictionCron() | tick | Prediction market settlement |
initPriceAlertsCron() | tick | Price alert firing |
initBridgeStatusCron() | tick | Bridge status reconciliation |
proposalStatusJob (daoCron.js) | tick | DAO proposal lifecycle |
Currently commented out in index.js: cronJobService, cronClaimRewards. Full inventory: cron jobs.
Ports summary
| Port | Process | Environment |
|---|---|---|
| 5000 | bz-backend | prod + local |
| 4000 | bz-agent | prod + local |
| 5003 | bz-tokens | prod only |
| 3001 | qube-backend | prod only |
| 5173 | defi-dex defi shell | local dev |
| 5174 | defi-dex AI shell | local dev |
| 4174 | defi-dex AI preview | local |
| 5199 | this documentation site | local |
5173, 5174, 5000, 3000, 3001, 3002 and 3039 are all in the backend CORS allowlist, so local frontends can talk to production API without a CORS change.
Request entry points
app.set('trust proxy', 1) is set, and express.json({ limit: '20mb' }) — the large limit exists for pasted Solidity source and generated dApp HTML.
Every request is logged with timestamp, method, URL, forwarded IP and origin.
External dependencies
A partial outage in any of these degrades a specific product rather than the platform:
| Service | Product affected if down |
|---|---|
| 21 aggregator APIs | Individual routes drop out of the fan-out; the rest still quote |
| Orderly Network | Perpetuals entirely |
| Zerion | Portfolio balances and the wallet card |
| Etherscan v2 | Contract auditor, token safety, approval scan, wallet card |
| Model provider (extraction) | Audit, token safety, every extraction node |
| Model provider (conversation) | Conversation node (falls back to the extraction provider) |
| Model provider (drafting) | Autopilot policy drafting |
| CoinMarketCap | Autopilot triggers |
| CoinGecko | Fee summary |
| ZeroDev | dApp builder on-chain deployment |
| 5 fiat ramps | Buy/sell — comparison degrades gracefully |
| Firebase | Push notifications |
| AWS SES | |
| Cloudinary | Image uploads |
| Moralis | Some balance lookups |
Aggregator failures are swallowed on purpose
AggregatorFactory.getQuotes wraps each provider in a try/catch so one failing API cannot kill the SSE stream. The user sees fewer routes, not an error. That resilience is also why a silently-broken provider can go unnoticed — see monitoring.
Deprecated hosts
For context when reading old scripts or logs:
| Host | Was |
|---|---|
Azure 20.198.176.70 | Old backend, scripts/deploy.sh |
Kamatera 194.113.195.167 (bz-backend-us) | Previous backend, scripts/deploy.kamatera.sh |
Both are superseded by scripts/deploy.gcp.sh.