Skip to main content

System Flow

The competition platform combines off-chain scoring with on-chain settlement. Here’s the end-to-end flow:
1

Enrollment

Trader pays entry fee on-chain via the Shoot Anchor program. Funds are held in a PDA vault until settlement.
2

Live Trading

Trader executes trades on Adrena. A WebSocket consumer persists trade events to the database. REST position polling provides a fallback.
3

Metrics Computation

The off-chain engine (adrena-live-adapter) fetches positions or trade events, then computes PnL%, volume, win rate, consistency, and drawdown per trader.
4

Sybil Detection

Three uncorrelated heuristics run in parallel: funding source clustering, trading pattern correlation, and P&L mirroring.
5

Scoring

Two scoring formulas run in parallel — Tournament Score (additive, multi-factor) and Mutagen (multiplicative, per-trade). Both produce standings.
6

Social Layers

Matchups, narrative beats, rivalries, crowd votes, and risk events are generated from standings changes.
7

Settlement

Off-chain engine computes payouts. On-chain Shoot program distributes from the vault PDA to winning traders.

Architecture Diagram

┌──────────────────────────────────────────────────────────────┐
│  Frontend (Next.js 16 / React 19)                             │
│  Arena Hub · Leaderboard · Projection Lab · World Cup Bracket │
├──────────────────────────────────────────────────────────────┤
│  API Routes                                                   │
│  /competition · /world-cup · /cron · /agent · /admin · /health│
├──────────────────────────┬───────────────────────────────────┤
│  Core Engine             │  Autopilot SDK (@shoot/autopilot) │
│  Scoring · Sybil · Quests│  5 Playbooks · Indicators · Risk   │
│  Streaks · Narrative     │  24/7 Autonomous Execution        │
├──────────────────────────┼───────────────────────────────────┤
│  PostgreSQL (Prisma)     │  Solana Program (Anchor)          │
│  18 models               │  11 instructions · 4 account types│
│                          │  22 errors · 11 events            │
├──────────────────────────┼───────────────────────────────────┤
│  Keeper Service (Rust)   │  Adrena Data API                  │
│  Yellowstone gRPC        │  datapi.adrena.trade              │
│  Position Monitor        │  Live positions · Pool stats      │
│  Scoring Engine (pure)   │  Liquidity · APR                  │
│  Lifecycle FSM           │                                   │
└──────────────────────────┴───────────────────────────────────┘

Dual Data Path

The system uses two data sources for redundancy:
Trade events streamed via WebSocket consumer (lib/adrena/ws-consumer.ts) and persisted to the database. This is the real-time, official path.
Both paths feed into the same metrics computation pipeline — computeMetricsFromPositions() normalizes the data regardless of source.

Key Design Patterns

PatternWhere UsedWhy
Seeded RNGMatchups, group draws, risk scenariosDeterministic results — same inputs always produce same outputs for auditability
PDA EscrowOn-chain vaultsCapital immutability — funds locked until settlement, no admin withdrawal
Event-Driven QuestsQuest engineLifecycle hooks trigger progress increments; decoupled from scoring
Dual Data PathLive adapterWebSocket (primary) + REST (fallback) for redundancy
Heuristic Abuse DetectionSybil detectorThree uncorrelated signals (funding, pattern, P&L) prevent false positives
Streaming CommentaryNarrative engineReal-time beats generated from standings changes, not stored events
Pure-Function ScoringKeeper scoring engineAll metrics computed without IO — deterministic, testable, auditable
Yellowstone gRPCKeeper position monitorReal-time position account changes from Solana — matches Adrena’s keeper stack
Strict FSMCompetition lifecycleLinear state machine prevents invalid transitions and race conditions
Autopilot Tick LoopSDK executorStrategy → risk check → execute cycle repeats autonomously on configurable interval

Always-On Services

These services run independently of user requests:
ServiceTriggerCadencePurpose
Cohort SchedulerVercel cron (/api/cron/rotate-cohorts)Every 15 minutesCreates new cohorts when existing ones expire
Score RefreshVercel cron (/api/competition/refresh)Every 5 minutesRecomputes scores from latest trade data
Streak TrackerDaily UTC midnightOnce per dayUpdates streak state for all active traders
Quest EngineTrade eventsOn each qualifying eventIncrements quest progress
SSE StreamClient connectionReal-timePushes leaderboard updates to connected browsers

Database Schema

The system uses PostgreSQL (via Prisma) for persistent state. Key tables:
TablePurpose
enrollmentsWallet → cohort mappings with entry fee receipts
leaderboard_rowsPer-trader scores, metrics, and standings per cohort
sybil_flagsAbuse detection results with confidence levels
streak_statePer-wallet streak tracking (current streak, last activity)
quest_progressPer-wallet quest completion state
spectator_votesCrowd favorite votes per matchup
funded_trader_profilesFunded ladder status and progression
world_cup_seasonsTournament state (groups, brackets, matches)
See prisma/schema.prisma for full definitions.