Deployment
Two-environment architecture deployed to Cloudflare Workers via GitHub Actions. Two-branch model — develop is the integration/staging branch, main is production.
Environments
| Staging | Production | |
|---|---|---|
| Trigger | Push to develop | Push to main (merge from develop) |
| Web Worker | cabal-trading-staging | cabal-trading-production |
| Indexer Worker | cabal-indexer-staging | cabal-indexer-production |
| URL | https://cabal-trading-staging.cabal-trading.workers.dev | https://cabal.trading |
| Database | Separate Supabase project | Primary Supabase project |
| Solana | Devnet (Raydium), dev treasury wallet | Mainnet (Jupiter), prod treasury wallet |
| Hyperliquid | Testnet (api.hyperliquid-testnet.xyz) | Mainnet (api.hyperliquid.xyz) |
| Indexer cron | Every 10 min | Every 5 min |
How It Works
- Feature branches: Branch off
develop, PR back todevelop. CI runs on PRs to bothdevelopandmain. - Push to
develop: Staging deploys automatically (.github/workflows/deploy-staging.yml) — migrate → deploy app + indexer. - Merge
developtomain: Deploys production (.github/workflows/deploy-production.yml) — migrate → deploy app + indexer.
Build uses bunx @opennextjs/cloudflare build. Deploy uses bunx wrangler deploy --env <environment>.
Wrangler Environments
Both apps/web/wrangler.toml and apps/indexer/wrangler.toml use wrangler environments ([env.staging], [env.production]). Top-level config is shared; environment-specific config (Hyperdrive, R2, vars, crons) lives under the env sections.
Public vars (NEXT_PUBLIC_PRIVY_APP_ID, SQUADS_VAULT_ADDRESS, etc.) live in wrangler.toml — not in Infisical or GitHub Secrets.
Bare wrangler deploy (no --env) will fail intentionally — prevents wrong-environment deploys.
Secrets Management — Infisical
Secrets are managed via Infisical. Cloudflare Worker secrets are synced automatically via Infisical's native Cloudflare integration — no wrangler secret put calls needed.
How secrets flow
Infisical is the single source of truth. Two integrations push secrets where they're needed:
| Integration | Direction | What it does |
|---|---|---|
| Cloudflare Worker sync | Infisical → CF Workers | Pushes secrets directly to running workers. Rotate a DB password in Infisical → workers pick it up without a redeploy. |
| GitHub Action (OIDC) | Infisical → CI runner | Injects secrets into CI at runtime for builds, migrations, E2E tests. Zero secrets stored in GitHub. |
For local dev, use infisical run --env=staging -- bun dev (or .env.local for manual setup).
Adding/Updating Secrets
- Update the secret in Infisical dashboard (or via
infisical secrets set) - Cloudflare Worker sync pushes it to Workers automatically — no deploy needed
- CI picks up changes on next run automatically (fetched via OIDC at runtime)
- For changes that need a redeploy (e.g., build-time vars), merge to
developormain
Required GitHub Secrets
None. All secrets (including CLOUDFLARE_API_TOKEN) are stored in Infisical and fetched at CI runtime via OIDC.
Infisical Setup
Project structure
/web/
staging/ — 16 secrets (DATABASE_URL, PRIVY_APP_SECRET, HELIUS_API_KEY, etc.)
production/ — 16 secrets (same keys, production values)Cloudflare Worker syncs (4 total)
| Infisical env | → | Cloudflare Worker |
|---|---|---|
| staging | → | cabal-trading-staging |
| staging | → | cabal-indexer-staging |
| production | → | cabal-trading-production |
| production | → | cabal-indexer-production |
GitHub Actions (OIDC)
CI workflows use the Infisical/secrets-action GitHub Action with OIDC auth to fetch secrets at runtime. No secrets are stored in GitHub — GitHub's OIDC token authenticates directly with Infisical.
Machine identity: github (ID: 6f2cbe50-29b6-49a2-8f5d-b3d574a601dc), OIDC auth, Viewer role.
Secrets in Infisical (per environment)
| Secret | Purpose |
|---|---|
DATABASE_URL | Supabase pooler connection string |
PRIVY_APP_SECRET | Privy app secret |
PRIVY_AUTH_PUBLIC_KEY | P256 authorization public key |
PRIVY_AUTH_PRIVATE_KEY | P256 authorization private key |
HELIUS_RPC_URL | Helius RPC endpoint (devnet or mainnet — cluster derived from URL) |
HELIUS_API_KEY | Helius API key for REST API calls (api.helius.xyz) |
HELIUS_WEBHOOK_SECRET | Helius webhook verification |
JUPITER_API_KEY | Jupiter API key for swaps |
FAL_API_KEY | fal.ai for image generation |
KIE_API_KEY | Kie.ai for Sora 2 video generation |
KIE_WEBHOOK_SECRET | Kie webhook verification |
BYTEPLUS_API_KEY | BytePlus for Seedance video generation |
BYTEPLUS_WEBHOOK_SECRET | BytePlus webhook verification |
R2_ACCOUNT_ID | Cloudflare account ID for R2 S3-compatible API |
R2_ACCESS_KEY_ID | R2 API token access key |
R2_SECRET_ACCESS_KEY | R2 API token secret key |
CLOUDFLARE_API_TOKEN | Cloudflare API token (used by wrangler in CI) |
Public vars in wrangler.toml (not secrets)
| Var | Purpose |
|---|---|
NEXT_PUBLIC_SITE_URL | Site URL for client-side |
NEXT_PUBLIC_GA_ID | Google Analytics (production only) |
NEXT_PUBLIC_PRIVY_APP_ID | Privy app ID (public, used client-side and server-side) |
SQUADS_VAULT_ADDRESS | Squads v4 multisig vault PDA (receives Jupiter/DBC fees) |
SQUADS_MULTISIG_ADDRESS | Squads multisig program account (for proposal deep-links) |
HL_API_URL | Hyperliquid API URL |
HL_WS_URL | Hyperliquid WebSocket URL |
R2_BUCKET_NAME | R2 bucket name (cabal-assets-staging / cabal-assets) |
R2_PUBLIC_URL | R2 public URL for assets |
Local Development
With Infisical (recommended)
infisical login # One-time auth
infisical run --env=staging -- bun dev # Start dev server with staging secretsWithout Infisical
cp apps/web/.env.example apps/web/.env.local
# Fill in values manually
bun devManual Setup (one-time)
Before the first staging deploy:
- Create a staging Supabase project → get pooler connection string (port 6543)
- Create Hyperdrive for staging:
bunx wrangler hyperdrive create cabal-staging --connection-string="<staging_pooler_url>"→ update ID inapps/web/wrangler.toml - Create staging R2 bucket:
bunx wrangler r2 bucket create cabal-assets-staging - Generate a dev treasury Solana wallet (separate from prod)
- Seed secrets into Infisical staging environment
- Set up Cloudflare Worker syncs in Infisical dashboard
- Create machine identity with OIDC auth for GitHub Actions
- Add
CLOUDFLARE_API_TOKENto Infisical (both staging and production environments)
Custom Domain
The cabal.trading domain route must be pointed at the cabal-trading-production worker (not the old cabal-trading worker). Update this in the Cloudflare dashboard under Workers → Routes after the first production deploy.
Local Scripts
bun run deploy:staging # Build & deploy web to staging
bun run deploy:production # Build & deploy web to production
bun run deploy:indexer:staging # Deploy indexer to staging
bun run deploy:indexer:production # Deploy indexer to productionTroubleshooting
| Symptom | Cause | Fix |
|---|---|---|
wrangler deploy fails (no --env) | Missing environment flag | Use --env staging or --env production |
| "Trading not available" | SQUADS_VAULT_ADDRESS not set | Check wrangler.toml value |
| Staging HL agents show zero balances | Indexer queries HL testnet | Expected — agents have mainnet HL addresses |
Build fails with missing NEXT_PUBLIC_* | Not set at build time | These must be in the workflow env: block, not just wrangler vars |
Old cabal-trading worker still running | Not cleaned up after migration | Delete from CF dashboard after verifying new workers work |
| Secrets not in Worker | Infisical sync not configured | Check Infisical dashboard → Integrations → Cloudflare |
Known Issues
- No production deploy approval gate: Push to
mainauto-deploys with no manual approval step and no rollback mechanism. A bad merge goes straight to production. - Zero centralized error tracking: No Sentry or equivalent. Errors are only visible in Cloudflare Workers logs (limited retention, no alerting).
sync-cli.ymlhas unpinned actions: Usesactions/checkout@v4while all other workflows pin to specific commit SHAs.- CLI sync workflow broken: The
GH_PATsecret has expired. CLI must be synced to the publiccabaltrading/clirepo manually until a new PAT is generated and the workflow is updated. - Device code cleanup: Expired device authorization codes accumulate indefinitely in the database. No cleanup cron or TTL-based purge exists.