Challenge Module
The challenge module manages games, matches, matchmaking, and rewards in BattlesBit.
Terminology
| Term | Description |
|---|---|
| Game | A challenge template with rules and rewards |
| Match | An instance of a game with actual participants |
| Participant | A 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: []ConsumableMatch (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: []GameMatchPositionParticipant
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: []GameMatchPositionMatch 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 completeMatchmaking
File: matchmaking_usecase.go
Flow
- User calls
AddParticipant(gameID, userID) - Balance check against entry fee
- Message published to NATS
MATCHMAKINGstream MatchParticipants()worker consumes messages- Per-game queue accumulates participants
- When
min_playersreached,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)- Validate game is active
- Check player count limits
- Withdraw entry fee from each participant’s wallet
- Create match with start/end times
- Create participant records with initial
v_balance - Send notifications
Match Closure
File: game_usecase_close.go
Automatic Closure
ProcessCloseMatches() runs periodically:
- Query matches where
status IN (pending, open) AND end_time <= now - For each match, call
TryCloseMatch()
Close Process
- Update match status to
closed - Notify all participants
- Send achievement events
- 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
- Set as match winner
- Add
reward_priceto wallet - Add
cuppoints to profile - Add
win_expXP - Grant consumable rewards
Loser Rewards
- Subtract
cuppoints from profile - Add
lose_expXP
Notifications
| Event | When |
|---|---|
NotificationEventMatchCreated | Match created, participants joined |
NotificationEventMatchClosed | Match ended |
| Position events | During 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