gavel
Documento legal

Regras dos desafios

Atualizado em 14/05/2026

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ítulo viram 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ógioColuna SQLSignificado
Prazo de aceiteaccept_deadline_at (timestamptz)Até quando o streamer pode aceitar/recusar
Janela de execuçãoexecution_window_hours (int 1-24)Quantas horas o streamer tem pra cumprir, contadas a partir do aceite

Defaults por canal

Canal de criaçãoPrazo aceite defaultJanela execução default
Creator logado (form padrão)24h (configurável 1h-30d)6h (configurável 1h-24h)
Guest QR/livepix link1h (mais agressivo)6h (configurável 1h-24h)
Off-platform placeholder7 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=&#39;live&#39;. 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=&#39;any&#39; + challenge_mode=&#39;race&#39;), 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 tem user_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_hours corre (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árioDestino
Streamer específico cadastradoImediato 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 (vira disputed). 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). Tabela disputes registra 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-timeouts disparam 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=true E 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_at do 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:

SlugPT-BREN
killsKillsKills
finishesFinalizaçõesFinishes
winsWinsWins
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, kind admin_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
Regras dos desafios — PayPerFrag