Adoption Bonus & Cohorts
The adoption-bonus pool — also called the "virality bonus" — rewards
early adopters when later cohorts of players join. It is funded by
viralityBonusProportion of every entry, and it is paid out per-cohort
in a way that creates a viral incentive to recruit new players.
Cohort assignment
Each entrant ID k (1-indexed) has a cohort membership (used for claim
eligibility) of:
cohort(k) = floor(log_{B_v}(k)) + 1
where B_v is viralityBonusLogBase. Cohort sizes grow geometrically.
Note the distinction between membership and pool funding: the
contribution from an entry is routed into a pool indexed by
max(2, floor(log_{B_v}(k)) + 1) (see Pool routing below). As a result
Pool[1] is always empty — cohort-1 members exist for eligibility, but
their contributions are redirected into Pool[2], which therefore spans
entrant IDs 1 to B_v² - 1.
Cohort d |
Entrant ID range | Size |
|---|---|---|
| 1 | 1 to B_v - 1 |
B_v - 1 |
| 2 | B_v to B_v² - 1 |
B_v² - B_v |
| 3 | B_v² to B_v³ - 1 |
B_v³ - B_v² |
d |
B_v^(d-1) to B_v^d - 1 |
B_v^d - B_v^(d-1) |
Example: B_v = 3
| Cohort | IDs | Size |
|---|---|---|
| 1 | 1–2 | 2 |
| 2 | 3–8 | 6 |
| 3 | 9–26 | 18 |
| 4 | 27–80 | 54 |
| 5 | 81–242 | 162 |
| 6 | 243–728 | 486 |
Example: B_v = 10
| Cohort | IDs | Size |
|---|---|---|
| 1 | 1–9 | 9 |
| 2 | 10–99 | 90 |
| 3 | 100–999 | 900 |
| 4 | 1000–9999 | 9000 |
Pool routing
Each ticket contributes v = entryAmount * p_v / (p_g + p_v) wei (post fee)
into a cohort pool. The routing rule is:
c = cohort(k)
if c == 1: contribution → Pool[2] (cohort 1 redirects forward)
if c >= 2: contribution → Pool[c]
Why the redirect? Cohort 1 has no earlier cohort to reward. Sending its contributions to Pool[2] ensures cohort 1 itself gets paid when cohort 3 arrives and unlocks Pool[2].
Pool unlocking and claimability
A cohort's pool unlocks for claims the moment the next cohort starts:
Pool[j] is claimable ⇔ C >= j + 1
where C = floor(log_{B_v}(N)) + 1 is the current max cohort
The claim distributes Pool[j] equally among the j-1 earlier cohorts
("teams"), and each team's share is then split equally among its members:
team_prize(j) = floor(Pool[j] / (j - 1))
individual_prize(d, j) = floor(team_prize(j) / size(d))
What this looks like in practice
With B_v = 3 and N = 50:
| Pool | Funded by | Balance | Claimable? | Goes to |
|---|---|---|---|---|
| Pool[2] | IDs 1–8 | 8 * v |
✅ (C=4 > 2) | Cohort 1 |
| Pool[3] | IDs 9–26 | 18 * v |
✅ (C=4 > 3) | Cohorts 1, 2 |
| Pool[4] | IDs 27–50 | 24 * v |
❌ (C=4 is the last cohort) | Host leftover |
The earlier you enter, the more pools you eventually claim from — every later cohort funds a slice of every earlier cohort's payout.
Leftover handling
The last cohort's pool is never unlocked for participant claims (no later
cohort exists to trigger it). After the game ends the host calls
clearLeftoverAdoptionBonus(gameId) to sweep Pool[C] to themselves.
This is a one-time action per game.
The larger a game grows beyond a cohort boundary, the more this leftover accumulates — it functions as a built-in operator-revenue mechanism on top of the deposit fee.
Why this is "viral"
Each cohort can only earn from cohorts after it. So each member has a direct incentive to attract more entrants — it grows the next cohort, unlocks their current pool, and grows the future pools they'll later claim from.
Two claim paths
There are two functions for claiming adoption bonuses, designed for different recipient gas profiles:
| Path | Function | Gas to recipient | DOS-resistant |
|---|---|---|---|
| Batch (default) | batchClaimAdoptionBonusPrize |
30,000-gas cap on .call |
✅ failed transfers don't revert the batch |
| Single-claim | claimAdoptionBonusPrize |
full forwarding | reverts on transfer failure |
EOAs and standard smart wallets should use the batch path. Wallets with
heavy receive/fallback logic that need more than 30k gas should use
the single-claim path.
See Public Interface for both signatures.