Skip to main content

Payout System

SignalNet uses a pool-normalized payout system to ensure fair distribution of rewards while preventing insolvency.

Overview

Each round has a fixed reward pool funded by the tournament creator. Payouts are computed off-chain, normalized against the pool, and committed on-chain as a merkle root. Users claim their payouts with merkle proofs.

Payout Calculation

Phase 1: Raw Scores

Each submission is scored independently:

Final Score = w_ic × IC + w_tc × TC + w_mmc × MMC

Default weights: IC 40%, TC 35%, MMC 25%. The high TC+MMC weight (60%) means signal uniqueness matters more than raw accuracy.

Phase 1.5: Diversity Penalty (Anti-Sybil)

After scoring, the engine computes pairwise Spearman correlation between every pair of submissions. If two submissions from different users have similarity above 85%, the later one receives a diversity penalty:

diversity_factor = max(0, 1 - max_similarity_to_any_other_submission)
adjusted_score = final_score × diversity_factor

Example: If your signal has 0.95 correlation with another user's signal, your diversity factor = 0.05, and your adjusted score is reduced by 95%. Identical signals (correlation = 1.0) receive zero payout.

This makes sybil attacks economically irrational — duplicate accounts earn almost nothing.

Phase 2: Raw Payouts

Raw Reward = Final Score × Stake × (PayoutMultiplier / 10000)

With PayoutMultiplier = 2500 (25%), a score of +0.10 on a 1,000 SIGNAL stake yields:

Raw Reward = 0.10 × 1,000 × 0.25 = 25 SIGNAL

For negative scores, the loss is capped:

Raw Loss = min(|Score| × Stake × 0.25, Stake × MaxLoss / 10000)

Phase 3: Pool Normalization

This is the critical step. Raw payouts are computed independently per user, so the sum of positive rewards could exceed the available pool.

Budget = Reward Pool + Total Slashed

If total positive rewards exceed the budget:

Scale Factor = Budget / Total Positive Rewards
Scaled Reward = Raw Reward × Scale Factor

All positive payouts are scaled down proportionally. Negative payouts (slashing) are not affected — they've already been capped at max_loss_bps.

Phase 4: Final Payout

If reward > 0:  Payout = Stake + (Reward × Scale Factor)
If reward < 0: Payout = Stake - Loss
If reward = 0: Payout = Stake

Examples

Normal Round (budget sufficient)

Pool: 50,000 SIGNAL. Three participants:

UserStakeScoreRaw RewardPayout
Alice1,000+0.10+251,025
Bob2,000+0.04+202,020
Carol500-0.08-10490

Total positive rewards: 45. Budget: 50,000 + 10 = 50,010. Scale factor: 1.0 (no scaling needed).

Oversubscribed Round (budget insufficient)

Pool: 1,000 SIGNAL. Fifty participants all score well:

Total StakeAvg ScoreTotal Raw RewardsBudgetScale Factor
80,000+0.061,2001,0000.833

Every positive reward is multiplied by 0.833. A user who would have earned 30 SIGNAL now earns 25 SIGNAL. The pool is fully distributed but never exceeded.

Genesis Round (no slashing)

With max_loss_bps = 0:

  • No tokens are slashed from negative scorers
  • Budget = Reward Pool only (no slash supplement)
  • Negative scorers still get their full stake back
  • All rewards come purely from the pool

Design Decisions

Why normalize instead of first-come-first-served?

Without normalization, the last users to claim could find the contract empty. Pool normalization ensures every participant gets their fair share, computed before any claims begin.

Why include slashed tokens in the budget?

Slashed tokens are locked in the StakeVault. Rather than leaving them stranded, they supplement the reward pool. This creates a natural feedback loop: poor signals fund rewards for good signals.

Why not make it purely zero-sum?

A pure zero-sum system (losers fund winners) discourages participation — nobody wants to play a game where the expected value is zero. The reward pool creates positive expected value for skilled contributors, which bootstraps the network.

Why cap losses?

Uncapped losses would deter participation, especially from smaller contributors. The 25% cap ensures contributors can recover from a bad round without being wiped out.

Contract Flow

1. Tournament creator calls createRound() → deposits reward pool
2. Users call submitSignal() → stakes locked in StakeVault
3. After resolution, manager computes normalized payouts off-chain
4. Manager posts merkle root via postResults()
5. Users call claimReward(proof) → StakeVault transfers payout
6. Manager can reclaim unused pool tokens after claim deadline

Parameters

ParameterDefaultGenesis
Payout multiplier25% (2500 bps)25%
Max loss25% (2500 bps)0%
Min stake100 SIGNAL100
Max stake10,000 SIGNAL2,000
Max models per user33