Skip to main content
All runtime behavior is controlled through environment variables and data/competition-cohorts.json. No code changes are needed to adjust tier difficulty, rotate cohorts, or add new presets.

Environment Variables

Copy .env.example to .env.local and fill in your values.

Database

VariableRequiredDescription
DATABASE_URLOptionalPostgreSQL connection string. Format: postgresql://user@host:5432/dbname?schema=public. Without this, state is per-browser.

Authentication (Privy)

VariableRequiredDescription
NEXT_PUBLIC_PRIVY_APP_IDRequiredApp ID from dashboard.privy.io.
NEXT_PUBLIC_PRIVY_APP_SECRETRequiredApp secret key from the same dashboard.

Competition Data

VariableDefaultDescription
NEXT_PUBLIC_COMPETITION_PROVIDERadrenaadrena = live data from Adrena API. mock = offline seeded data (for UI review only).

Adrena APIs

VariableRequiredDescription
ADRENA_DATA_API_BASE_URLOptionalAdrena Data API. Default: https://datapi.adrena.trade.
ADRENA_COMPETITION_API_BASE_URLOptionalAdrena Competition Service REST API. Used as primary data source in production.
ADRENA_API_KEYProductionAuth key for the Adrena competition service WebSocket. Request from Adrena team.
ADRENA_WS_HOSTProductionWebSocket hostname for live trade event streaming (e.g. adrena-competition-service.onrender.com).

Solana

VariableDefaultDescription
NEXT_PUBLIC_SOLANA_RPChttps://api.mainnet-beta.solana.comSolana RPC endpoint for balance checks and transaction submission.
NEXT_PUBLIC_PROGRAM_AUTHORITYOn-chain authority pubkey for the Shoot Anchor program. Required for enrollment transactions.

Notifications

VariableRequiredDescription
DISCORD_WEBHOOK_URLOptionalDiscord webhook for general competition events (cohort start/end, settlements).
DISCORD_OPS_WEBHOOK_URLOptionalSeparate webhook for sybil detection alerts sent to the ops team.

Automation

VariableRequiredDescription
CRON_SECRETProductionBearer token sent in the Authorization header for /api/cron/* endpoints. Set to a random 32-character string and configure your cron scheduler to send it.

Cohort JSON Schema

All live cohorts are defined in data/competition-cohorts.json. The cron job at /api/cron/rotate-cohorts reads this file every 15 minutes and creates new cohorts when existing ones expire.

Top-level config

{
  "config": {
    "seasonId": "season-7",
    "cohortDurationHours": 336,
    "participantCap": 128,
    "entryFeeUsd": 25,
    "scoringWeights": {
      "pnlPercent": 8.5,
      "volumeUsd": 6,
      "consistency": 0.28,
      "winRate": 0.08,
      "drawdownPenalty": 0.65
    },
    "prizePoolSplit": [0.48, 0.24, 0.14, 0.08, 0.06],
    "fundedRewardShareBps": 450,
    "presets": [...]
  }
}
FieldTypeDescription
seasonIdstringIdentifier for the current season. Used to group cohorts for analytics.
cohortDurationHoursnumberDefault cohort window length. 336 = 14 days.
participantCapnumberMaximum enrolled wallets per cohort. New enrollments are rejected above this limit.
entryFeeUsdnumberDefault entry fee in USD. Overridden per cohort if set.
scoringWeightsobjectWeights for the tournament score formula (see Scoring Engine).
prizePoolSplitnumber[]Prize allocation for top 5 finishers. Must sum to 1.0. Default: [0.48, 0.24, 0.14, 0.08, 0.06].
fundedRewardShareBpsnumberBasis points of protocol fee revenue allocated to Funded-tier traders.

Cohort object

{
  "id": "cohort-crypto-0315",
  "name": "Crypto Impulse 03.15",
  "presetId": "crypto-impulse",
  "state": "live",
  "startTime": "2026-03-15T00:00:00.000Z",
  "endTime": "2026-03-29T00:00:00.000Z",
  "narrative": "...",
  "rewardPoolUsd": 3750,
  "entryFeeUsd": 25,
  "participantCap": 128,
  "enrolledWallets": ["GZXqnVpZ..."]
}
FieldTypeDescription
idstringUnique cohort identifier. Convention: cohort-{theme}-{MMDD}.
namestringDisplay name shown in the UI.
presetIdstringReferences a preset in config.presets. Inherits quest points, streak multiplier, raffle tickets.
state"live" | "settling" | "closed"Cohort lifecycle state. Only "live" cohorts are scored.
startTimeISO stringCohort window start. Used as the P&L baseline snapshot time.
endTimeISO stringCohort window end. After this, the cron marks the cohort for settlement.
narrativestringOne-sentence cohort description shown in the hub UI.
rewardPoolUsdnumberTotal prize pool in USD (60% of entry fees collected).
entryFeeUsdnumberEntry fee for this specific cohort.
participantCapnumberMax enrollments for this cohort.
enrolledWalletsstring[]Solana wallet addresses enrolled in this cohort. The live adapter fetches positions for all of these.

Preset Templates

Presets define the narrative and engagement parameters overlaid on a cohort. Each cohort references one preset by presetId.
PresetFocusQuest PointsStreak MultiplierRaffle Tickets
macro-sprintRWA majors (FX, macro pairs)701.2×4
carry-breakerRates and commodities (oil, gold)901.35×5
crypto-impulseHigh-beta crypto perps601.15×3
To add a new preset, append to config.presets in competition-cohorts.json:
{
  "id": "my-preset",
  "name": "My Preset",
  "focus": "asset class description",
  "tagline": "One sentence shown in UI.",
  "questRewardPoints": 80,
  "streakMultiplier": 1.25,
  "raffleTickets": 4
}

Tier Parameters

Challenge tiers are configured per-deployment. Pass rate guardrails recommend when to adjust:
TierEntry FeeMin CapitalProfit TargetMax DDDaily LimitDurationFunded Eligible
Scout$2$508%5%3%7 daysNo
Ranger$5$20010%8%4%10 daysNo
Veteran$10$50012%6%3%10 daysNo
Elite$25$2,00015%5%2.5%14 daysYes
Apex$50$5,00015%4%2%14 daysYes
Pass rate guardrails:
  • Pass rate > 40%: tighten profit target by 2pp or reduce max drawdown by 1pp
  • Pass rate < 15%: relax drawdown limit by 1pp or extend duration by 2 days
  • Retry rate < 20%: increase retry discount from 30% to 40%
Alpha test validation: Scout achieved 35.5% (target ~35%). Ranger achieved 21.4% (target ~25%) — recommend relaxing Ranger max drawdown from 8% to 9% for first production run.

Cron Schedule

Automated jobs run via Vercel cron (configured in vercel.json):
EndpointCadencePurpose
/api/cron/rotate-cohortsEvery 15 minutesChecks for expired cohorts, creates new ones from preset templates, marks settling cohorts as closed.
The cron endpoint requires the Authorization: Bearer <CRON_SECRET> header. Configure your cron scheduler (Vercel, cron-job.org, etc.) to include this header.

Adding a Scheduled Cohort

To schedule a cohort to go live at a specific time, add it to data/competition-cohorts.json with "state": "live" and set startTime to the desired launch time. The cron job will begin scoring wallets once the start time is reached.

Scoring Weight Tuning

Scoring weights in config.scoringWeights control the tournament score formula. Changing these affects leaderboard rankings globally:
WeightKeyEffect of Increasing
PnL%pnlPercent (8.5)Rewards raw profit more — whale-favoring
VolumevolumeUsd (6)Rewards activity more — benefits active traders
Consistencyconsistency (0.28)Rewards even equity curves — benefits disciplined traders
Win RatewinRate (0.08)Rewards win ratio more
Drawdown penaltydrawdownPenalty (0.65)Penalizes risk-taking more harshly
Changing scoring weights mid-cohort is not recommended — it retroactively alters standings and may be perceived as unfair. Only change weights between cohorts.