Skip to main content

Prize Pool Escrow

Trustless tournament and event settlement with off-chain result computation and on-chain payout finality.


Overview​

The Prize Pools subsystem is Monsuta Core’s settlement layer for competitions.

  • Off-chain services run tournaments, compute final standings, and calculate exact payouts.
  • On-chain contracts escrow funds, finalize immutable results, and let winners claim directly.

This gives you the intended hybrid model:

  • high-performance competition logic off-chain
  • trust-minimized money movement on-chain

Current Implementation Status​

Contracts​

Implemented and tested:

  • PrizePoolFactory.sol (UUPS upgradeable)
  • PrizePool.sol (UUPS implementation used via ERC1967 proxies per pool)

Settlement Modes​

Each pool supports:

  • Native AVAX settlement (settlementToken = address(0))
  • ERC-20 settlement (settlementToken = tokenAddress)

Lifecycle (Implemented)​

  • Created
  • Registering
  • Active
  • Finalized
  • Completed
  • Cancelled

Test Coverage (Current)​

Contract suite currently passes with full lifecycle coverage (native + ERC-20), including:

  • registration and funding paths
  • authorization controls
  • result submission and claim flow
  • cancellation/refund flow
  • pause/unpause and admin controls

Architecture​

One factory deploys many isolated pool instances:

PrizePoolFactory (UUPS)
β”œβ”€ PrizePool Proxy #1 (competition A)
β”œβ”€ PrizePool Proxy #2 (competition B)
└─ PrizePool Proxy #N

Each pool is independent. A failure in one pool does not automatically compromise another.


Role Model​

Factory Owner​

  • Authorize/revoke deployers
  • Set default treasury
  • Upgrade factory logic
  • Update pool implementation pointer for future deployments

Authorized Deployer​

  • Create pools through factory
  • Becomes owner of pools it creates

Pool Owner​

  • Open registration / activate (owner-or-server path)
  • Cancel before finalization
  • Extend claim deadline
  • Update authorized server
  • Pause/unpause contract

Authorized Server​

  • Submit final results
  • Optional gasless registration path (registerPlayer)

Participants / Sponsors​

  • Fund pools
  • Register (self path)
  • Claim prizes (winners)
  • Refund deposits if cancelled

Pool Lifecycle​

Created
|
| openRegistration()
v
Registering
| \
| \ fund() / fundERC20() / register() / registerPlayer()
| \
| activate()
v
Active
|
| submitResults(winners, amounts) [authorizedServer]
v
Finalized
| \
| \ claim() by winners
| \ -> all winners claimed => Completed
|
-> claim window expires -> sweepUnclaimed() -> Completed

Before Finalized:
Created/Registering/Active -> cancel() -> Cancelled -> refund()

Core Contract Behavior​

1) Funding​

  • fund() for native pools
  • fundERC20(amount) for token pools
  • allowed only in Created or Registering
  • deposits are tracked per address for cancellation refunds

2) Registration​

  • register() supports entry fee collection
  • registerPlayer(address) allows server-side gasless registration
  • max participant cap enforced when configured

3) Activation​

  • openRegistration() transitions Created -> Registering
  • activate() transitions Registering -> Active

4) Result Finalization​

  • submitResults(winners[], amounts[]) callable by authorized server only in Active
  • validates non-empty and aligned arrays
  • computes treasury fee and verifies payout budget
  • sets Finalized + claim deadline
  • winners claim independently afterward

5) Claims and Closure​

  • claim() enforces winner eligibility and no double-claim
  • pool auto-completes when all winners claim
  • sweepUnclaimed() allows post-deadline cleanup to treasury/owner fallback

6) Cancellation and Refunds​

  • cancel() available before finalization
  • refund() returns depositor’s tracked amount in Cancelled

Game / tournament backend
-> computes standings + exact payout amounts
-> (recommended) signs settlement payload
-> queues job for distributor worker
-> worker validates pool state and signer policy
-> worker submits on-chain via authorized server wallet
-> winners claim directly from pool contract

This keeps tournament complexity off-chain while preserving on-chain settlement guarantees.


Operational Tooling (Current)​

The subsystem includes:

  • deployment/preflight/status/verify scripts
  • lifecycle operation scripts (create/open/fund/register/activate/results/claim/cancel/refund)
  • distributor worker for queued result submission + audits
  • per-run operational logs
  • one-shot and daemon runtime modes for workers

Security Properties (Current)​

  • no arbitrary admin withdrawal path for active prize funds
  • server can finalize results but cannot directly β€œsweep winners” by admin payout
  • reentrancy protection on external fund movements
  • winner claim tracking prevents double claim
  • cancellation refunds are bounded by tracked deposits
  • per-pool isolation via proxy-per-competition model

Known Constraints / Gaps​

These are important to understand in current version:

  1. Result trust root is still off-chain
    Final rankings originate from game backend authority.

  2. Duplicate winner protection is primarily off-chain today
    Worker-level validation can reject duplicates, but hard on-chain duplicate guard is a recommended hardening step.

  3. Attested results pipeline is not fully enforced end-to-end yet
    There is scaffolding for stricter signing workflows, but full mandatory attestation enforcement is still roadmap work.


Hardening Roadmap​

P0 β€” Settlement Integrity​

  • Define canonical signed result schema (e.g. tournamentId, poolAddress, winners, amounts, resultHash, expiry, nonce)
  • Enforce attestation verification in worker submit path
  • Add idempotency key policy to prevent accidental duplicate submissions
  • Add stale-active and near-claim-deadline alerting runbooks

P1 β€” Contract Hardening (v2 Candidate)​

  • Add explicit on-chain duplicate winner rejection in submitResults
  • Add richer settlement metadata events for auditability (e.g. result hash / external competition id)
  • Evaluate optional signature-verified submit function for cryptographic result provenance
  • Review treasury/payout edge behavior with non-standard ERC-20 tokens

P1 β€” Consistency Cleanup​

  • Remove duplicate source-tree ambiguity in contracts paths
  • Maintain single ABI/source-of-truth path for scripts + SDK + docs
  • Keep docs synchronized with implementation tracker

P2 β€” Mainnet Ops Readiness​

  • Publish full Fuji smoke test evidence (native + ERC-20, multi-wallet)
  • Publish multisig admin rotation runbook
  • Publish incident/disaster playbook (signer compromise, pause/cancel actions)
  • Publish unclaimed sweep accounting policy

Before high-value events:

  • use dedicated authorized server key with hardware-backed signing
  • move owner/admin functions to multisig
  • run preflight and pool-state checks before every result submission
  • enforce strict queue validation (status == Active, unique winners, bounded total payout)
  • monitor claim windows and unresolved finalized pools

Example: Off-chain to On-chain Result Bundle​

{
"tournamentId": "tourn-204",
"poolAddress": "0xPool...",
"winners": ["0xA...", "0xB...", "0xC..."],
"amounts": ["3040000000000000000", "1824000000000000000", "1216000000000000000"],
"resultHash": "0x...",
"signedBy": "0xAttestor...",
"signedAt": "2026-03-02T12:00:00Z"
}

The distributor worker should validate this payload, then submit submitResults from the authorized server wallet.


Summary​

Prize Pools are already functional as Monsuta’s trust anchor for competition payouts:

  • competition logic stays off-chain for speed and flexibility
  • settlement finality stays on-chain for player trust
  • hardening now focuses on stronger result attestation, duplicate-proofing, and production operations

For the intended hybrid model, this is the correct direction: off-chain heavy compute + on-chain escrowed settlement.