Appendix F · Project setup
Everything needed to start development on day one: repository structure, accounts and keys, environment variables, local startup, workers and schedules, and deployment.
Repository structure#
One repository (pnpm + Turborepo monorepo), TypeScript everywhere except contracts.
vivra/
├── apps/
│ ├── web/ # Next.js: landing + dapp (4 screens)
│ ├── admin/ # Next.js: back-office (SIWE)
│ ├── api/ # HTTP + WebSocket server (Fastify)
│ └── workers/ # BullMQ: analysis, tracking, orchestrator, agents, buyback, treasury, bots
├── packages/
│ ├── db/ # schema, migrations, client (Appendix A)
│ ├── core/ # pure business logic: statuses, recovery score, voting rules
│ ├── chain/ # chain configuration, ABIs, viem reads and writes (Appendix D)
│ ├── agents/ # YAML specs, prompts, output schemas, tools (Appendix C)
│ ├── templates/ # website, visual, and mini-app templates
│ └── ui/ # shared components, design tokens
├── contracts/ # Foundry : BuybackBurner + tests
├── infra/ # Dockerfiles, docker-compose, deployment configuration
└── .github/workflows/ # CIAccounts and keys to create (milestone M0)#
| Service | Purpose | Variable |
|---|---|---|
| Alchemy (Robinhood Chain) | primary mainnet and testnet RPC | RPC_URL_MAINNET, RPC_URL_TESTNET |
| Second RPC provider | fallback | RPC_URL_MAINNET_FALLBACK |
| Surplus Intelligence | agent inference | SURPLUS_API_KEY, SURPLUS_BASE_URL |
| Image generator (through Surplus if available, otherwise a dedicated provider) | Designer | IMAGE_API_KEY |
| X API (paid write access in the source plan) | Vivra and CTO token posts | X_API_KEY, X_API_SECRET, then one OAuth token per account stored encrypted in the database |
| Telegram (@BotFather) | Vivra bot and TG Bot | TELEGRAM_BOT_TOKEN, TELEGRAM_ALERTS_CHAT_ID |
| Email service (Resend or Postmark) | magic links and codes | EMAIL_API_KEY, EMAIL_FROM |
| Invisible captcha (Cloudflare Turnstile) | signup | TURNSTILE_SECRET |
| Cloudflare Pages (or Vercel) | CTO token websites | PAGES_API_TOKEN, PAGES_ACCOUNT_ID |
| Vivra domain DNS (Cloudflare) | <ticker>.<domain> subdomains | DNS_ZONE_ID |
| Object storage (Cloudflare R2 or S3) | visuals and memes | S3_ENDPOINT, S3_BUCKET, S3_ACCESS_KEY, S3_SECRET_KEY |
| Container host (Railway, Fly.io, or Render) | API and workers | managed by host |
| Managed Postgres + managed Redis | data and queues | DATABASE_URL, REDIS_URL |
| Secrets vault (host-provided or Doppler) | hot-wallet keys | see below |
| Sentry | errors | SENTRY_DSN |
Environment variables#
Commit .env.example without any secret values. Actual values belong in the secrets vault.
# General
NODE_ENV=development
APP_URL=http://localhost:3000
API_URL=http://localhost:4000
CHAIN_ENV=testnet # testnet | fork | mainnet
# Data
DATABASE_URL=postgres://vivra:vivra@localhost:5432/vivra
REDIS_URL=redis://localhost:6379
# Security
SESSION_SECRET= # 32 random bytes
EMAIL_HMAC_KEY= # for users.email_hash
EMAIL_ENC_KEY= # AES-256-GCM for users.email_enc
SLICE_SCHEDULE_KEY= # encrypts slice times
# Chain
RPC_URL_TESTNET=
RPC_URL_MAINNET=
RPC_URL_MAINNET_FALLBACK=
BUYBACK_BURNER_ADDRESS=
BUYBACK_HOT_WALLET_KEY_REF= # vault reference, never the key itself
AI_HOT_WALLET_KEY_REF=
TREASURY_OPERATOR_KEY_REF= # signs escrow claims (Collection and CTO Fees wallets)
# Inference and services
SURPLUS_BASE_URL=https://api.surplusintelligence.ai/v1
SURPLUS_API_KEY=
IMAGE_API_KEY=
X_API_KEY=
X_API_SECRET=
TELEGRAM_BOT_TOKEN=
TELEGRAM_ALERTS_CHAT_ID=
EMAIL_API_KEY=
EMAIL_FROM=vote@<vivra-domain>
TURNSTILE_SECRET=
PAGES_API_TOKEN=
PAGES_ACCOUNT_ID=
DNS_ZONE_ID=
S3_ENDPOINT=
S3_BUCKET=
S3_ACCESS_KEY=
S3_SECRET_KEY=
SENTRY_DSN=Hot-wallet private keys must never appear in environment variables readable by all services. Only the relevant worker reads its key from the vault at startup.
Running locally#
pnpm install
docker compose -f infra/docker-compose.yml up -d # Postgres 16 + Redis 7
pnpm db:migrate && pnpm db:seed # seed: 30 fictional tokens, 1 cycle, 1 demo operation
pnpm dev # web :3000, admin :3001, api :4000, workers
cd contracts && forge test # BuybackBurner tests
forge test --fork-url $RPC_URL_MAINNET # mainnet-fork testspnpm dev starts workers with CHAIN_ENV=testnet: no mainnet write is possible unless CHAIN_ENV is mainnet.
Queues and scheduled jobs (BullMQ)#
| Queue | Trigger | Purpose |
|---|---|---|
projects.analyze | team address submission or rerun | complete Scanner analysis of one token |
tracking.candidates | every 15 min, the 5 candidates in the current vote | live market data and recovery score |
tracking.live | continuous, active CTO token only (pool or curve events) | chart, slices, war room |
tracking.followup | hourly, tokens taken over through Day 30 | follow-up, verdict, handover |
cycles.clock | every minute | open and close voting, create operation at Day 0 |
orchestrator.tick | every minute | start due steps (Appendix C) |
agents.run | orchestrator request | run an agent, concurrency 3 |
tg.updates | Telegram webhook | TG Bot anti-spam and answers |
buyback.slices | delayed job per slice, at its selected time | execute a slice |
buyback.refill | daily | refill hot wallet from reserve |
treasury.daily | daily at 00:30 UTC | sweep, claim, split, Surplus top-up, ledger |
social.publish | every minute | publish approved posts whose scheduled time has passed |
alerts.check | every minute | thresholds from the Security page |
Each job is idempotent: retrying after a crash must not repeat a transaction already sent. Check tx_hash in the database before sending.
Deployment#
| Environment | Where | When |
|---|---|---|
| Preview | one frontend URL per pull request | every PR |
| Staging | container host + Robinhood Chain testnet | every merge to main |
| Pre-production | staging pointed at a mainnet fork | before launch (milestone M5) |
| Production | container host + mainnet | manually triggered v* tag |
CI (GitHub Actions) on every PR: lint, types, unit tests, forge test, application builds, and secret scan (gitleaks). A failed step blocks merging.
Recommended implementation order#
The development roadmap defines milestones. Within the code, this order minimizes blocked work:
packages/dbandpackages/chain(schema, configuration, ABIs).- On-demand analysis and recovery score, plus submission and review back-office (M1).
- Orchestrator with stub agents returning fixed outputs, then real agents one at a time (M2).
- BuybackBurner on testnet, then Buyback Bot and treasury (M3).
- Voting, back-office, complete dapp, bots (M4).
- Full-cycle rehearsal on a mainnet fork (M5).