A Jungle Gaming é uma software house especializada em iGaming — desenvolvemos plataformas de cassino online com tecnologia de ponta: NestJS, Bun, TanStack, DDD e arquitetura orientada a eventos. Somos apaixonados por engenharia de software e acreditamos que grandes produtos nascem de grandes times.
Este desafio é a porta de entrada para fazer parte desse time. Ele foi desenhado para refletir problemas reais do nosso dia a dia: sistemas distribuídos, tempo real, precisão monetária, experiência de usuário e arquitetura bem pensada.
Não esperamos perfeição — esperamos raciocínio claro, código limpo e decisões justificadas. Mostre como você pensa e como você constrói.
Um Crash Game é um jogo de cassino multiplayer em tempo real: um multiplicador sobe a partir de 1.00x e pode "crashar" a qualquer momento. Jogadores apostam antes da rodada e precisam sacar (cash out) antes do crash para garantir os ganhos — caso contrário, perdem a aposta.
Você deve construir o backend (engine do jogo, carteira, comunicação em tempo real) e o frontend (UI com animações, interface de apostas, histórico).
Autenticação não faz parte do escopo. O projeto já vem com Keycloak configurado — fique à vontade para substituí-lo por Auth0 ou Okta se preferir.
- Fase de Apostas — Janela configurável (ex: 10s) para apostar. Cada jogador pode fazer apenas uma aposta por rodada.
- Início da Rodada — O multiplicador começa em
1.00xe sobe continuamente. - Cash Out — O jogador pode sacar a qualquer momento durante a rodada. Pagamento =
aposta × multiplicador atual. Após sacar, não pode reentrar. - Crash — O multiplicador para em um ponto pré-determinado. Quem não sacou perde a aposta.
- Fim da Rodada — Resultados revelados, saldos atualizados, nova fase de apostas começa.
Restrições:
- Aposta mínima:
1.00/ Máxima:1.000,00 - Saldo insuficiente → aposta rejeitada
- Sem aposta na rodada → não pode sacar
- Rodada ativa → não pode apostar (apenas na fase de apostas)
┌──────────────────────────┐
│ Frontend │
│ (React + Tailwind CSS) │
└─────┬────────────┬────────┘
HTTP/REST WebSocket
│ │
┌─────▼────────────▼────────┐
│ Kong │
│ (API Gateway) │
└─────┬────────────┬────────┘
│ │
┌─────────▼──┐ ┌─────▼────────┐
│ Game │ │ Wallet │
│ Service │ │ Service │
│ (NestJS) │ │ (NestJS) │
└──┬─────┬──┘ └──────┬───────┘
│ └──────┬──────┘
┌────▼────┐ ┌────▼──────────┐
│PostgreSQL│ │ RabbitMQ/SQS │
└─────────┘ └───────────────┘
┌─────────────────┐
│ Keycloak │
│ (IdP — OIDC) │
└─────────────────┘
| Camada | Tecnologia |
|---|---|
| Runtime | Bun (latest) |
| Backend | NestJS + TypeScript (strict mode) |
| Banco | PostgreSQL 18+ com ORM (MikroORM, Prisma ou TypeORM) |
| Mensageria | RabbitMQ, Kafka Ou AWS SQS (Via LocalStack) |
| API Gateway | Kong ou AWS API Gateway |
| IdP | Keycloak (preferido), Auth0 ou Okta |
| WebSocket | @nestjs/websockets + socket.io ou ws |
| Frontend | Next.js, Vite ou Tanstack Start |
| Estilo | Tailwind CSS v4 + shadcn/ui |
| Estado | TanStack Query (server state) + Zustand ou Context (client state) |
| Testes | Bun test runner ou Vitest |
| Docs | Swagger / OpenAPI (@nestjs/swagger) |
| Infra | Docker Compose |
O sistema é dividido em dois bounded contexts:
Responsável pelo ciclo de vida da rodada, apostas, lógica de crash, provably fair e WebSocket.
- Round — Agregado principal. Gerencia o ciclo de vida completo de uma rodada.
- Bet — Aposta de um jogador em uma rodada.
- Crash Point — Multiplicador pré-determinado onde a rodada termina (gerado via algoritmo provably fair).
Cabe a você modelar os estados, transições, invariantes e regras de negócio de cada entidade.
Responsável pela carteira do jogador: saldo, operações de crédito e débito.
- Wallet — Uma por jogador. Nunca use ponto flutuante para dinheiro — use centavos inteiros (
BIGINT),NUMERICou biblioteca Decimal.
Game e Wallet se comunicam assincronamente via RabbitMQ/SQS. Você deve projetar os eventos, fluxos e estratégias de compensação necessários para garantir consistência entre os serviços.
O design dessa comunicação é parte central da avaliação.
O crash point de cada rodada deve ser verificável pelo jogador — garantindo que o resultado foi pré-determinado e não manipulado após as apostas.
Pesquise como algoritmos provably fair funcionam em crash games. Conceitos relevantes: hash chains, HMAC, seeds, house edge. O jogador deve ser capaz de verificar independentemente o crash point de qualquer rodada passada.
A implementação desse algoritmo (geração, cálculo e verificação) faz parte da avaliação.
Todos os endpoints são acessados via Kong (http://localhost:8000).
| Método | Endpoint | Auth | Descrição |
|---|---|---|---|
POST |
/wallets |
Sim | Cria carteira para o jogador autenticado |
GET |
/wallets/me |
Sim | Retorna carteira e saldo do jogador |
Crédito e débito não são expostos via REST — acontecem via message broker.
| Método | Endpoint | Auth | Descrição |
|---|---|---|---|
GET |
/games/rounds/current |
Não | Estado da rodada atual com apostas |
GET |
/games/rounds/history |
Não | Histórico paginado de rodadas |
GET |
/games/rounds/:roundId/verify |
Não | Dados de verificação provably fair |
GET |
/games/bets/me |
Sim | Histórico de apostas do jogador (paginado) |
POST |
/games/bet |
Sim | Fazer aposta na rodada atual |
POST |
/games/bet/cashout |
Sim | Sacar no multiplicador atual |
A conexão WebSocket é usada exclusivamente para comunicação do servidor para o cliente (push de eventos em tempo real). Todas as ações do jogador (apostar, sacar) são feitas via REST.
Você deve projetar os eventos que o servidor emite para manter todos os clientes sincronizados em tempo real. Considere quais informações o frontend precisa receber para:
- Saber quando uma nova rodada começa e quando a fase de apostas termina
- Acompanhar o multiplicador durante a rodada
- Saber quando a rodada crashou (e os dados de verificação)
- Ver as apostas e cash outs dos outros jogadores em tempo real
O design dos eventos WebSocket, seus payloads e a estratégia de sincronização do multiplicador fazem parte da avaliação.
Redirect para Keycloak (OIDC authorization code flow). Tratar callback e armazenar tokens.
Gráfico do Crash — Multiplicador animado subindo de 1.00x, curva visual, indicação clara do crash, exibição do hash da seed antes da rodada.
Controles de Aposta — Input de valor com validação, botão "Apostar" (habilitado só na fase de apostas), botão "Cash Out" (habilitado só durante rodada ativa com aposta pendente, exibindo pagamento potencial), timer de contagem regressiva.
Apostas da Rodada Atual — Lista em tempo real de todas as apostas, mostrando username, valor e status. Destacar cash outs.
Histórico de Rodadas — Últimos ~20 crash points, com código de cores (vermelho = crash baixo, verde = crash alto).
Info do Jogador — Saldo atual em destaque, username (do JWT).
- Dark mode — Estética de cassino (fundo escuro, acentos vibrantes/neon)
- Responsivo — Desktop e mobile
- Animações — Curva suave, feedback de cashout, animação de crash
- Loading states — Skeletons ou spinners
- Erros — Toast notifications (saldo insuficiente, erro de rede, etc.)
- Bun >= 1.x
- Docker & Docker Compose
O repositório já inclui docker-compose.yml e arquivos de suporte prontos para uso:
| Serviço | Imagem | Portas |
|---|---|---|
| PostgreSQL | postgres:18.3-alpine |
5432 (databases: games e wallets) |
| RabbitMQ | rabbitmq:4.2.4-management-alpine |
5672 (AMQP), 15672 (UI) |
| Keycloak | quay.io/keycloak/keycloak:26.5.5 |
8080 |
| Kong | kong:3.9.1 |
8000 (proxy), 8001 (admin) |
| Frontend | — | http://localhost:3000 |
| Game Service | — | http://localhost:4001 |
| Wallet Service | — | http://localhost:4002 |
Você pode modificar qualquer parte da infra. Prefere SQS ao invés de RabbitMQ? Outro API Gateway? Outro IdP? Fique à vontade. O único requisito é que bun run docker:up suba tudo sem nenhum passo manual — incluindo realm do Keycloak, config do Kong e migrations de banco.
O realm crash-game é importado automaticamente no docker:up. Nenhuma configuração manual necessária.
| Item | Valor |
|---|---|
| Admin UI | http://localhost:8080 (admin / admin) |
| Realm | crash-game |
| Client ID | crash-game-client (public, PKCE S256) |
| Usuário teste | player / player123 |
| OIDC discovery | http://localhost:8080/realms/crash-game/.well-known/openid-configuration |
Backend — pronto. Ambos os serviços já possuem scaffold NestJS funcional com estrutura DDD e rota GET /health. Estão integrados ao docker-compose.yml e roteados pelo Kong.
| Serviço | Porta direta | Via Kong |
|---|---|---|
| Game Service | 4001 |
http://localhost:8000/games/* |
| Wallet Service | 4002 |
http://localhost:8000/wallets/* |
O Kong em docker/kong/kong.yml inclui o plugin global cors para as origens da UI (localhost:3000 e 5173), com credentials: true, para que o browser aceite Authorization no preflight OPTIONS. Depois de alterar o YAML, recarregue o Kong: docker compose restart kong.
Cada serviço tem:
- Estrutura de camadas DDD:
domain/,application/,infrastructure/,presentation/ tests/unit/etests/e2e/prontos para receber os testespackages/na raiz do monorepo para pacotes compartilhados entre serviços (ex:@crash/eslint)
Frontend — scaffold Vite na pasta frontend/ (TanStack Query, Tailwind v4, shadcn-style, OIDC Keycloak). Serviço Docker na porta 3000; desenvolvimento local: cd frontend && bun dev (5173).
- Vite + React — opção mais leve, ideal se quiser controle total
- Next.js — SSR out-of-the-box, boa escolha para SEO e rotas
- TanStack Start — preferido na stack da Jungle Gaming
O placeholder no docker-compose.yml está comentado — descomente e adapte com seu Dockerfile e porta após criar o scaffold.
As credenciais de infraestrutura (PostgreSQL, RabbitMQ, Keycloak) estão hardcoded no docker-compose.yml — são valores de desenvolvimento local, sem necessidade de .env no root.
Cada serviço possui .env.example com as variáveis necessárias. Copie para .env antes de rodar fora do Docker:
cp services/games/.env.example services/games/.env
cp services/wallets/.env.example services/wallets/.envVocê pode modificar qualquer parte da infra. Prefere SQS ao invés de RabbitMQ? Outro API Gateway? Outro IdP? Fique à vontade. O único requisito é que bun run docker:up suba tudo.
git clone https://github.com/junglegaming/fullstack-challenge
cd fullstack-challenge
bun install
bun run docker:up # Sobe tudo (infra + serviços + frontend)
bun run docker:down # Para os containers
bun run docker:prune # Remove tudo (containers, volumes, imagens)Estrutura sugerida — pode adaptar, desde que mantenha a separação de camadas DDD (domain → application → infrastructure → presentation).
fullstack-challenge/
├── services/
│ ├── games/
│ │ ├── src/
│ │ │ ├── main.ts
│ │ │ ├── app.module.ts
│ │ │ ├── domain/
│ │ │ ├── application/
│ │ │ ├── infrastructure/
│ │ │ └── presentation/
│ │ ├── tests/ (unit/ + e2e/)
│ │ ├── Dockerfile
│ │ ├── .env
│ │ └── package.json
│ └── wallets/
│ ├── src/
│ │ ├── main.ts
│ │ ├── app.module.ts
│ │ ├── domain/
│ │ ├── application/
│ │ ├── infrastructure/
│ │ └── presentation/
│ ├── tests/ (unit/ + e2e/)
│ ├── Dockerfile
│ ├── .env
│ └── package.json
├── packages/ # Pacotes compartilhados entre serviços
│ │ # Ex: @crash/eslint
│ └── (pacotes serão adicionados aqui)
├── frontend/
│ ├── src/
│ │ ├── components/
│ │ ├── hooks/
│ │ ├── pages/
│ │ ├── services/
│ │ └── stores/
│ ├── Dockerfile
│ ├── .env
│ └── package.json
├── docker/
│ ├── kong/kong.yml
│ ├── keycloak/realm-export.json
│ └── postgres/init-databases.sh
├── docker-compose.yml
├── package.json
└── README.md
Unitários (camada de domínio):
- Ciclo de vida do Round (transições de estado, violação de invariantes)
- Lógica de Bet (cálculo de cashout, transições de status, validação de valor)
- Wallet (crédito, débito, saldo insuficiente, precisão monetária)
- Provably fair (cálculo determinístico do crash point, verificação da hash chain)
E2E (camada de API):
- Apostar → multiplicador sobe → cashout → saldo atualizado
- Apostar → crash → aposta perdida
- Erros de validação (saldo insuficiente, aposta dupla, aposta durante rodada ativa)
cd services/games && bun test tests/unit
cd services/wallets && bun test tests/unit
cd services/games && bun test tests/e2e # requer docker:up
cd frontend && bun testbun run docker:upsobe tudo sem passos manuais- Gameplay funciona (apostar → multiplicador → cashout/crash → liquidação)
- Dois serviços separados comunicando via RabbitMQ/SQS
- Sincronização em tempo real (múltiplas abas mostram o mesmo estado)
- Precisão monetária (sem ponto flutuante para dinheiro, saldo nunca negativo)
- Autenticação via IdP (Keycloak/Auth0/Okta) — backend valida JWTs
- Testes existem (unitários + E2E)
| Critério | Peso | O que é avaliado |
|---|---|---|
| DDD e Arquitetura | 25% | Bounded contexts, agregados, value objects, separação de camadas, design de sagas |
| Qualidade de Código | 20% | TypeScript strict, estilo consistente, nomes significativos, sem código morto |
| Testes | 20% | Cobertura de happy path + cenários de erro |
| Frontend/UX | 15% | Animações, responsividade, estética de cassino, loading states |
| Provably Fair | 10% | Hash chain, endpoint de verificação, cálculo correto |
| Histórico Git | 10% | Commits atômicos, mensagens claras, progressão lógica |
- Aritmética de ponto flutuante para valores monetários
bun run docker:upnão funciona- Sem testes
- Código plagiado/gerado por IA sem entendimento (haverá arguição)
| Item | Requisito |
|---|---|
| Repositório | GitHub público |
| README | Instruções de setup, decisões de arquitetura, trade-offs |
| Docker Compose | bun run docker:up sobe tudo |
| Usuário de teste | Pré-configurado no Keycloak com saldo na carteira |
| Prazo | 5 dias corridos a partir do recebimento |
Não obrigatórios, mas diferenciam candidatos excepcionais:
- Outbox/Inbox transacional — Garantia de at-least-once delivery e exactly-once processing
- Auto cashout — Jogador define multiplicador alvo para saque automático
- Auto bet — Configuração de apostas automáticas com estratégia (ex: Martingale, valor fixo) e stop-loss configurável
- Observabilidade — OpenTelemetry + Prometheus + Grafana para métricas de jogo (RTP, volume de apostas, latência de eventos WebSocket)
- Seed determinística para testes E2E — Script que popula banco e broker com estado consistente e reproduzível, permitindo simular cenários específicos (ex: crash em 1.5x, sequência de rodadas)
- Efeitos sonoros — Feedback de áudio para aposta, cashout, crash
- Leaderboard — Top jogadores por lucro (24h/semana)
- CI pipeline — GitHub Actions rodando testes no push
- Playwright — Testes E2E de ponta a ponta simulando fluxos reais do jogador no browser
- Rate limiting — Via Kong ou na aplicação
- Storybook — Biblioteca de componentes
- Fórmula da curva na UI — Exibir a fórmula para transparência
Entre em contato com o recrutador.
Boa sorte — e que o multiplicador esteja ao seu favor! 🎲