Regras dos desafios
Sujeito a alterações conforme a evolução do MVP. Em caso de divergência entre versões, a publicada nesta página prevalece. Dúvidas: suporte.
e . O CI roda pnpm rules:check em todo PR: se algum arquivo no glob mudou desde last-verified, a seção é flagada e o PR é bloqueado até alguém: 1. Revisar a seção, atualizar texto se preciso 2. Bumpar last-verified` pro SHA atual
Override de emergência: label "rules-skip" no PR ou trailer "Rules: skip" no commit.
Sintaxe:
- Headers
## N. Títuloviram seções principais (entram no TOC da página) - `` separado por vírgula, suporta globs
- `
é gerenciado por humanos;pnpm rules:check
--bump <seção>` atualiza pro HEAD atual após review -->
Este documento descreve todas as regras de runtime hoje. Se algum comportamento divergir disso, é bug — abra issue ou contato.
1.Modos de pagamento (Tipo de Desafio)
Existem 2 modos de pagamento:
Por unidade (per_unit)
Streamer ganha um valor fixo por unidade da métrica cumprida, proporcional, até o teto definido.
- Exemplo: R$ 2 por kill, máximo R$ 60.
- Mínimo pra começar a pagar (opcional): se preenchido, streamer só ganha algo a partir desse número. Abaixo dele, paga R$ 0.
- Ex: mínimo 5 kills · streamer fez 4 → R$ 0; fez 10 → R$ 20.
- Se o mínimo ficar 0 ou vazio, paga desde a primeira unidade.
Meta (goal, tudo ou nada)
Streamer ganha o prêmio inteiro se atingir a meta, R$ 0 se ficar abaixo. Sem rateio.
- Exemplo: R$ 50 fixo se chegar a 30 kills, R$ 0 se chegar a 29.
- Meta é obrigatória nesse modo (> 0).
2.Prazos (modelo unificado)
Pós-LDP-1 (v0.43.0, 2026-05-19): modelo unificado. Não existe mais distinção async/live no DB. Cada desafio tem 2 prazos explícitos.
Os dois relógios
| Relógio | Coluna SQL | Significado |
|---|---|---|
| Prazo de aceite | accept_deadline_at (timestamptz) | Até quando o streamer pode aceitar/recusar |
| Janela de execução | execution_window_hours (int 1-24) | Quantas horas o streamer tem pra cumprir, contadas a partir do aceite |
Defaults por canal
| Canal de criação | Prazo aceite default | Janela execução default |
|---|---|---|
| Creator logado (form padrão) | 24h (configurável 1h-30d) | 6h (configurável 1h-24h) |
| Guest QR/livepix link | 1h (mais agressivo) | 6h (configurável 1h-24h) |
| Off-platform placeholder | 7 dias (substitui o TTL 72h legado) | 6h (após claim) |
Fluxo
1. Creator cria → accept_deadline_at = now() + accept_window. 2. Streamer aceita → cronômetro de execução começa: deadline_at = now() + execution_window_hours. 3. Streamer cumpre (consenso ou prova). Se passar do deadline_at, expira automaticamente (refund).
Conceito "live" vira cosmético
Não há mais flag challenge_type='live'. A coluna is_current (mig 054) mantém nome, mas semântica muda:
- Antes: armava timer one-shot.
- Agora: só sinaliza "este desafio aparece como toast no overlay OBS". Sem efeito em prazo.
Onde "live" entra na prática
- Streamer em live + overlay rodando + recebe desafio → toast aparece no OBS com botões aceitar/rejeitar (mig 097+).
- Streamer offline ou sem overlay → desafio fica na lista "Disponíveis" do dashboard, aceita quando puder.
Aceite automático (opt-in do streamer)
Streamer pode ativar toggle "aceite automático quando em live" nas configurações:
- Toggle on + streamer em live + overlay ativo → aceita imediato ao receber.
- Toggle on + streamer offline → desafio vai pra lista manual (mesmo do toggle off).
Race aberto — sem cronômetro individual
Em race aberto (target_mode='any' + challenge_mode='race'), o execution_window_hours não é aplicado individualmente. Quem aceita corre até o accept_deadline_at geral. Primeiro a submeter prova válida vence. Modelo competitivo clássico.
Trade-off conhecido: streamer único em race aberto pode "farmar" até o prazo geral expirar. Cenário raro — race geralmente atrai múltiplos.
Estensão de prazo (creator-side)
Creator pode estender execution_window_hours 1 vez por desafio, até dobrar o original. Ex: criou com 6h, pode estender pra 12h máx. Via extendChallengeDeadlineAction.
Pausa durante consenso/disputa
Quando entra em pending_creator_confirmation, pending_streamer_confirmation ou disputed, o cronômetro de execução pausa. Admin SLA 48h continua valendo (mig 116). Cronômetro nunca retoma — estados são terminais. Conflito = disputed permanente até admin decidir.
Aceite no limite
Streamer aceita 1s antes do accept_deadline_at expirar → 6h começam do aceite (não do limite). Predictable.
Rejeição após aceitar
Streamer pode rejeitar a qualquer momento durante a janela de execução. Sem penalidade. Fica com a tip (modelo LivePix puro — tip é irrevogável uma vez paga).
Apelido editável na doação (L29-L32)
Tanto em doações guest quanto empilhamento de creator logado:
- Campo "Nome a exibir" no form, default =
display_name, editável (max 30 chars). - Apelido aparece em: overlay OBS, email pro streamer, página pública do desafio.
- Histórico do creator (
Meus desafios) usa nome real (DB sempre temuser_id). - Empilhamento usa verbo "CASOU" em PT-BR (gíria po-pa): "Fulano CASOU R$5". EN usa
stacked.
3.Modos de aceitação (Quem aceita)
Existem 2 modos disponíveis hoje:
Streamer específico (targeted)
Direcionado a um único streamer cadastrado (ou um placeholder off-platform, ver §7).
- Só o streamer-alvo pode aceitar e submeter.
- Default da UI.
- Após aceite,
execution_window_hourscorre (default 6h, configurável 1h-24h).
Qualquer streamer (open + race)
Marketplace aberto. Qualquer streamer cadastrado pode aceitar e submeter.
- Sempre race: vence o primeiro que entregar prova válida e for aprovado.
- Race FIFO: aprovar uma submissão pula automaticamente todas as posteriores (aprovação out-of-order é rejeitada).
- Auto-approve pula contas suspeitas (
is_suspicious=true) — admin precisa rever manualmente.
4.Pagamento: o que você paga, quando e pra quem
Total debitado no momento da criação
TOTAL = escrow_do_desafio + gorjeta
- Escrow: prêmio máximo. Fica travado até aprovação/rejeição/expiração.
- Gorjeta: obrigatória, mínimo R$ 2,00. Streamer cadastrado pode subir o piso dele (campo
min_donation_cents). - Você não paga fee. A taxa de 5% é descontada do streamer no momento do payout.
Pra onde vai a gorjeta
| Cenário | Destino |
|---|---|
| Streamer específico cadastrado | Imediato pro saldo dele (irrevogável, mesmo sem aceite) |
| Streamer específico off-platform (placeholder) | Escrow até claim (72h) ou expira → refund pro creator |
| Qualquer streamer (race) | Escrow até aprovação → vai pro vencedor |
A gorjeta imediata em "streamer específico cadastrado" funciona como doação tipo LivePix: uma vez paga, é do streamer. Não tem volta nem cancelamento.
Pra onde vai o escrow
- Aprovado: pro streamer (descontado fee 5%).
- Rejeitado / expirado sem entrega válida: refund integral pra você.
- Aprovação parcial (admin): proporcional ao
mercy_pct; resto refund.
5.Aprovação, rejeição e expiração
Pós-v0.42 existem dois caminhos de aprovação: modelo de consenso (default, sem prova upfront) e fluxo de prova (race/marketplace). Detalhes completos em docs/challenge-verdict-consensus-model.md.
Modelo de consenso (single targeted)
Aprovação acontece por acordo das partes, sem prova obrigatória. Streamer e creator marcam Done/Wrong; só vira disputa em desacordo.
- Trust mode (creator marcou "Confiar no streamer" no form): streamer clica Done → payout libera imediato. Se creator discordar depois (até auto-approve), pode abrir disputa via admin SLA 48h.
- No-trust mode (default): streamer Done → status
pending_creator_confirmation. Creator tem 48h pra confirmar (paga) ou marcar Wrong (viradisputed). Silêncio do creator = aprova automático. - Caminho inverso: creator marca Done sozinho → status
pending_streamer_confirmation. Streamer tem 48h pra confirmar ou disputar. Silêncio do streamer = expira (penaliza streamer). - Disputa (
disputed): admin SLA 48h decide release (paga) ou expire (refund). Tabeladisputesregistra evidências. Ambas as partes podem ver a página read-only/disputes/[id]. - Cron 48h:
process_consensus_timeouts(mig 116) + route/api/cron/process-consensus-timeoutsdisparam emails e processam silêncios.
Fluxo de prova (race/marketplace)
Race/multi/any mode usa o fluxo clássico de prova:
- Streamer envia prova (link YouTube/Twitch). FIFO em race (primeira válida ganha; outras auto-rejected).
- Creator aprova manualmente OU auto-approve dispara depois do prazo (24h/48h/72h).
- Auto-approve não roda em contas suspeitas (revisão manual obrigatória).
- Race rejeita pular submissões anteriores (
race_out_of_order) e pula contas suspeitas no auto-approve.
Regras universais
- Streamer não pode aprovar própria submissão (anti-self-deal).
- Streamer precisa
is_verified=trueE subconta Asaas válida (is_payout_ready=true) pra receber. - Fee 5% PIX / 7% cartão (cartão dormente até v4.1+) é debitada no payout, não na criação.
Rejeição (fluxo de prova) / Wrong (consenso)
- Creator rejeita prova a qualquer momento antes do auto-approve. Streamer pode submeter de novo até deadline.
- "Marcar como Errado" no consenso vira
disputed(não recusa direta). - Rejeitar 3+ submissões do mesmo user lifetime ⇒
is_suspicious=true.
Expiração
Pós-LDP-1, há 2 motivos de expiração:
1. Prazo de aceite (accept_deadline_at) expirou sem aceite → refund integral (com exceções abaixo). 2. Janela de execução (deadline_at calculado de accept + execution_window_hours) expirou sem entrega → refund + tip (com exceções abaixo).
Cron expire_pending_submissions (*/15 min) cobre os 2 casos.
Refund integral pro creator, com exceções:
- Tip já liberada em "streamer específico cadastrado" no momento da criação (mig 066) → não refundada. Streamer ficou com tip mesmo se rejeitou ou prazo expirou.
- Off-platform sem claim em 7 dias: gorjeta + fee refundadas pro creator (mig 056, ajustado pelo LDP-1 — era 72h, agora 7 dias).
- Submissões parciais em per_unit: se streamer atingiu mínimo configurado, paga proporcional automaticamente (mig 040). Senão, refund total.
Removido em LDP-1: partial_approve_challenge RPC + slider mercy. Não há mais decisão manual do creator pra pagar parcial — é automático pelo modelo per_unit.
6.Submissão de prova
Streamer envia URL de prova (vídeo/clipe Twitch ou YouTube) + métrica reivindicada.
Cooldowns e limites
- 60s global entre submissões (qualquer desafio).
- 2min por desafio (não pode spammar o mesmo desafio).
- 20 submissões/h por user.
- Submissão antes do
created_atdo desafio é rejeitada. - URL duplicada: a mesma URL não pode ser reusada como prova em outro desafio (unique index).
Auto-flag suspeito
- Lifetime rejects ≥ 3 ⇒
is_suspicious=true. - Qualquer duplicata de proof URL detectada ⇒
is_suspicious=true. - Transição false→true debita
trust_score - 3(one-shot).
7.Off-platform (criar desafio pra streamer não cadastrado)
Você pode criar um desafio pra um streamer que ainda não tem conta no PayPerFrag, colando a URL do canal Twitch/YouTube ou @handle.
Como funciona
1. Você cola a URL e cria o desafio. 2. Criamos um placeholder (usuário fantasma) com a identidade do canal. 3. Escrow + gorjeta ficam travados em escrow (não vão imediato). 4. Streamer tem 72h pra criar conta no PayPerFrag e linkar o canal mencionado. 5. Se linkar dentro do prazo: desafio é "claimado" — streamer real assume e pode aceitar. 6. Se não linkar em 72h: desafio expira automaticamente, valor integral volta pra você.
Restrições
- Você não envia DM nem email. O streamer descobre por links virais, marketplace público.
- Identidade do canal é resolvida via API externa (YouTube Data API / Twitch Helix) antes de criar — se o canal não existe na API, falha cedo.
- Placeholder é imutável: não pode trocar o canal depois de criado.
8.Stacking (múltiplos creators contribuindo no mesmo desafio)
Outros creators podem somar valor ao seu desafio (aumentar o prêmio).
- Contribuição é append-only no ledger; cada contribuição é refundada individualmente em rejeição/expiração.
- Contribuidor não pode aceitar o desafio (anti-conluio).
- Streamer não pode contribuir no próprio desafio (anti-self-deal).
- Aprovação parcial (admin, edge case) paga
floor(share × mercy_pct/100)por contribuição.
9.Métricas válidas
Hoje o picker oferece 4 opções:
| Slug | PT-BR | EN |
|---|---|---|
kills | Kills | Kills |
finishes | Finalizações | Finishes |
wins | Wins | Wins |
other (livre) | Outro (você digita) | Other (you type) |
Auto-pluralização aplica nos 3 presets (singular após "por", plural após o número). Em "Outro", o termo digitado é usado nos dois slots sem alteração.
10.Saldo e top-up
- Saldo é mantido em centavos no ledger interno.
- Top-up via PIX (Asaas): mínimo R$ 2,00.
- KYC mínimo (CPF/CNPJ) obrigatório no primeiro top-up.
- Saldo é derivado de
transactions(append-only) — não há "saldo direto" editável. - Buckets:
available(saldo gastável e sacável),locked(escrow),revenue(plataforma).
11.Payout (streamer recebe)
Pra receber, o streamer precisa cumulativamente:
1. Estar is_verified=true (conta verificada). 2. Ter payout_account_id válido (subconta Asaas criada). 3. Ter is_payout_ready=true (onboarding KYC Asaas concluído).
Se qualquer um falhar no momento da aprovação, o approve_challenge lança streamer_not_verified / streamer_no_payout_account / streamer_not_payout_ready — você precisa rejeitar e o streamer precisa completar onboarding antes de tentar de novo.
12.Anti-fraude e governance
Camadas de proteção que rodam no banco (não dá pra burlar pelo frontend):
- Ledger
transactionsé append-only (UPDATE/DELETE revogados). - Escrow trava na mesma TX da criação do desafio (race-free).
- Submissões com URL duplicada são rejeitadas estruturalmente.
is_suspicious=true⇒ auto-approve não roda + cooldowns mais agressivos.- Admin pode ajustar saldo só via
admin_adjust_balance(auditável, kindadmin_adjustment). - Todo evento entra em
challenge_events(append-only) pra auditoria.
Tem dúvida? Reportar bug?
Se algo no app não bate com esse documento, é bug. Mande pra suporte@payperfrag.com com:
- O que você esperava (cite a seção)
- O que aconteceu
- ID do desafio se aplicável