Skip to Content
BackendChallenge Module

Challenge Module

The challenge module manages games, matches, matchmaking, and rewards in BattlesBit.

Terminology

TermDescription
GameA challenge template with rules and rewards
MatchAn instance of a game with actual participants
ParticipantA user joined in a specific match

Data Model

Game (Challenge Template)

File: internal/ent/schema/game.go

Fields: - id: UUID - title: string - description: string - is_active: bool - duration: int64 (nanoseconds) - max_players: uint8 - min_players: uint8 - max_positions: uint8 - start_v_balance: float64 (default: 100.0) - entry_fee: uint64 - cup: uint64 (ranking points) - win_exp: uint64 - lose_exp: uint64 - reward_price: uint64 - cover: string (image URL) - currency: enum (USDT, SOL, BT) Edges: - matches: []GameMatch - rewards: []Consumable

Match (Challenge Instance)

File: internal/ent/schema/game_match.go

Fields: - id: UUID - game_id: UUID - status: enum (open, pending, closed) - start_time: time - end_time: time - winner_id: UUID (nullable) Edges: - game: Game - winner: User - participants: []GameMatchParticipant - positions: []GameMatchPosition

Participant

File: internal/ent/schema/game_match_participant.go

Fields: - id: UUID - user_id: UUID - game_match_id: UUID - v_balance: float64 (virtual trading balance) - currency: enum Edges: - user: User - match: GameMatch - positions: []GameMatchPosition

Match Lifecycle

Game Created (template) User requests to join Matchmaking queue (NATS JetStream) Min players reached → Match Created status: open Trading period (duration) End time reached → ProcessCloseMatches() status: closed Calculate ranking & rewards Settlement complete

Matchmaking

File: matchmaking_usecase.go

Flow

  1. User calls AddParticipant(gameID, userID)
  2. Balance check against entry fee
  3. Message published to NATS MATCHMAKING stream
  4. MatchParticipants() worker consumes messages
  5. Per-game queue accumulates participants
  6. When min_players reached, Join() is called

Join Process

File: game_usecase.go:183

func (u *gameUseCase) Join( ctx context.Context, gameID uuid.UUID, participantIDs ...uuid.UUID, ) (*ent.GameMatch, error)
  1. Validate game is active
  2. Check player count limits
  3. Withdraw entry fee from each participant’s wallet
  4. Create match with start/end times
  5. Create participant records with initial v_balance
  6. Send notifications

Match Closure

File: game_usecase_close.go

Automatic Closure

ProcessCloseMatches() runs periodically:

  1. Query matches where status IN (pending, open) AND end_time <= now
  2. For each match, call TryCloseMatch()

Close Process

  1. Update match status to closed
  2. Notify all participants
  3. Send achievement events
  4. Calculate rewards

Ranking & Rewards

File: game_usecase_close.go:158

Ranking Algorithm

func (u *gameUseCase) calculateRanking( participants []*ent.GameMatchParticipant, ) []*ent.GameMatchParticipant { // Sort by v_balance DESC, then by position count DESC sort.Slice(ranking, func(i, j int) bool { if ranking[i].VBalance != ranking[j].VBalance { return ranking[i].VBalance > ranking[j].VBalance } return len(ranking[i].Edges.Positions) > len(ranking[j].Edges.Positions) }) }

Winner Rewards

  1. Set as match winner
  2. Add reward_price to wallet
  3. Add cup points to profile
  4. Add win_exp XP
  5. Grant consumable rewards

Loser Rewards

  1. Subtract cup points from profile
  2. Add lose_exp XP

Notifications

EventWhen
NotificationEventMatchCreatedMatch created, participants joined
NotificationEventMatchClosedMatch ended
Position eventsDuring trading

Services

GameService Interface

type GameService interface { // Game CRUD GetByID(ctx, id) (*Game, error) Create(ctx, input) (*Game, error) Update(ctx, id, input) (*Game, error) Delete(ctx, id) error // Match operations Join(ctx, gameID, participants...) (*GameMatch, error) TryCloseMatch(ctx, match) error ProcessCloseMatches(ctx) error // Position operations CreatePosition(ctx, userID, input) (*Position, error) ClosePosition(ctx, userID, positionID, percent) (*Position, error) CancelPosition(ctx, userID, positionID) error UpdatePosition(ctx, userID, positionID, input) (*Position, error) // Workers CalculatePositions(ctx, consumerName) error }

MatchMakingService Interface

type MatchMakingService interface { AddParticipant(ctx, userID, gameID) error MatchParticipants(ctx, consumerName) error }

Implemented Features

Room-Based Matching (replaces matchmaking queue)

  • Admin creates match rooms (CreateMatch)
  • Players browse and join rooms directly (JoinMatch)
  • Auto-start when max_players reached
  • Admin can manually start (StartMatch)
  • Match statuses: waiting → open → closed (+ canceled, archived)

Challenge Rules

  • Per-challenge allowed symbols (allowed_symbols)
  • Per-challenge max leverage (max_leverage)
  • Trade direction limits (allowed_directions: both/long_only/short_only)
  • Minimum trades requirement (min_trades)
  • Profit target (profit_target_pct)
  • Trading frequency limits (max_trades_per_minute)
  • Per-challenge margin type (deferred)

Risk Management

  • Max daily drawdown (max_daily_drawdown_pct)
  • Max total drawdown (max_total_drawdown_pct)
  • Max loss per trade (max_loss_per_trade_pct)
  • Auto-disqualification on breach
  • Kill switch: ForceCloseMatch, LockTrading

Disqualification

  • Drawdown breach detection
  • Auto-disqualification with reason
  • Disqualified players blocked from trading

Refunds

  • Match cancellation with entry fee refund (CancelMatch)

Lifecycle

  • Waiting status (room accepting players)
  • Canceled status (with refund)
  • Archived status
  • Auto-close expired matches (ProcessCloseMatches)

Remaining TODOs

  • Archived status
  • Challenge summary/report
Last updated on